Skip to content

Commit a6cff11

Browse files
committed
Make MuSig keys tweakable
1 parent 0714f7e commit a6cff11

File tree

13 files changed

+154
-53
lines changed

13 files changed

+154
-53
lines changed

coinlib/bin/build_wasm.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ RUN ${WASI_SDK_PATH}/bin/wasm-ld \
8080
--export secp256k1_ecdh \
8181
--export secp256k1_ec_pubkey_sort \
8282
--export secp256k1_musig_pubkey_agg \
83+
--export secp256k1_musig_pubkey_xonly_tweak_add \
8384
# The secp256k1 library file
8485
build/lib/libsecp256k1.a \
8586
# Need to include libc for wasi here as it isn't done for us

coinlib/lib/src/coinlib_base.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export 'package:coinlib/src/encode/base58.dart';
2020
export 'package:coinlib/src/encode/bech32.dart';
2121
export 'package:coinlib/src/encode/wif.dart';
2222

23+
export 'package:coinlib/src/musig/keys.dart';
24+
2325
export 'package:coinlib/src/scripts/codes.dart';
2426
export 'package:coinlib/src/scripts/operations.dart';
2527
export 'package:coinlib/src/scripts/program.dart';
@@ -64,7 +66,6 @@ export 'package:coinlib/src/tx/sighash/witness_signature_hasher.dart';
6466

6567
export 'package:coinlib/src/address.dart';
6668
export 'package:coinlib/src/coin_unit.dart';
67-
export 'package:coinlib/src/musig.dart';
6869
export 'package:coinlib/src/network.dart';
6970

7071
Future<void> loadCoinlib() => secp256k1.load();

coinlib/lib/src/common/bytes.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Uint8List checkBytes(Uint8List bytes, int length, { String name = "Bytes" }) {
1010
return bytes;
1111
}
1212

