Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ecdsa/Secp256k1 implementation #404

Merged
merged 4 commits into from
Jan 30, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
- sr25519
- substrate_bip39
- substrate_metadata
- secp256k1_ecdsa

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.package }}
Expand Down
2 changes: 2 additions & 0 deletions examples/pubspec_overrides.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ dependency_overrides:
path: ../packages/polkadart_cli
polkadart_keyring:
path: ../packages/polkadart_keyring
secp256k1_ecdsa:
path: ../packages/secp256k1_ecdsa
polkadart_scale_codec:
path: ../packages/polkadart_scale_codec
ss58:
Expand Down
3 changes: 3 additions & 0 deletions packages/polkadart_keyring/lib/polkadart_keyring.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import 'package:collection/collection.dart';
import 'package:convert/convert.dart';
import 'package:substrate_bip39/substrate_bip39.dart';
import 'package:ed25519_edwards/ed25519_edwards.dart' as ed;
import 'package:secp256k1_ecdsa/secp256k1.dart' as secp256k1;
import 'package:ss58/ss58.dart';
import 'package:sr25519/sr25519.dart' as sr25519;
import 'package:merlin/merlin.dart' as merlin;
import 'package:pointycastle/digests/blake2b.dart' show Blake2bDigest;

part 'src/keyring.dart';
part 'src/keypair.dart';
part 'src/pairs.dart';
part 'src/extensions.dart';
part 'src/ecdsa.dart';
part 'src/ed25519.dart';
part 'src/sr25519.dart';
part 'src/constants.dart';
Expand Down
135 changes: 135 additions & 0 deletions packages/polkadart_keyring/lib/src/ecdsa.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
part of polkadart_keyring;

