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

MultiSig Implementation #417

Merged
merged 22 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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: 0 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ jobs:
- sr25519
- substrate_bip39
- substrate_metadata
- secp256k1_ecdsa

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.package }}
Expand Down
11 changes: 11 additions & 0 deletions packages/polkadart_keyring/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ dependencies:

## Usage

### Creating MultiSig Address

```dart
final result = MultiSig.createMultiSigAddress([
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
'5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
'5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y'
], 2);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to put here in the docs that 2 is the threshold and what it means, here you can find a reference text: https://wiki.polkadot.network/docs/learn-account-multisig

// result = 5DjYJStmdZ2rcqXbXGX7TW85JsrW6uG4y9MUcLq2BoPMpRA7
```

### Creating KeyPairs from Mnemonic

You can create a new [KeyPair] from a BIP39 mnemonic and optionally add it to the keyring. Here's an example:
Expand Down
1 change: 1 addition & 0 deletions packages/polkadart_keyring/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ linter:
prefer_final_fields: true
prefer_final_locals: true
prefer_single_quotes: true
non_constant_identifier_names: false
13 changes: 11 additions & 2 deletions packages/polkadart_keyring/lib/polkadart_keyring.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
library polkadart_keyring;

import 'dart:convert';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:convert/convert.dart';
Expand All @@ -10,13 +11,21 @@ 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;
import 'package:polkadart_scale_codec/polkadart_scale_codec.dart'
as scale_codec;

// src
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';
part 'src/public_key.dart';
part 'src/multisig.dart';

// utils
part 'utils/constants.dart';
part 'utils/extensions.dart';
part 'utils/hashers.dart';
part 'utils/utilities.dart';
45 changes: 45 additions & 0 deletions packages/polkadart_keyring/lib/src/multisig.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
part of polkadart_keyring;

final PREFIX = utf8.encode('modlpy/utilisuba');

class MultiSig {
static Uint8List createMultiSigBytes(
List<Uint8List> signatories, int threshold) {
if (signatories.isEmpty) {
throw ArgumentError('No signatories provided.');
}

// sort the signatories
final sortedSignatories = List<Uint8List>.from(signatories)
..sort(uint8ListCompare);

// generate the multi output result
final result = <int>[];

// append the PREFIX
result.addAll(PREFIX);

// append the length
result.addAll(
scale_codec.CompactCodec.codec.encode(sortedSignatories.length));
for (final who in sortedSignatories) {
result.addAll(who);
}
// append the threshold
result.addAll(bnToU8a(threshold, bitLength: 16));

return blake2bDigest(Uint8List.fromList(result));
}

static String createMultiSigAddress(List<String> signatories, int threshold,
{int ss58Format = 42}) {
return Address(
prefix: ss58Format,
pubkey: createMultiSigBytes(
signatories
.map((address) => Address.decode(address).pubkey)
.toList(),
threshold))
.encode();
}
}
9 changes: 9 additions & 0 deletions packages/polkadart_keyring/lib/utils/hashers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
part of polkadart_keyring;

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;
}
59 changes: 59 additions & 0 deletions packages/polkadart_keyring/lib/utils/utilities.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
part of polkadart_keyring;

// Little endian
Uint8List bnToU8a(int? value,
{int bitLength = -1, bool isLittleEndian = true, bool isNegative = false}) {
if (value == null) {
return bitLength == -1 ? Uint8List(1) : Uint8List((bitLength + 7) >> 3);
}

BigInt valueBn = BigInt.from(value);

final int byteLength =
bitLength == -1 ? (valueBn.bitLength + 7) >> 3 : ((bitLength + 7) >> 3);

final Uint8List output = Uint8List(byteLength);
if (isNegative) {
valueBn = valueBn.toUnsigned(byteLength * 8);
}

final List<int> bytes = _bigIntToBytes(valueBn, byteLength, isLittleEndian);
output.setAll(0, bytes);

return output;
}

List<int> _bigIntToBytes(BigInt number, int size, bool isLittleEndian) {
final List<int> result = List.filled(size, 0);
int i = 0;
while (number > BigInt.zero && i < size) {
result[isLittleEndian ? i : (size - i - 1)] =
(number & BigInt.from(0xff)).toInt();
number = number >> 8;
i++;
}
return result;
}

int uint8ListCompare(Uint8List a, Uint8List b) {
int i = 0;
while (true) {
final overA = i >= a.length;
final overB = i >= b.length;
if (overA && overB) {
// both ends reached
return 0;
} else if (overA) {
// a has no more data, b has data
return -1;
} else if (overB) {
// b has no more data, a has data
return 1;
} else if (a[i] != b[i]) {
// the number in this index doesn't match
// (we don't use u8aa[i] - u8ab[i] since that doesn't match with localeCompare)
return a[i] > b[i] ? 1 : -1;
}
i++;
}
}
1 change: 1 addition & 0 deletions packages/polkadart_keyring/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dependencies:
sr25519: ^0.1.0
merlin: ^1.0.3
collection: ^1.18.0
polkadart_scale_codec: ^1.1.2

dev_dependencies:
lints: ^2.0.0
Expand Down
60 changes: 60 additions & 0 deletions packages/polkadart_keyring/test/multisig_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'dart:typed_data';
import 'package:polkadart_keyring/polkadart_keyring.dart';
import 'package:test/test.dart';

void main() {
test('creates a valid multikey (aligning with Rust, needs sorting)', () {
final result = MultiSig.createMultiSigBytes([
Uint8List.fromList([1, 0, 0, 0, 0, 0, 0, 0]),
Uint8List.fromList([3, 0, 0, 0, 0, 0, 0, 0]),
Uint8List.fromList([2, 0, 0, 0, 0, 0, 0, 0])
], 2);

expect(
result.toList().toString(),
Uint8List.fromList([
67,
151,
196,
155,
179,
207,
47,
123,
90,
2,
35,
54,
162,
111,
241,
226,
88,
148,
54,
193,
252,
195,
93,
101,
16,
5,
93,
101,
186,
186,
254,
79
]).toList().toString());
});

test('creates a valid multikey for the address.', () {
final result = MultiSig.createMultiSigAddress([
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
'5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
'5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y'
], 2);

expect(result, '5DjYJStmdZ2rcqXbXGX7TW85JsrW6uG4y9MUcLq2BoPMpRA7');
});
}
Loading