diff --git a/lib/src/bip/bip32/address_encoders/a_blockchain_address_encoder.dart b/lib/src/bip/bip32/address_encoders/a_blockchain_address_encoder.dart index d9518c98..d100c7f8 100644 --- a/lib/src/bip/bip32/address_encoders/a_blockchain_address_encoder.dart +++ b/lib/src/bip/bip32/address_encoders/a_blockchain_address_encoder.dart @@ -15,7 +15,8 @@ abstract class ABlockchainAddressEncoder { AddressEncoderType.bitcoinP2WPKH => BitcoinP2WPKHAddressEncoder(hrp: args[0]), AddressEncoderType.cosmos => CosmosAddressEncoder(hrp: args[0]), AddressEncoderType.ethereum => EthereumAddressEncoder(skipChecksumBool: args[0] == 'true'), - }; + AddressEncoderType.solana => SolanaAddressEncoder(), + } as ABlockchainAddressEncoder; } String serializeType() { diff --git a/lib/src/bip/bip32/address_encoders/address_encoder_type.dart b/lib/src/bip/bip32/address_encoders/address_encoder_type.dart index a14bcbcd..595f3651 100644 --- a/lib/src/bip/bip32/address_encoders/address_encoder_type.dart +++ b/lib/src/bip/bip32/address_encoders/address_encoder_type.dart @@ -4,4 +4,5 @@ enum AddressEncoderType { bitcoinP2WPKH, cosmos, ethereum, + solana, } diff --git a/lib/src/bip/bip32/address_encoders/solana_address_encoder.dart b/lib/src/bip/bip32/address_encoders/solana_address_encoder.dart new file mode 100644 index 00000000..12619682 --- /dev/null +++ b/lib/src/bip/bip32/address_encoders/solana_address_encoder.dart @@ -0,0 +1,17 @@ +import 'package:codec_utils/codec_utils.dart'; +import 'package:cryptography_utils/cryptography_utils.dart'; + +/// The [SolanaAddressEncoder] class is designed for encoding addresses in accordance with the Solana network. +/// Solana uses the Base58 encoding to generate addresses from public keys +class SolanaAddressEncoder extends ABlockchainAddressEncoder { + @override + AddressEncoderType get addressEncoderType => AddressEncoderType.solana; + + @override + List get args => []; + + @override + String encodePublicKey(ED25519PublicKey publicKey) { + return Base58Codec.encode(publicKey.bytes); + } +} diff --git a/lib/src/bip/bip32/bip32.dart b/lib/src/bip/bip32/bip32.dart index 5eab7096..d7eba200 100644 --- a/lib/src/bip/bip32/bip32.dart +++ b/lib/src/bip/bip32/bip32.dart @@ -5,12 +5,14 @@ export 'address_encoders/bitcoin/bitcoin_p2sh_address_encoder.dart'; export 'address_encoders/bitcoin/bitcoin_p2wpkh_address_encoder.dart'; export 'address_encoders/cosmos_address_encoder.dart'; export 'address_encoders/ethereum_address_encoder.dart'; +export 'address_encoders/solana_address_encoder.dart'; export 'bip32_key_metadata.dart'; export 'bip32_network_version.dart'; export 'derivation_path/legacy_derivation_path/legacy_derivation_path.dart'; export 'derivation_path/legacy_derivation_path/legacy_derivation_path_element.dart'; export 'derivators/a_derivator.dart'; export 'derivators/legacy_derivators/a_legacy_derivator.dart'; +export 'derivators/legacy_derivators/ed25519_derivator.dart'; export 'derivators/legacy_derivators/secp256k1_derivator.dart'; export 'hd_wallet/a_hd_wallet.dart'; export 'hd_wallet/legacy_hd_wallet.dart'; diff --git a/lib/src/bip/bip32/derivators/a_derivator.dart b/lib/src/bip/bip32/derivators/a_derivator.dart index f7465f39..8db14088 100644 --- a/lib/src/bip/bip32/derivators/a_derivator.dart +++ b/lib/src/bip/bip32/derivators/a_derivator.dart @@ -6,6 +6,7 @@ abstract class ADerivator { DerivatorType derivatorType = DerivatorType.values.byName(type); return switch (derivatorType) { DerivatorType.secp256k1 => Secp256k1Derivator(), + DerivatorType.ed25519 => ED25519Derivator(), }; } diff --git a/lib/src/bip/bip32/derivators/derivator_type.dart b/lib/src/bip/bip32/derivators/derivator_type.dart index daf82ada..ca19c4cb 100644 --- a/lib/src/bip/bip32/derivators/derivator_type.dart +++ b/lib/src/bip/bip32/derivators/derivator_type.dart @@ -1,3 +1,4 @@ enum DerivatorType { secp256k1, + ed25519, } diff --git a/lib/src/bip/bip32/derivators/legacy_derivators/ed25519_derivator.dart b/lib/src/bip/bip32/derivators/legacy_derivators/ed25519_derivator.dart new file mode 100644 index 00000000..66fdc796 --- /dev/null +++ b/lib/src/bip/bip32/derivators/legacy_derivators/ed25519_derivator.dart @@ -0,0 +1,114 @@ +// Class was shaped by the influence of several key sources including: +// "blockchain_utils" - Copyright (c) 2010 Mohsen +// https://github.com/mrtnetwork/blockchain_utils/. +// +// BSD 3-Clause License +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import 'dart:typed_data'; + +import 'package:cryptography_utils/cryptography_utils.dart'; +import 'package:cryptography_utils/src/bip/bip32/derivators/derivator_type.dart'; + +/// [ED25519Derivator] is a class that implements the derivation of [ED25519PrivateKey] objects. +class ED25519Derivator extends ALegacyDerivator { + static const List _hardenedPrivateKeyPrefix = [0x00]; + + @override + DerivatorType get derivatorType => DerivatorType.ed25519; + + /// Derives a [ED25519PrivateKey] from a mnemonic phrase and full derivation path + @override + Future derivePath(Mnemonic mnemonic, LegacyDerivationPath legacyDerivationPath) async { + ED25519PrivateKey masterPrivateKey = await deriveMasterKey(mnemonic); + ED25519PrivateKey childPrivateKey = masterPrivateKey; + for (LegacyDerivationPathElement derivationPathElement in legacyDerivationPath.pathElements) { + childPrivateKey = deriveChildKey(childPrivateKey, derivationPathElement); + } + + return childPrivateKey; + } + + /// Derives a master [ED25519PrivateKey] from a mnemonic phrase + @override + Future deriveMasterKey(Mnemonic mnemonic) async { + LegacyMnemonicSeedGenerator seedGenerator = LegacyMnemonicSeedGenerator(seedLength: 64); + Uint8List seedUint8List = await seedGenerator.generateSeed(mnemonic); + Uint8List hmacHashUint8List = HMAC(hash: Sha512(), key: Bip32HMACKeys.hmacKeyED25519Bytes).process(seedUint8List); + + Uint8List privateKeyUint8List = hmacHashUint8List.sublist(0, 32); + Uint8List chainCodeUint8List = hmacHashUint8List.sublist(32); + + EDPrivateKey edPrivateKey = EDPrivateKey.fromBytes(privateKeyUint8List); + BigInt masterFingerprint = ABip32PublicKey.calcFingerprint(edPrivateKey.edPublicKey.bytes); + + ED25519PrivateKey ed25519privateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 0, + chainCode: chainCodeUint8List, + fingerprint: masterFingerprint, + parentFingerprint: BigInt.from(0x00000000), + masterFingerprint: masterFingerprint, + ), + edPrivateKey: edPrivateKey, + ); + + return ed25519privateKey; + } + + /// Derives a child [ED25519PrivateKey] from a parent [ED25519PrivateKey] and a single element of a derivation path + @override + ED25519PrivateKey deriveChildKey(ED25519PrivateKey ed25519privateKey, LegacyDerivationPathElement derivationPathElement) { + return _deriveHard(ed25519privateKey, derivationPathElement); + } + + /// Derives a child [ED25519PrivateKey] from a parent [ED25519PrivateKey] and a single hardened element of a derivation path + ED25519PrivateKey _deriveHard(ED25519PrivateKey parentEd25519PrivateKey, LegacyDerivationPathElement legacyDerivationPathElement) { + Bip32KeyMetadata parentBip32KeyMetadata = parentEd25519PrivateKey.metadata; + if (parentBip32KeyMetadata.chainCode == null) { + throw Exception('Cannot derive hardened key without chain code'); + } + + Uint8List dataUint8List = + Uint8List.fromList([..._hardenedPrivateKeyPrefix, ...parentEd25519PrivateKey.bytes, ...legacyDerivationPathElement.toBytes()]); + + Uint8List hmacHashUint8List = HMAC(hash: Sha512(), key: parentBip32KeyMetadata.chainCode!).process(dataUint8List); + Uint8List scalarUint8List = hmacHashUint8List.sublist(0, 32); + Uint8List chainCodeUint8List = hmacHashUint8List.sublist(32); + + EDPrivateKey edPrivateKey = EDPrivateKey.fromBytes(scalarUint8List); + + return ED25519PrivateKey( + metadata: parentBip32KeyMetadata.deriveNext( + newChainCode: chainCodeUint8List, + newCompressedPublicKey: edPrivateKey.edPublicKey.bytes, + newShiftedIndex: legacyDerivationPathElement.shiftedIndex, + ), + edPrivateKey: edPrivateKey, + ); + } +} diff --git a/lib/src/bip/bip32/keys/bip32_hmac_keys.dart b/lib/src/bip/bip32/keys/bip32_hmac_keys.dart index c1ee51cd..00227402 100644 --- a/lib/src/bip/bip32/keys/bip32_hmac_keys.dart +++ b/lib/src/bip/bip32/keys/bip32_hmac_keys.dart @@ -4,4 +4,6 @@ import 'dart:typed_data'; /// Stores HMAC keys used in BIP-32 derivations. class Bip32HMACKeys { static Uint8List get hmacKeySecp256k1Bytes => utf8.encode('Bitcoin seed'); + + static Uint8List get hmacKeyED25519Bytes => utf8.encode('ed25519 seed'); } diff --git a/lib/src/slip/slip44.dart b/lib/src/slip/slip44.dart index e4efdc8b..52435842 100644 --- a/lib/src/slip/slip44.dart +++ b/lib/src/slip/slip44.dart @@ -4,4 +4,5 @@ class Slip44 { static const int ethereum = 60; static const int cosmos = 118; static const int kira = 118; + static const int solana = 501; } diff --git a/lib/src/wallet_config/bip44_wallets_config.dart b/lib/src/wallet_config/bip44_wallets_config.dart index 96371fcc..6d944994 100644 --- a/lib/src/wallet_config/bip44_wallets_config.dart +++ b/lib/src/wallet_config/bip44_wallets_config.dart @@ -30,4 +30,10 @@ class Bip44WalletsConfig { derivator: Secp256k1Derivator(), curveType: CurveType.secp256k1, ); + + static LegacyWalletConfig solana = LegacyWalletConfig( + addressEncoder: SolanaAddressEncoder(), + derivator: ED25519Derivator(), + curveType: CurveType.ed25519, + ); } diff --git a/pubspec.yaml b/pubspec.yaml index 66cbaa93..cf6d0119 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: cryptography_utils description: "Dart package containing utility methods for common cryptographic and blockchain-specific operations" publish_to: none -version: 0.0.26 +version: 0.0.27 environment: sdk: ">=3.2.6" diff --git a/test/bip/bip32/address_encoders/a_blockchain_address_encoder_test.dart b/test/bip/bip32/address_encoders/a_blockchain_address_encoder_test.dart index c0df963e..e66be073 100644 --- a/test/bip/bip32/address_encoders/a_blockchain_address_encoder_test.dart +++ b/test/bip/bip32/address_encoders/a_blockchain_address_encoder_test.dart @@ -85,5 +85,16 @@ void main() { expect(actualAddressEncoder, isA()); expect((actualAddressEncoder as EthereumAddressEncoder).skipChecksumBool, false); }); + + test('Should [return SolanaAddressEncoder] when type is "solana()"', () { + // Arrange + String actualSerializedType = 'solana()'; + + // Act + ABlockchainAddressEncoder actualAddressEncoder = ABlockchainAddressEncoder.fromSerializedType(actualSerializedType); + + // Assert + expect(actualAddressEncoder, isA()); + }); }); } diff --git a/test/bip/bip32/address_encoders/solana_address_encoder_test.dart b/test/bip/bip32/address_encoders/solana_address_encoder_test.dart new file mode 100644 index 00000000..63ed3e14 --- /dev/null +++ b/test/bip/bip32/address_encoders/solana_address_encoder_test.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +import 'package:cryptography_utils/cryptography_utils.dart'; +import 'package:test/test.dart'; + +void main() { + EDPoint actualPointA = EDPoint( + curve: Curves.ed25519, + n: BigInt.parse('7237005577332262213973186563042994240857116359379907606001950938285454250989'), + x: BigInt.parse('19793122580953396643657614893737675412715271732516190437477497872396293476517'), + y: BigInt.parse('11043535616153659670420426046812415327322207260394876224747641236324749531277'), + z: BigInt.parse('52307325976996590356746334614254516249809358280279038039749391123110162041889'), + t: BigInt.parse('18663140037355883143136513717086037971900193259991003799674694468591719114115'), + ); + + ED25519PublicKey actualPublicKey = ED25519PublicKey( + metadata: Bip32KeyMetadata( + depth: 5, + shiftedIndex: 0, + fingerprint: BigInt.parse('2889905688'), + parentFingerprint: BigInt.parse('58474422'), + masterFingerprint: BigInt.parse('83580899'), + chainCode: base64Decode('+ZGw7L105N54Uwwvz4mMpJ6iNJj7xumWTKKCh8QAgIY='), + ), + edPublicKey: EDPublicKey(actualPointA), + ); + + group('Tests of SolanaAddressEncoder.serializeType()', () { + test('Should [return "solana()"] for SolanaAddressEncoder', () { + // Arrange + SolanaAddressEncoder actualSolanaAddressEncoder = SolanaAddressEncoder(); + + // Act + String actualSerializedType = actualSolanaAddressEncoder.serializeType(); + + // Assert + String expectedSerializedType = 'solana()'; + + expect(actualSerializedType, expectedSerializedType); + }); + }); + + group('Tests of SolanaAddressEncoder.encodePublicKey()', () { + test('Should [return Base58 address] for given public key', () { + // Arrange + SolanaAddressEncoder actualSolanaAddressEncoder = SolanaAddressEncoder(); + + // Act + String actualAddress = actualSolanaAddressEncoder.encodePublicKey(actualPublicKey); + + // Assert + String expectedAddress = 'EyjopNhvxFMtpzuQMxTqP5P5tDjsjCPWUSxfERkNEXmY'; + + expect(actualAddress, expectedAddress); + }); + }); +} diff --git a/test/bip/bip32/derivators/a_derivator_test.dart b/test/bip/bip32/derivators/a_derivator_test.dart index 18087458..b3ef80fc 100644 --- a/test/bip/bip32/derivators/a_derivator_test.dart +++ b/test/bip/bip32/derivators/a_derivator_test.dart @@ -13,5 +13,16 @@ void main() { // Assert expect(actualDerivator, isA()); }); + + test('Should [return ED25519Derivator] when type is "ed25519"', () { + // Arrange + String actualType = 'ed25519'; + + // Act + ADerivator actualDerivator = ADerivator.fromSerializedType(actualType); + + // Assert + expect(actualDerivator, isA()); + }); }); } diff --git a/test/bip/bip32/derivators/legacy_derivators/ed25519_derivator_test.dart b/test/bip/bip32/derivators/legacy_derivators/ed25519_derivator_test.dart new file mode 100644 index 00000000..0a82204d --- /dev/null +++ b/test/bip/bip32/derivators/legacy_derivators/ed25519_derivator_test.dart @@ -0,0 +1,372 @@ +import 'dart:convert'; + +import 'package:cryptography_utils/cryptography_utils.dart'; +import 'package:cryptography_utils/src/bip/bip32/derivators/derivator_type.dart'; +import 'package:test/test.dart'; + +void main() { + ED25519Derivator actualED25519Derivator = ED25519Derivator(); + Mnemonic actualMnemonic = Mnemonic.fromMnemonicPhrase('shift shed release funny grab acquire fish cannon comic proof quantum cabbage'); + + group('Tests of ED25519Derivator.derivatorType getter', () { + test('Should [return DerivatorType.ed25519] for ED25519Derivator', () { + // Act + DerivatorType actualDerivatorType = actualED25519Derivator.derivatorType; + + // Assert + DerivatorType expectedDerivatorType = DerivatorType.ed25519; + + expect(actualDerivatorType, expectedDerivatorType); + }); + }); + + group('Tests of ED25519Derivator.serializeType()', () { + test('Should [return "ed25519"] for ED25519Derivator', () { + // Act + String actualType = actualED25519Derivator.serializeType(); + + // Assert + String expectedType = 'ed25519'; + + expect(actualType, expectedType); + }); + }); + + group('Tests of ED25519Derivator.derivePath()', () { + test("Should [return ED25519PrivateKey] constructed from mnemonic and derivation path (m/44'/)", () async { + // Act + LegacyDerivationPath actualLegacyDerivationPath = LegacyDerivationPath.parse("m/44'/"); + ED25519PrivateKey actualED25519PrivateKey = await actualED25519Derivator.derivePath(actualMnemonic, actualLegacyDerivationPath); + + // Assert + ED25519PrivateKey expectedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 1, + shiftedIndex: 2147483692, + chainCode: base64Decode('oVTP3c7E2KeoquJttMLqsSV7zyzEbvACvVcFTjW2Cz4='), + fingerprint: BigInt.parse('2330465125'), + parentFingerprint: BigInt.parse('3578578273'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('MinQRVP+LSjLX9pmmkDLcm01pJP8IVaKrlVAGlNXUbs=')), + ); + + expect(actualED25519PrivateKey, expectedED25519PrivateKey); + }); + + test("Should [return ED25519PrivateKey] constructed from mnemonic and derivation path (m/44'/60'/)", () async { + // Act + LegacyDerivationPath actualLegacyDerivationPath = LegacyDerivationPath.parse("m/44'/60'/"); + ED25519PrivateKey actualED25519PrivateKey = await actualED25519Derivator.derivePath(actualMnemonic, actualLegacyDerivationPath); + + // Assert + ED25519PrivateKey expectedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 2, + shiftedIndex: 2147483708, + chainCode: base64Decode('AlYCSjYOCo//7XisF+s9f+4uREPjJlQ3lZVRypceTI0='), + fingerprint: BigInt.parse('4278372777'), + parentFingerprint: BigInt.parse('2330465125'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('dZiZCf9yd0YSUxbInwyamtkndKTTRj6j+G6xmj928vY=')), + ); + + expect(actualED25519PrivateKey, expectedED25519PrivateKey); + }); + + test("Should [return ED25519PrivateKey] constructed from mnemonic and derivation path (m/44'/60'/0'/)", () async { + // Act + LegacyDerivationPath actualLegacyDerivationPath = LegacyDerivationPath.parse("m/44'/60'/0'/"); + ED25519PrivateKey actualED25519PrivateKey = await actualED25519Derivator.derivePath(actualMnemonic, actualLegacyDerivationPath); + + // Assert + ED25519PrivateKey expectedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 3, + shiftedIndex: 2147483648, + chainCode: base64Decode('XJIq3dw+4wLO363ghHmYr8iBf0sSpDC1SsJGbG6BMxM='), + fingerprint: BigInt.parse('4237045580'), + parentFingerprint: BigInt.parse('4278372777'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('dnfNSsX9wTWBvMXva/SUqWt3iRaK+oH68fM9Feg/SIs=')), + ); + + expect(actualED25519PrivateKey, expectedED25519PrivateKey); + }); + + test("Should [return ED25519PrivateKey] constructed from mnemonic and derivation path (m/44'/60'/0'/0'/)", () async { + // Act + LegacyDerivationPath actualLegacyDerivationPath = LegacyDerivationPath.parse("m/44'/60'/0'/0'/"); + ED25519PrivateKey actualED25519PrivateKey = await actualED25519Derivator.derivePath(actualMnemonic, actualLegacyDerivationPath); + + // Assert + ED25519PrivateKey expectedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 4, + shiftedIndex: 2147483648, + chainCode: base64Decode('7VKKNJkj9ZEp7SX+GveAvvcZhi+8NJNrSG98+BNgjxY='), + fingerprint: BigInt.parse('2130523803'), + parentFingerprint: BigInt.parse('4237045580'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('Dy1S7AO7gPWC+vO7mV/cwTi9sjJ56abeYtD8s3qRTAk=')), + ); + + expect(actualED25519PrivateKey, expectedED25519PrivateKey); + }); + + test("Should [return ED25519PrivateKey] constructed from mnemonic and derivation path (m/44'/60'/0'/0'/0')", () async { + // Act + LegacyDerivationPath actualLegacyDerivationPath = LegacyDerivationPath.parse("m/44'/60'/0'/0'/0'"); + ED25519PrivateKey actualED25519PrivateKey = await actualED25519Derivator.derivePath(actualMnemonic, actualLegacyDerivationPath); + + // Assert + ED25519PrivateKey expectedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 5, + shiftedIndex: 2147483648, + chainCode: base64Decode('hz6ve3vISMyVDK7ZD0zQoV2v4K1ota1QJ9kY1xakFR4='), + fingerprint: BigInt.parse('3808761756'), + parentFingerprint: BigInt.parse('2130523803'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('fbrqtRb364ZjYonX89pDhtlKE+4jFrxmpHGU2kmQaGs=')), + ); + + expect(actualED25519PrivateKey, expectedED25519PrivateKey); + }); + + test("Should [return ED25519PrivateKey] constructed from mnemonic and derivation path (m/44'/60'/0'/0'/1')", () async { + // Act + LegacyDerivationPath actualLegacyDerivationPath = LegacyDerivationPath.parse("m/44'/60'/0'/0'/1'"); + ED25519PrivateKey actualED25519PrivateKey = await actualED25519Derivator.derivePath(actualMnemonic, actualLegacyDerivationPath); + + // Assert + ED25519PrivateKey expectedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 5, + shiftedIndex: 2147483649, + chainCode: base64Decode('l1JlMYKoERbLwsNQIZbHugLvqyxktb1J+O+zU5jue+k='), + fingerprint: BigInt.parse('2573384385'), + parentFingerprint: BigInt.parse('2130523803'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('AVPH3U7LPhJ6E3PFI9S+Ek+vUM59I5RBk5uRG1nMIBw=')), + ); + + expect(actualED25519PrivateKey, expectedED25519PrivateKey); + }); + }); + + group('Tests of ED25519Derivator.deriveMasterKey()', () { + test('Should [return ED25519PrivateKey] constructed from mnemonic', () async { + // Act + ED25519PrivateKey actualED25519PrivateKey = await actualED25519Derivator.deriveMasterKey(actualMnemonic); + + // Assert + ED25519PrivateKey expectedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 0, + chainCode: base64Decode('3S5mnCmDWh5+QmP15XLOUYDGkOuxbn1nWXmhnoF0VXc='), + fingerprint: BigInt.parse('3578578273'), + parentFingerprint: BigInt.parse('0'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('to6LIOQMmlfq/dgQjfIr4CIb77WkealNrGon6wMsYrs=')), + ); + + expect(actualED25519PrivateKey, expectedED25519PrivateKey); + }); + }); + + group('Tests of SubstrateED25519Derivator.deriveChildKey()', () { + test("Should [return ED25519PrivateKey] derived from ED25519PrivateKey (m -> m/44'/)", () async { + // Arrange + LegacyDerivationPathElement actualDerivationPathElement = LegacyDerivationPathElement.parse("44'"); + ED25519PrivateKey actualED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 0, + chainCode: base64Decode('3S5mnCmDWh5+QmP15XLOUYDGkOuxbn1nWXmhnoF0VXc='), + fingerprint: BigInt.parse('3578578273'), + parentFingerprint: BigInt.parse('0'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('to6LIOQMmlfq/dgQjfIr4CIb77WkealNrGon6wMsYrs=')), + ); + + // Act + ED25519PrivateKey actualDerivedED25519PrivateKey = actualED25519Derivator.deriveChildKey(actualED25519PrivateKey, actualDerivationPathElement); + + // Assert + ED25519PrivateKey expectedDerivedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 1, + shiftedIndex: 2147483692, + chainCode: base64Decode('oVTP3c7E2KeoquJttMLqsSV7zyzEbvACvVcFTjW2Cz4='), + fingerprint: BigInt.parse('2330465125'), + parentFingerprint: BigInt.parse('3578578273'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('MinQRVP+LSjLX9pmmkDLcm01pJP8IVaKrlVAGlNXUbs=')), + ); + + expect(actualDerivedED25519PrivateKey, expectedDerivedED25519PrivateKey); + }); + + test("Should [return ED25519PrivateKey] derived from ED25519PrivateKey (m/44'/ -> m/44'/60'/)", () async { + // Arrange + LegacyDerivationPathElement actualDerivationPathElement = LegacyDerivationPathElement.parse("60'"); + ED25519PrivateKey actualED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 1, + shiftedIndex: 2147483692, + chainCode: base64Decode('oVTP3c7E2KeoquJttMLqsSV7zyzEbvACvVcFTjW2Cz4='), + fingerprint: BigInt.parse('2330465125'), + parentFingerprint: BigInt.parse('3578578273'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('MinQRVP+LSjLX9pmmkDLcm01pJP8IVaKrlVAGlNXUbs=')), + ); + + // Act + ED25519PrivateKey actualDerivedED25519PrivateKey = actualED25519Derivator.deriveChildKey(actualED25519PrivateKey, actualDerivationPathElement); + + // Assert + ED25519PrivateKey expectedDerivedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 2, + shiftedIndex: 2147483708, + chainCode: base64Decode('AlYCSjYOCo//7XisF+s9f+4uREPjJlQ3lZVRypceTI0='), + fingerprint: BigInt.parse('4278372777'), + parentFingerprint: BigInt.parse('2330465125'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('dZiZCf9yd0YSUxbInwyamtkndKTTRj6j+G6xmj928vY=')), + ); + + expect(actualDerivedED25519PrivateKey, expectedDerivedED25519PrivateKey); + }); + + test("Should [return ED25519PrivateKey] derived from ED25519PrivateKey (m/44'/60'/ -> m/44'/60'/0'/)", () async { + // Arrange + LegacyDerivationPathElement actualDerivationPathElement = LegacyDerivationPathElement.parse("0'"); + ED25519PrivateKey actualED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 2, + shiftedIndex: 2147483708, + chainCode: base64Decode('AlYCSjYOCo//7XisF+s9f+4uREPjJlQ3lZVRypceTI0='), + fingerprint: BigInt.parse('4278372777'), + parentFingerprint: BigInt.parse('2330465125'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('dZiZCf9yd0YSUxbInwyamtkndKTTRj6j+G6xmj928vY=')), + ); + + // Act + ED25519PrivateKey actualDerivedED25519PrivateKey = actualED25519Derivator.deriveChildKey(actualED25519PrivateKey, actualDerivationPathElement); + + // Assert + ED25519PrivateKey expectedDerivedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 3, + shiftedIndex: 2147483648, + chainCode: base64Decode('XJIq3dw+4wLO363ghHmYr8iBf0sSpDC1SsJGbG6BMxM='), + fingerprint: BigInt.parse('4237045580'), + parentFingerprint: BigInt.parse('4278372777'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('dnfNSsX9wTWBvMXva/SUqWt3iRaK+oH68fM9Feg/SIs=')), + ); + + expect(actualDerivedED25519PrivateKey, expectedDerivedED25519PrivateKey); + }); + + test("Should [return ED25519PrivateKey] derived from ED25519PrivateKey (m/44'/60'/0'/ -> m/44'/60'/0'/0'/)", () async { + // Arrange + LegacyDerivationPathElement actualDerivationPathElement = LegacyDerivationPathElement.parse("0'"); + ED25519PrivateKey actualED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 3, + shiftedIndex: 2147483648, + chainCode: base64Decode('XJIq3dw+4wLO363ghHmYr8iBf0sSpDC1SsJGbG6BMxM='), + fingerprint: BigInt.parse('4237045580'), + parentFingerprint: BigInt.parse('4278372777'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('dnfNSsX9wTWBvMXva/SUqWt3iRaK+oH68fM9Feg/SIs=')), + ); + + // Act + ED25519PrivateKey actualDerivedED25519PrivateKey = actualED25519Derivator.deriveChildKey(actualED25519PrivateKey, actualDerivationPathElement); + + // Assert + ED25519PrivateKey expectedDerivedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 4, + shiftedIndex: 2147483648, + chainCode: base64Decode('7VKKNJkj9ZEp7SX+GveAvvcZhi+8NJNrSG98+BNgjxY='), + fingerprint: BigInt.parse('2130523803'), + parentFingerprint: BigInt.parse('4237045580'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('Dy1S7AO7gPWC+vO7mV/cwTi9sjJ56abeYtD8s3qRTAk=')), + ); + + expect(actualDerivedED25519PrivateKey, expectedDerivedED25519PrivateKey); + }); + + test("Should [return ED25519PrivateKey] derived from ED25519PrivateKey (m/44'/60'/0'/0'/ -> m/44'/60'/0'/0'/0'/)", () async { + // Arrange + LegacyDerivationPathElement actualDerivationPathElement = LegacyDerivationPathElement.parse("0'"); + ED25519PrivateKey actualED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 4, + shiftedIndex: 2147483648, + chainCode: base64Decode('7VKKNJkj9ZEp7SX+GveAvvcZhi+8NJNrSG98+BNgjxY='), + fingerprint: BigInt.parse('2130523803'), + parentFingerprint: BigInt.parse('4237045580'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('Dy1S7AO7gPWC+vO7mV/cwTi9sjJ56abeYtD8s3qRTAk=')), + ); + + // Act + ED25519PrivateKey actualDerivedED25519PrivateKey = actualED25519Derivator.deriveChildKey(actualED25519PrivateKey, actualDerivationPathElement); + + // Assert + ED25519PrivateKey expectedDerivedED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 5, + shiftedIndex: 2147483648, + chainCode: base64Decode('hz6ve3vISMyVDK7ZD0zQoV2v4K1ota1QJ9kY1xakFR4='), + fingerprint: BigInt.parse('3808761756'), + parentFingerprint: BigInt.parse('2130523803'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('fbrqtRb364ZjYonX89pDhtlKE+4jFrxmpHGU2kmQaGs=')), + ); + + expect(actualDerivedED25519PrivateKey, expectedDerivedED25519PrivateKey); + }); + + test('Should [throw Exception] if Bip32KeyMetadata does not contain chain code', () { + // Arrange + LegacyDerivationPathElement actualDerivationPathElement = LegacyDerivationPathElement.parse("0'"); + ED25519PrivateKey actualED25519PrivateKey = ED25519PrivateKey( + metadata: Bip32KeyMetadata( + depth: 4, + shiftedIndex: 2147483648, + fingerprint: BigInt.parse('2130523803'), + parentFingerprint: BigInt.parse('4237045580'), + masterFingerprint: BigInt.parse('3578578273'), + ), + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('Dy1S7AO7gPWC+vO7mV/cwTi9sjJ56abeYtD8s3qRTAk=')), + ); + + // Act + Assert + expect(() => actualED25519Derivator.deriveChildKey(actualED25519PrivateKey, actualDerivationPathElement), throwsException); + }); + }); +} diff --git a/test/bip/bip32/hd_wallet/legacy_hd_wallet_test.dart b/test/bip/bip32/hd_wallet/legacy_hd_wallet_test.dart index 1f235ac8..74214476 100644 --- a/test/bip/bip32/hd_wallet/legacy_hd_wallet_test.dart +++ b/test/bip/bip32/hd_wallet/legacy_hd_wallet_test.dart @@ -289,5 +289,49 @@ void main() { expect(actualKiraWallet, expectedKiraWallet); }); + + test('Should [return LegacyHDWallet] from given mnemonic (BIP44-Solana)', () async { + // Act + LegacyHDWallet actualSolanaWallet = await LegacyHDWallet.fromMnemonic( + mnemonic: mnemonic, + walletConfig: Bip44WalletsConfig.solana, + derivationPath: LegacyDerivationPath.parse("m/44'/501'/0'"), + ); + + // Assert + Bip32KeyMetadata expectedBip32Metadata = Bip32KeyMetadata( + depth: 3, + shiftedIndex: 2147483648, + fingerprint: BigInt.parse('2293503694'), + parentFingerprint: BigInt.parse('3373575796'), + masterFingerprint: BigInt.parse('2882279341'), + chainCode: base64Decode('+ZGw7L105N54Uwwvz4mMpJ6iNJj7xumWTKKCh8QAgIY='), + ); + + LegacyHDWallet expectedSolanaWallet = LegacyHDWallet( + address: '9hhbpdfqH2xaNhr4Lrd6JsAsmhsMJoou6aTv9K7MC7G6', + walletConfig: Bip44WalletsConfig.solana, + privateKey: ED25519PrivateKey( + metadata: expectedBip32Metadata, + edPrivateKey: EDPrivateKey.fromBytes(base64Decode('R9I6Ttyga6IgJLNgZPIR0Sq5oCyrujJiWI3ngJn8NuU=')), + ), + publicKey: ED25519PublicKey( + metadata: expectedBip32Metadata, + edPublicKey: EDPublicKey( + EDPoint( + curve: Curves.ed25519, + n: BigInt.parse('7237005577332262213973186563042994240857116359379907606001950938285454250989'), + x: BigInt.parse('44770412612249132879508693710199042699271465674024261288602292914024751467771'), + y: BigInt.parse('9071192539003680880021133333155815940615935403600658841594844653366853384648'), + z: BigInt.parse('215655415489707601689906281986627164961686488479408878249048443374796070278'), + t: BigInt.parse('55926202586167076010672118601058589559146741453778469146658296279768153697900'), + ), + ), + ), + derivationPath: LegacyDerivationPath.parse("m/44'/501'/0'"), + ); + + expect(actualSolanaWallet, expectedSolanaWallet); + }); }); }