13+
Uint8List checkScalar(Uint8List scalar)
14+
=> checkBytes(scalar, 32, name: "Scalar");
15+
1316
/// Throws an [ArgumentError] if the [bytes] are not of the required [length]
1417
/// and returns a copy of the [bytes].
1518
Uint8List copyCheckBytes(

coinlib/lib/src/crypto/ec_private_key.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class ECPrivateKey {
4141
/// created (practically impossible for random 32-bit scalars), then null will
4242
/// be returned.
4343
ECPrivateKey? tweak(Uint8List scalar) {
44-
checkBytes(scalar, 32, name: "Scalar");
44+
checkScalar(scalar);
4545
final newScalar = secp256k1.privKeyTweak(_data, scalar);
4646
return newScalar == null ? null : ECPrivateKey(newScalar, compressed: compressed);
4747
}

coinlib/lib/src/crypto/ec_public_key.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class ECPublicKey {
3737
/// the instance a new key cannot be created (practically impossible for
3838
/// random 32-bit scalars), then null will be returned.
3939
ECPublicKey? tweak(Uint8List scalar) {
40-
checkBytes(scalar, 32, name: "Scalar");
40+
checkScalar(scalar);
4141
final newKey = secp256k1.pubKeyTweak(_data, scalar, compressed);
4242
return newKey == null ? null : ECPublicKey(newKey);
4343
}
Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:typed_data';
2+
import 'package:coinlib/src/common/bytes.dart';
13
import 'package:coinlib/src/crypto/ec_private_key.dart';
24
import 'package:coinlib/src/crypto/ec_public_key.dart';
35
import 'package:coinlib/src/secp256k1/secp256k1.dart';
@@ -12,7 +14,9 @@ class MuSigPublicKeys {
1214

1315
late final MuSigCache _aggCache;
1416

15-
MuSigPublicKeys(this.pubKeys) {
17+
MuSigPublicKeys._(this.pubKeys, this.aggregate, this._aggCache);
18+
19+
factory MuSigPublicKeys(Set<ECPublicKey> pubKeys) {
1620

1721
if (pubKeys.isEmpty) {
1822
throw ArgumentError.value(pubKeys, "pubKeys", "should not be empty");
@@ -21,11 +25,20 @@ class MuSigPublicKeys {
2125
final (bytes, cache) = secp256k1.muSigAgggregate(
2226
pubKeys.map((pk) => pk.data).toList(),
2327
);
24-
aggregate = ECPublicKey.fromXOnly(bytes);
25-
_aggCache = cache;
28+
29+
return MuSigPublicKeys._(pubKeys, ECPublicKey.fromXOnly(bytes), cache);
2630

2731
}
2832

33+
/// Tweaks as an x-only aggregate public key
34+
MuSigPublicKeys tweak(Uint8List scalar) {
35+
checkScalar(scalar);
36+
final (keyBytes, cache) = secp256k1.muSigTweakXOnly(
37+
_aggCache, scalar,
38+
);
39+
return MuSigPublicKeys._(pubKeys, ECPublicKey(keyBytes).xonly, cache);
40+
}
41+
2942
}
3043

3144
/// The private MuSig2 information for a participant
@@ -34,7 +47,13 @@ class MuSigPrivate {
3447
final ECPrivateKey privateKey;
3548
final MuSigPublicKeys public;
3649

50+
MuSigPrivate._(this.privateKey, this.public);
51+
3752
MuSigPrivate(this.privateKey, Set<ECPublicKey> otherKeys)
3853
: public = MuSigPublicKeys({ privateKey.pubkey, ...otherKeys });
3954

55+
MuSigPrivate tweak(Uint8List scalar) => MuSigPrivate._(
56+
privateKey, public.tweak(scalar),
57+
);
58+
4059
}

coinlib/lib/src/secp256k1/heap_wasm.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,14 @@ class HeapFactory {
9797
}
9898

9999
/// Allocate data for a miscellaneous object with [size] bytes.
100-
HeapWasm alloc(int size) => HeapWasm._(_malloc(size), _free);
100+
/// If [copyFrom] is specified, data shall be copied from this pointer.
101+
HeapWasm alloc(int size, { int? copyFrom }) {
102+
final heap = HeapWasm._(_malloc(size), _free);
103+
if (copyFrom != null) {
104+
_memory.setRange(heap.ptr, heap.ptr + size, _memory, copyFrom);
105+
}
106+
return heap;
107+
}
101108

102109
/// Allocates an integer on the heap.
103110
HeapIntWasm integer() => HeapIntWasm._withMemory(

coinlib/lib/src/secp256k1/secp256k1.wasm.g.dart

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

coinlib/lib/src/secp256k1/secp256k1_base.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ abstract class Secp256k1Base<
104104
late int Function(
105105
CtxPtr, XPubKeyPtr, MuSigAggCachePtr, PubKeyPtrPtr, int,
106106
) extMuSigPubkeyAgg;
107+
late int Function(
108+
CtxPtr, PubKeyPtr, MuSigAggCachePtr, UCharPtr,
109+
) extMuSigPubkeyXOnlyTweakAdd;
107110

108111
// Heap arrays
109112

@@ -557,6 +560,32 @@ abstract class Secp256k1Base<
557560

558561
}
559562

563+
/// Tweaks the aggregate MuSig key returning the new aggregate key and cache.
564+
/// The new cache is a new object. The previous cache is not valid for the
565+
/// tweaked key
566+
(Uint8List, MuSigCacheGeneric<MuSigAggCachePtr>) muSigTweakXOnly(
567+
MuSigCacheGeneric<MuSigAggCachePtr> cache,
568+
Uint8List scalar,
569+
) {
570+
_requireLoad();
571+
572+
scalarArray.load(scalar);
573+
574+
// Copy cache to avoid side-effects
575+
final newCache = copyMuSigCache(cache._cache.ptr);
576+
577+
if (
578+
extMuSigPubkeyXOnlyTweakAdd(
579+
ctxPtr, pubKey.ptr, newCache.ptr, scalarArray.ptr,
580+
) != 1
581+
) {
582+
throw Secp256k1Exception("Couldn't apply tweak to MuSig key");
583+
}
584+
585+
return (_serializePubKeyFromPtr(true), MuSigCacheGeneric(newCache));
586+
587+
}
588+
560589
/// Specialised sub-classes should override to allocate a [size] number of
561590
/// secp256k1_pubkey and then alloate and set an array of pointers on the heap
562591
/// to them.
@@ -566,4 +595,9 @@ abstract class Secp256k1Base<
566595
/// secp256k1_musig_keyagg_cache on the heap.
567596
Heap<MuSigAggCachePtr> allocMuSigCache();
568597

598+
/// Specialised sub-classes should override to allocate an
599+
/// secp256k1_musig_keyagg_cache on the heap and then copy the [copyFrom]
600+
/// struct into it.
601+
Heap<MuSigAggCachePtr> copyMuSigCache(MuSigAggCachePtr copyFrom);
602+
569603
}

coinlib/lib/src/secp256k1/secp256k1_io.dart

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ String _libraryPath() {
3838

3939
DynamicLibrary _openLibrary() => DynamicLibrary.open(_libraryPath());
4040

41-
typedef MuSigCache = MuSigCacheGeneric<Pointer<secp256k1_musig_keyagg_cache>>;
42-
4341
typedef PubKeyPtr = Pointer<secp256k1_pubkey>;
42+
typedef MuSigAggCachePtr = Pointer<secp256k1_musig_keyagg_cache>;
43+
44+
typedef MuSigCache = MuSigCacheGeneric<MuSigAggCachePtr>;
4445

4546
/// Specialises Secp256k1Base to use the FFI
4647
class Secp256k1 extends Secp256k1Base<
@@ -53,7 +54,7 @@ class Secp256k1 extends Secp256k1Base<
5354
Pointer<secp256k1_keypair>,
5455
Pointer<secp256k1_xonly_pubkey>,
5556
Pointer<Int>,
56-
Pointer<secp256k1_musig_keyagg_cache>,
57+
MuSigAggCachePtr,
5758
Pointer<PubKeyPtr>,
5859
Pointer<Never>
5960
> {
@@ -94,6 +95,7 @@ class Secp256k1 extends Secp256k1Base<
9495
extEcdh = _lib.secp256k1_ecdh;
9596
extEcPubkeySort = _lib.secp256k1_ec_pubkey_sort;
9697
extMuSigPubkeyAgg = _lib.secp256k1_musig_pubkey_agg;
98+
extMuSigPubkeyXOnlyTweakAdd = _lib.secp256k1_musig_pubkey_xonly_tweak_add;
9799

98100
// Set heap arrays
99101
key32Array = HeapBytesFfi(Secp256k1Base.privkeySize);
@@ -135,7 +137,13 @@ class Secp256k1 extends Secp256k1Base<
135137
=> HeapPointerArrayFfi(malloc(size), size, () => malloc());
136138

137139
@override
138-
Heap<Pointer<secp256k1_musig_keyagg_cache>> allocMuSigCache()
139-
=> HeapFfi(malloc());
140+
Heap<MuSigAggCachePtr> allocMuSigCache() => HeapFfi(malloc());
141+
142+
@override
143+
Heap<MuSigAggCachePtr> copyMuSigCache(MuSigAggCachePtr copyFrom) {
144+
final newCache = HeapFfi<secp256k1_musig_keyagg_cache>(malloc());
145+
newCache.ptr.ref = copyFrom.ref;
146+
return newCache;
147+
}
140148

141149
}

0 commit comments

Comments
 (0)