Skip to content

Commit

Permalink
Add MultisigProgram.sorted to sort public keys in multisig
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewLM committed Jan 9, 2024
1 parent e13524f commit 3b7af74
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 29 deletions.
12 changes: 11 additions & 1 deletion coinlib/lib/src/common/bytes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ Uint8List copyCheckBytes(
Uint8List bytes, int length, { String name = "Bytes", }
) => Uint8List.fromList(checkBytes(bytes, length, name: name));

/// Determines if two objects are equal lists
/// Determines if two objects are equal Uint8List data
bool bytesEqual(Object? a, Object? b)
=> (a is Uint8List) && (b is Uint8List) && ListEquality().equals(a, b);

/// Compares two Uint8List bytes from the left-most to right-most byte. If all
/// bytes are the same apart from one list being longer, the shortest list comes
/// first.
int compareBytes(Uint8List a, Uint8List b) {
for (int i = 0; i < a.length && i < b.length; i++) {
if (a[i] != b[i]) return a[i] - b[i];
}
return a.length - b.length;
}
63 changes: 39 additions & 24 deletions coinlib/lib/src/scripts/programs/multisig.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'dart:typed_data';
import 'package:coinlib/src/common/bytes.dart';
import 'package:coinlib/src/crypto/ec_public_key.dart';
import 'package:coinlib/src/scripts/operations.dart';
import 'package:coinlib/src/scripts/program.dart';
import 'package:coinlib/src/scripts/script.dart';
import 'package:collection/collection.dart';

class MultisigProgram implements Program {

Expand All @@ -14,6 +16,43 @@ class MultisigProgram implements Program {
late final int threshold;
late final List<ECPublicKey> pubkeys;

/// Creates a multisig script program for a given [threshold] (t-of-n) and a
/// list of public keys. The public keys are inserted into the script in the
/// same order that they are given.
MultisigProgram(this.threshold, Iterable<ECPublicKey> pubkeys)
: pubkeys = List.unmodifiable(pubkeys),
script = Script([
ScriptOp.fromNumber(threshold),
...pubkeys.map((pk) => ScriptPushData(pk.data)),
ScriptOp.fromNumber(pubkeys.length),
checkmultisig,
]) {

if (pubkeys.isEmpty || pubkeys.length > maxPubkeys) {
throw ArgumentError.value(
pubkeys, "pubkeys", "must have length between 1 and $maxPubkeys",
);
}

if (threshold < 1 || threshold > pubkeys.length) {
throw ArgumentError.value(
threshold, "threshold",
"must have length between 1 and the number of public keys",
);
}

}

/// Creates a multisig script program for a given [threshold] (t-of-n) and a
/// list of public keys that are sorted according to the big-endian encoded
/// bytes. Public keys will be inserted into the script from smallest to
/// largest encoded data.
MultisigProgram.sorted(int threshold, Iterable<ECPublicKey> pubkeys)
: this(
threshold,
pubkeys.sorted((a, b) => compareBytes(a.data, b.data)),
);

MultisigProgram.fromScript(this.script) {

// Must have threshold, 1-20 public keys, pubkey number and CHECKMULTISIG
Expand Down Expand Up @@ -62,28 +101,4 @@ class MultisigProgram implements Program {

MultisigProgram.fromAsm(String asm) : this.fromScript(Script.fromAsm(asm));

MultisigProgram(this.threshold, Iterable<ECPublicKey> pubkeys)
: pubkeys = List.unmodifiable(pubkeys),
script = Script([
ScriptOp.fromNumber(threshold),
...pubkeys.map((pk) => ScriptPushData(pk.data)),
ScriptOp.fromNumber(pubkeys.length),
checkmultisig,
]) {

if (pubkeys.isEmpty || pubkeys.length > maxPubkeys) {
throw ArgumentError.value(
pubkeys, "pubkeys", "must have length between 1 and $maxPubkeys",
);
}

if (threshold < 1 || threshold > pubkeys.length) {
throw ArgumentError.value(
threshold, "threshold",
"must have length between 1 and the number of public keys",
);
}

}

}
37 changes: 33 additions & 4 deletions coinlib/test/scripts/programs/multisig_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ final correctVectors = [
pubkeys: [pubkeyVec, longPubkeyVec],
threshold: 1,
),
// 2-of-2
// 2-of-2 with largest key first
MultisigVector(
asm: "02 $pubkeyVec $longPubkeyVec 02 OP_CHECKMULTISIG",
hex: "5221${pubkeyVec}41${longPubkeyVec}52ae",
pubkeys: [pubkeyVec, longPubkeyVec],
asm: "02 $longPubkeyVec $pubkeyVec 02 OP_CHECKMULTISIG",
hex: "5241${longPubkeyVec}21${pubkeyVec}52ae",
pubkeys: [longPubkeyVec, pubkeyVec],
threshold: 2,
),
// 20-of-20
Expand Down Expand Up @@ -106,6 +106,35 @@ void main() {

});

test("MultisigProgram.sorted()", () {

final pkA
= "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34";
final pkB
= "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
final pkC
= "0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798";
final pkD
= "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";

// Public keys given out of order
final multisig = MultisigProgram.sorted(
3, [pkC, pkB, pkD, pkA].map((hex) => ECPublicKey.fromHex(hex)),
);

// Public keys should be in the correct order
expectMultisig(
MultisigVector(
asm: "",
hex: "5321${pkA}21${pkB}21${pkC}41${pkD}54ae",
pubkeys: [pkA, pkB, pkC, pkD],
threshold: 3,
),
multisig,
);

});

test("invalid vectors", () {
for (final vec in invalidVectors) {
expect(
Expand Down

0 comments on commit 3b7af74

Please sign in to comment.