Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ abstract class ABlockchainAddressEncoder<T extends ABip32PublicKey> {
AddressEncoderType.bitcoinP2WPKH => BitcoinP2WPKHAddressEncoder(hrp: args[0]),
AddressEncoderType.cosmos => CosmosAddressEncoder(hrp: args[0]),
AddressEncoderType.ethereum => EthereumAddressEncoder(skipChecksumBool: args[0] == 'true'),
};
AddressEncoderType.solana => SolanaAddressEncoder(),
} as ABlockchainAddressEncoder<ABip32PublicKey>;
}

String serializeType() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ enum AddressEncoderType {
bitcoinP2WPKH,
cosmos,
ethereum,
solana,
}
17 changes: 17 additions & 0 deletions lib/src/bip/bip32/address_encoders/solana_address_encoder.dart
Original file line number Diff line number Diff line change
@@ -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<ED25519PublicKey> {
@override
AddressEncoderType get addressEncoderType => AddressEncoderType.solana;

@override
List<String> get args => <String>[];

@override
String encodePublicKey(ED25519PublicKey publicKey) {
return Base58Codec.encode(publicKey.bytes);
}
}
2 changes: 2 additions & 0 deletions lib/src/bip/bip32/bip32.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 1 addition & 0 deletions lib/src/bip/bip32/derivators/a_derivator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ abstract class ADerivator {
DerivatorType derivatorType = DerivatorType.values.byName(type);
return switch (derivatorType) {
DerivatorType.secp256k1 => Secp256k1Derivator(),
DerivatorType.ed25519 => ED25519Derivator(),
};
}

Expand Down
1 change: 1 addition & 0 deletions lib/src/bip/bip32/derivators/derivator_type.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
enum DerivatorType {
secp256k1,
ed25519,
}
114 changes: 114 additions & 0 deletions lib/src/bip/bip32/derivators/legacy_derivators/ed25519_derivator.dart
Original file line number Diff line number Diff line change
@@ -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<ED25519PrivateKey> {
static const List<int> _hardenedPrivateKeyPrefix = <int>[0x00];

@override
DerivatorType get derivatorType => DerivatorType.ed25519;

/// Derives a [ED25519PrivateKey] from a mnemonic phrase and full derivation path
@override
Future<ED25519PrivateKey> 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<ED25519PrivateKey> 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(<int>[..._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,
);
}
}
2 changes: 2 additions & 0 deletions lib/src/bip/bip32/keys/bip32_hmac_keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
1 change: 1 addition & 0 deletions lib/src/slip/slip44.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
6 changes: 6 additions & 0 deletions lib/src/wallet_config/bip44_wallets_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ class Bip44WalletsConfig {
derivator: Secp256k1Derivator(),
curveType: CurveType.secp256k1,
);

static LegacyWalletConfig<ED25519PrivateKey> solana = LegacyWalletConfig<ED25519PrivateKey>(
addressEncoder: SolanaAddressEncoder(),
derivator: ED25519Derivator(),
curveType: CurveType.ed25519,
);
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,16 @@ void main() {
expect(actualAddressEncoder, isA<EthereumAddressEncoder>());
expect((actualAddressEncoder as EthereumAddressEncoder).skipChecksumBool, false);
});

test('Should [return SolanaAddressEncoder] when type is "solana()"', () {
// Arrange
String actualSerializedType = 'solana()';

// Act
ABlockchainAddressEncoder<ABip32PublicKey> actualAddressEncoder = ABlockchainAddressEncoder.fromSerializedType(actualSerializedType);

// Assert
expect(actualAddressEncoder, isA<SolanaAddressEncoder>());
});
});
}
57 changes: 57 additions & 0 deletions test/bip/bip32/address_encoders/solana_address_encoder_test.dart
Original file line number Diff line number Diff line change
@@ -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);
});
});
}
11 changes: 11 additions & 0 deletions test/bip/bip32/derivators/a_derivator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,16 @@ void main() {
// Assert
expect(actualDerivator, isA<Secp256k1Derivator>());
});

test('Should [return ED25519Derivator] when type is "ed25519"', () {
// Arrange
String actualType = 'ed25519';

// Act
ADerivator actualDerivator = ADerivator.fromSerializedType(actualType);

// Assert
expect(actualDerivator, isA<ED25519Derivator>());
});
});
}
Loading