class EcdsaKeyPair extends KeyPair {
late secp256k1.PublicKey _publicKey;
late secp256k1.PrivateKey _privateKey;

@override
int ss58Format = 42;

EcdsaKeyPair() : super(KeyPairType.ecdsa);

@override
KeyPair fromSeed(Uint8List seed) {
_privateKey = secp256k1.PrivateKey.fromBytes(seed);
_publicKey = _privateKey.getPublicKey();
return this;
}

@override
Future<KeyPair> fromUri(String uri, [String? password]) async {
final seed =
await SubstrateBip39.ecdsa.seedFromUri(uri, password: password);
return fromSeed(Uint8List.fromList(seed));
}

@override
Future<KeyPair> fromMnemonic(String uri, [String? password]) {
return fromUri(uri, password);
}

/// Returns the seed of the `KeyPair` as a hex string.
String seedHex() => secp256k1.Utilities.bytesToHex(_privateKey.bytes());

@override
Uint8List sign(Uint8List message) {
if (_isLocked) {
throw Exception('KeyPair is locked. Unlock it before signing.');
}
message = _blake2bDigest(message);
final signature = _privateKey.sign(message);
return Uint8List.fromList(signature.toCompactRawBytes());
}

@override
bool verify(Uint8List message, Uint8List signature) {
message = _blake2bDigest(message);
final signatureObject = secp256k1.Signature.fromCompactBytes(signature);
return _publicKey.verify(signatureObject, message);
}

@override
String get address {
return Address(prefix: ss58Format, pubkey: _addressPrivate()).encode();
}

@override
String get rawAddress {
return hex.encode(_addressPrivate());
}

Uint8List _addressPrivate() {
final Uint8List bytesValue = bytes();
if (bytesValue.length > 32) {
return _blake2bDigest(bytesValue);
}
return bytesValue;
}

Uint8List _blake2bDigest(Uint8List data) {
final digest = Blake2bDigest(digestSize: 32);
digest.update(data, 0, data.length);
final output = Uint8List(32);
digest.doFinal(output, 0);
return output;
}

secp256k1.PrivateKey _privateKeyFromSeed(Uint8List seed) {
return secp256k1.PrivateKey.fromBytes(seed);
}

@override
Future<void> unlockFromMnemonic(String mnemonic, [String? password]) async {
final seed =
await SubstrateBip39.ecdsa.seedFromUri(mnemonic, password: password);
_unlock(_privateKeyFromSeed(Uint8List.fromList(seed)));
}

@override
Future<void> unlockFromUri(String uri, [String? password]) async {
await unlockFromMnemonic(uri, password);
}

@override
void unlockFromSeed(Uint8List seed) {
_unlock(_privateKeyFromSeed(seed));
}

void _unlock(secp256k1.PrivateKey privateKey) {
if (privateKey.getPublicKey().toBytes().toString() != bytes.toString()) {
throw Exception('Public_Key_Mismatch: Invalid seed for given KeyPair.');
}
privateKey = privateKey;
_isLocked = false;
}

@override
PublicKey get publicKey => PublicKey(_publicKey.toBytes());

/// Returns the public key of the `KeyPair` as a hex string.
String publicKeyHex([bool compressed = true]) =>
secp256k1.Utilities.bytesToHex(_publicKey.toBytes(compressed));

@override
void lock() {
_isLocked = true;
_privateKey =
secp256k1.PrivateKey.fromBytes(Uint8List.fromList(List.filled(32, 0)));
}

@override
Uint8List bytes([bool compressed = true]) =>
Uint8List.fromList(_publicKey.toBytes(compressed));

///
/// Returns `true` if the `KeyPair` matches with the other object.
@override
bool operator ==(Object other) {
return super == (other);
}

///
/// Returns the hash code of the `KeyPair`.
@override
int get hashCode => super.hashCode;
}
10 changes: 7 additions & 3 deletions packages/polkadart_keyring/lib/src/ed25519.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ class Ed25519KeyPair extends KeyPair {

@override
String get address {
return Address(prefix: ss58Format, pubkey: bytes).encode();
return Address(prefix: ss58Format, pubkey: bytes()).encode();
}

@override
String get rawAddress => address;

@override
Future<void> unlockFromMnemonic(String mnemonic, [String? password]) async {
final seed =
Expand All @@ -64,7 +67,7 @@ class Ed25519KeyPair extends KeyPair {
}

void _unlock(ed.PrivateKey privateKey) {
if (ed.public(privateKey).bytes.toString() != bytes.toString()) {
if (ed.public(privateKey).bytes.toString() != bytes().toString()) {
throw Exception('Public_Key_Mismatch: Invalid seed for given KeyPair.');
}
_privateKey = privateKey;
Expand All @@ -81,7 +84,8 @@ class Ed25519KeyPair extends KeyPair {
}

@override
Uint8List get bytes => Uint8List.fromList(_publicKey.bytes);
Uint8List bytes([bool compressed = true]) =>
Uint8List.fromList(_publicKey.bytes);

///
/// Returns `true` if the `KeyPair` matches with the other object.
Expand Down
3 changes: 3 additions & 0 deletions packages/polkadart_keyring/lib/src/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ enum KeyPairType {

/// Sr25519 key pair type.
sr25519,

/// ECDSA (Secp256k1) key pair type.
ecdsa,
}
21 changes: 16 additions & 5 deletions packages/polkadart_keyring/lib/src/keypair.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ part of polkadart_keyring;
abstract class KeyPair {
static Ed25519KeyPair get ed25519 => Ed25519KeyPair();
static Sr25519KeyPair get sr25519 => Sr25519KeyPair();
static EcdsaKeyPair get ecdsa => EcdsaKeyPair();
late int ss58Format;
final KeyPairType keyPairType;
// ignore: prefer_final_fields
Expand All @@ -18,7 +19,7 @@ abstract class KeyPair {
/// constructor
KeyPair(this.keyPairType);

Uint8List get bytes;
Uint8List bytes([bool compressed = true]);

///
/// Create a new `KeyPair` from a given seed.
Expand Down Expand Up @@ -173,6 +174,18 @@ abstract class KeyPair {
///
String get address;

///
/// Get the raw address of the `KeyPair`.
///
/// Example:
/// ```dart
/// final keyPair = KeyPair.sr25519.fromSeed(seed); // Replace with your actual seed
/// print('Raw Address: ${keyPair.rawAddress}');
/// ```
///
///
String get rawAddress;

/// Get the public key.
///
/// Example:
Expand Down Expand Up @@ -211,14 +224,12 @@ abstract class KeyPair {
///
bool get isLocked => _isLocked;

String get hexr => hex.encode(bytes);

///
/// Returns `true` if the `KeyPair` matches with the other object.
@override
bool operator ==(Object other) {
return const ListEquality().equals(bytes.toList(growable: false),
(other as KeyPair).bytes.toList(growable: false));
return const ListEquality().equals(bytes().toList(growable: false),
(other as KeyPair).bytes().toList(growable: false));
}

///
Expand Down
16 changes: 13 additions & 3 deletions packages/polkadart_keyring/lib/src/keyring.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class Keyring {
/// Create a new `Keyring` instance for sr25519.
static get sr25519 => Keyring._(KeyPairType.sr25519);

///
/// Create a new `Keyring` instance for sr25519.
static get ecdsa => Keyring._(KeyPairType.ecdsa);

/// Create a new [KeyPair] from a BIP39 mnemonic and optionally add it to the keyring.
///
/// This method generates a key pair from the provided [uri].
Expand All @@ -49,6 +53,9 @@ class Keyring {
case KeyPairType.ed25519:
pair = await KeyPair.ed25519.fromUri(uri, password);
break;
case KeyPairType.ecdsa:
pair = await KeyPair.ecdsa.fromUri(uri, password);
break;
default:
pair = await KeyPair.sr25519.fromUri(uri, password);
}
Expand Down Expand Up @@ -93,14 +100,17 @@ class Keyring {
/// final seed = your Uint8List seed;
/// final keyPair = await keyring.fromSeed(seed);
/// ```
Future<KeyPair> fromSeed(Uint8List seed,
{bool addToPairs = false, KeyPairType? keyPairType}) async {
KeyPair fromSeed(Uint8List seed,
{bool addToPairs = false, KeyPairType? keyPairType}) {
late KeyPair pair;

switch (keyPairType ?? this.keyPairType) {
case KeyPairType.ed25519:
pair = KeyPair.ed25519.fromSeed(seed);
break;
case KeyPairType.ecdsa:
pair = KeyPair.ecdsa.fromSeed(seed);
break;
default:
pair = KeyPair.sr25519.fromSeed(seed);
}
Expand Down Expand Up @@ -222,7 +232,7 @@ class Keyring {
/// ```
List<List<int>> get publicKeys {
return pairs.all
.map((pair) => pair.bytes.toList(growable: false))
.map((pair) => pair.bytes().toList(growable: false))
.toList(growable: false);
}

Expand Down
7 changes: 2 additions & 5 deletions packages/polkadart_keyring/lib/src/pairs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ class Pairs {
///
/// - [pair]: The key pair to add to the collection.
void add(KeyPair pair) {
_pairs[Address.decode(pair.address)
.pubkey
.toList(growable: false)
.toString()] = pair;
_pairs[pair.bytes().toList(growable: false).toString()] = pair;
}

/// Get a [KeyPair] from the collection by its address.
Expand Down Expand Up @@ -77,7 +74,7 @@ class Pairs {
/// Get a list of all public keys from the collection.
List<List<int>> get publicKeys {
return all
.map((pair) => pair.bytes.toList(growable: false))
.map((pair) => pair.bytes().toList(growable: false))
.toList(growable: false);
}

Expand Down
10 changes: 7 additions & 3 deletions packages/polkadart_keyring/lib/src/sr25519.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,12 @@ class Sr25519KeyPair extends KeyPair {

@override
String get address {
return Address(prefix: ss58Format, pubkey: bytes).encode();
return Address(prefix: ss58Format, pubkey: bytes()).encode();
}

@override
String get rawAddress => address;

@override
Future<void> unlockFromMnemonic(String mnemonic, [String? password]) async {
final seed = await SubstrateBip39.ed25519.seedFromUri(mnemonic);
Expand All @@ -139,7 +142,7 @@ class Sr25519KeyPair extends KeyPair {
}

void _unlock(sr25519.SecretKey privateKey) {
if (privateKey.public().encode().toString() != bytes.toString()) {
if (privateKey.public().encode().toString() != bytes().toString()) {
throw Exception('Public_Key_Mismatch: Invalid seed for given KeyPair.');
}
_privateKey = privateKey;
Expand All @@ -153,7 +156,8 @@ class Sr25519KeyPair extends KeyPair {
}

@override
Uint8List get bytes => Uint8List.fromList(_publicKey.encode());
Uint8List bytes([bool compressed = true]) =>
Uint8List.fromList(_publicKey.encode());

///
/// Returns `true` if the `KeyPair` matches with the other object.
Expand Down
6 changes: 4 additions & 2 deletions packages/polkadart_keyring/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ version: 0.3.0
homepage: https://github.com/leonardocustodio/polkadart/tree/main/packages/polkadart_keyring
repository: https://github.com/leonardocustodio/polkadart


environment:
sdk: ">=3.0.1 <4.0.0"

# Add regular dependencies here.
dependencies:
cryptography: ^2.5.0
pointycastle: ^3.6.2
flutter_curve25519: ^0.1.2
ss58: ^1.1.2
substrate_bip39: ^0.2.0
substrate_bip39: ^0.3.0
secp256k1_ecdsa: ^1.0.0
typed_data: ^1.3.1 # BSD-3-Clause
ed25519_edwards: ^0.3.1
convert: ^3.1.1
Expand Down
Loading
Loading