Skip to content

Commit 463664c

Browse files
committed
Generate public/secret nonces for MuSig2
1 parent a6cff11 commit 463664c

File tree

10 files changed

+167
-27
lines changed

10 files changed

+167
-27
lines changed

coinlib/bin/build_wasm.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ RUN ${WASI_SDK_PATH}/bin/wasm-ld \
8181
--export secp256k1_ec_pubkey_sort \
8282
--export secp256k1_musig_pubkey_agg \
8383
--export secp256k1_musig_pubkey_xonly_tweak_add \
84+
--export secp256k1_musig_nonce_gen \
85+
--export secp256k1_musig_pubnonce_serialize \
8486
# The secp256k1 library file
8587
build/lib/libsecp256k1.a \
8688
# Need to include libc for wasi here as it isn't done for us

coinlib/lib/src/musig/signing.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import 'dart:typed_data';
2+
import 'package:coinlib/src/crypto/ec_public_key.dart';
3+
import 'package:coinlib/src/secp256k1/secp256k1.dart';
4+
import 'package:coinlib/src/musig/keys.dart';
5+
6+
/// A MuSig signing session state to be used for one signing session only.
7+
///
8+
/// This class is stateful unlike most of the classes in the library. This is to
9+
/// prevent re-use of earlier parts of the signing session, ensuring signing
10+
/// nonces are used no more than once.
11+
class MuSigStatefulSigningSession {
12+
13+
final MuSigPublicKeys keys;
14+
final ECPublicKey ourPublicKey;
15+
16+
late final MuSigSecretNonce _ourSecretNonce;
17+
late final Uint8List ourPublicNonce;
18+
19+
/// Starts a signing session with the MuSig [keys] and specifying the public
20+
/// key for the signer with [ourPublicKey].
21+
MuSigStatefulSigningSession({
22+
required this.keys,
23+
required this.ourPublicKey,
24+
}) {
25+
final (secret, public) = secp256k1.muSigGenerateNonce(ourPublicKey.data);
26+
_ourSecretNonce = secret;
27+
ourPublicNonce = public;
28+
}
29+
30+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'secp256k1_web.dart' if (dart.library.io) 'secp256k1_io.dart';
22
export 'secp256k1_web.dart' if (dart.library.io) 'secp256k1_io.dart'
3-
show MuSigCache;
3+
show MuSigCache, MuSigSecretNonce;
44

55
final secp256k1 = Secp256k1();

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: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:typed_data';
2+
import 'package:coinlib/src/crypto/random.dart';
23
import 'heap.dart';
34

45
class Secp256k1Exception implements Exception {
@@ -14,15 +15,15 @@ class SigWithRecId {
1415
SigWithRecId(this.signature, this.recid);
1516
}
1617

17-
class MuSigCacheGeneric<MuSigAggCachePtr> {
18-
final Heap<MuSigAggCachePtr> _cache;
19-
MuSigCacheGeneric(this._cache);
18+
class OpaqueGeneric<Ptr> {
19+
final Heap<Ptr> _heap;
20+
OpaqueGeneric(this._heap);
2021
}
2122

2223
abstract class Secp256k1Base<
2324
CtxPtr, UCharPtr, PubKeyPtr, SizeTPtr, SignaturePtr,
2425
RecoverableSignaturePtr, KeyPairPtr, XPubKeyPtr, IntPtr, MuSigAggCachePtr,
25-
PubKeyPtrPtr, NullPtr
26+
PubKeyPtrPtr, MuSigSecNoncePtr, MuSigPubNoncePtr, NullPtr
2627
> {
2728

2829
static const contextNone = 1;
@@ -36,6 +37,7 @@ abstract class Secp256k1Base<
3637
static const uncompressedPubkeySize = 65;
3738
static const sigSize = 64;
3839
static const derSigSize = 72;
40+
static const muSigPubNonceSize = 66;
3941
static const recSigSize = 65;
4042
static const keyPairSize = 96;
4143
static const xonlySize = 32;
@@ -107,6 +109,13 @@ abstract class Secp256k1Base<
107109
late int Function(
108110
CtxPtr, PubKeyPtr, MuSigAggCachePtr, UCharPtr,
109111
) extMuSigPubkeyXOnlyTweakAdd;
112+
late int Function(
113+
CtxPtr, MuSigSecNoncePtr, MuSigPubNoncePtr, UCharPtr, NullPtr, PubKeyPtr,
114+
NullPtr, NullPtr, NullPtr,
115+
) extMuSigNonceGen;
116+
late int Function(
117+
CtxPtr, UCharPtr, MuSigPubNoncePtr,
118+
) extMuSigPubNonceSerialize;
110119

111120
// Heap arrays
112121

@@ -119,6 +128,7 @@ abstract class Secp256k1Base<
119128
late HeapBytes<UCharPtr> entropyArray;
120129
late HeapBytes<UCharPtr> serializedSigArray;
121130
late HeapBytes<UCharPtr> derSigArray;
131+
late HeapBytes<UCharPtr> muSigPubNonceArray;
122132

123133
// Other pre-allocated heap objects
124134
late Heap<PubKeyPtr> pubKey;
@@ -128,6 +138,7 @@ abstract class Secp256k1Base<
128138
late Heap<KeyPairPtr> keyPair;
129139
late Heap<XPubKeyPtr> xPubKey;
130140
late HeapInt<IntPtr> recId;
141+
late Heap<MuSigPubNoncePtr> muSigPubNonce;
131142

132143
// Context pointer is allocated by underlying library
133144
late CtxPtr ctxPtr;
@@ -473,6 +484,7 @@ abstract class Secp256k1Base<
473484

474485
_parsePrivKeyIntoKeyPairPtr(privKey);
475486
hashArray.load(hash);
487+
if (extraEntropy != null) entropyArray.load(extraEntropy);
476488

477489
if (
478490
extSchnorrSign32(
@@ -528,7 +540,7 @@ abstract class Secp256k1Base<
528540
///
529541
/// Returns the aggregated public key bytes and an opaque object for the MuSig
530542
/// aggregation cache used for signing.
531-
(Uint8List, MuSigCacheGeneric<MuSigAggCachePtr>) muSigAgggregate(
543+
(Uint8List, OpaqueGeneric<MuSigAggCachePtr>) muSigAgggregate(
532544
List<Uint8List> pubKeysBytes,
533545
) {
534546
_requireLoad();
@@ -556,23 +568,23 @@ abstract class Secp256k1Base<
556568
throw Secp256k1Exception("Couldn't aggregate public keys for MuSig2");
557569
}
558570

559-
return (_serializeXPubKeyFromPtr(), MuSigCacheGeneric(musigCache));
571+
return (_serializeXPubKeyFromPtr(), OpaqueGeneric(musigCache));
560572

561573
}
562574

563575
/// Tweaks the aggregate MuSig key returning the new aggregate key and cache.
564576
/// The new cache is a new object. The previous cache is not valid for the
565577
/// tweaked key
566-
(Uint8List, MuSigCacheGeneric<MuSigAggCachePtr>) muSigTweakXOnly(
567-
MuSigCacheGeneric<MuSigAggCachePtr> cache,
578+
(Uint8List, OpaqueGeneric<MuSigAggCachePtr>) muSigTweakXOnly(
579+
OpaqueGeneric<MuSigAggCachePtr> cache,
568580
Uint8List scalar,
569581
) {
570582
_requireLoad();
571583

572584
scalarArray.load(scalar);
573585

574586
// Copy cache to avoid side-effects
575-
final newCache = copyMuSigCache(cache._cache.ptr);
587+
final newCache = copyMuSigCache(cache._heap.ptr);
576588

577589
if (
578590
extMuSigPubkeyXOnlyTweakAdd(
@@ -582,7 +594,45 @@ abstract class Secp256k1Base<
582594
throw Secp256k1Exception("Couldn't apply tweak to MuSig key");
583595
}
584596

585-
return (_serializePubKeyFromPtr(true), MuSigCacheGeneric(newCache));
597+
return (_serializePubKeyFromPtr(true), OpaqueGeneric(newCache));
598+
599+
}
600+
601+
/// Generates and returns an opaque secret and serialised public nonce for
602+
/// a MuSig signing session. This produces a unique nonce each time.
603+
///
604+
/// The nonce must only be used once for a single signing session and
605+
/// discarded after the signing session succeeds or fails for any reason.
606+
///
607+
/// The [pubKeyBytes] must be the compressed or uncompressed public key used
608+
/// by the signer.
609+
(OpaqueGeneric<MuSigSecNoncePtr>, Uint8List) muSigGenerateNonce(
610+
Uint8List pubKeyBytes,
611+
) {
612+
_requireLoad();
613+
614+
_parsePubkeyIntoPtr(pubKeyBytes);
615+
entropyArray.load(generateRandomBytes(32));
616+
617+
final secNonce = allocMuSigSecNonce();
618+
619+
if(
620+
extMuSigNonceGen(
621+
ctxPtr, secNonce.ptr, muSigPubNonce.ptr, entropyArray.ptr, nullPtr,
622+
pubKey.ptr, nullPtr, nullPtr, nullPtr,
623+
) != 1
624+
) {
625+
throw Secp256k1Exception("Couldn't generate MuSig nonce for key");
626+
}
627+
628+
extMuSigPubNonceSerialize(
629+
ctxPtr, muSigPubNonceArray.ptr, muSigPubNonce.ptr,
630+
);
631+
632+
return (
633+
OpaqueGeneric(secNonce),
634+
Uint8List.fromList(muSigPubNonceArray.list),
635+
);
586636

587637
}
588638

@@ -600,4 +650,8 @@ abstract class Secp256k1Base<
600650
/// struct into it.
601651
Heap<MuSigAggCachePtr> copyMuSigCache(MuSigAggCachePtr copyFrom);
602652

653+
/// Specialised sub-classes should override to allocate an
654+
/// secp256k1_musig_secnonce on the heap.
655+
Heap<MuSigSecNoncePtr> allocMuSigSecNonce();
656+
603657
}

coinlib/lib/src/secp256k1/secp256k1_io.dart

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ DynamicLibrary _openLibrary() => DynamicLibrary.open(_libraryPath());
4040

4141
typedef PubKeyPtr = Pointer<secp256k1_pubkey>;
4242
typedef MuSigAggCachePtr = Pointer<secp256k1_musig_keyagg_cache>;
43+
typedef MuSigSecNoncePtr = Pointer<secp256k1_musig_secnonce>;
4344

44-
typedef MuSigCache = MuSigCacheGeneric<MuSigAggCachePtr>;
45+
typedef MuSigCache = OpaqueGeneric<MuSigAggCachePtr>;
46+
typedef MuSigSecretNonce = OpaqueGeneric<MuSigSecNoncePtr>;
4547

4648
/// Specialises Secp256k1Base to use the FFI
4749
class Secp256k1 extends Secp256k1Base<
@@ -56,6 +58,8 @@ class Secp256k1 extends Secp256k1Base<
5658
Pointer<Int>,
5759
MuSigAggCachePtr,
5860
Pointer<PubKeyPtr>,
61+
MuSigSecNoncePtr,
62+
Pointer<secp256k1_musig_pubnonce>,
5963
Pointer<Never>
6064
> {
6165

@@ -96,6 +100,8 @@ class Secp256k1 extends Secp256k1Base<
96100
extEcPubkeySort = _lib.secp256k1_ec_pubkey_sort;
97101
extMuSigPubkeyAgg = _lib.secp256k1_musig_pubkey_agg;
98102
extMuSigPubkeyXOnlyTweakAdd = _lib.secp256k1_musig_pubkey_xonly_tweak_add;
103+
extMuSigNonceGen = _lib.secp256k1_musig_nonce_gen;
104+
extMuSigPubNonceSerialize = _lib.secp256k1_musig_pubnonce_serialize;
99105

100106
// Set heap arrays
101107
key32Array = HeapBytesFfi(Secp256k1Base.privkeySize);
@@ -105,6 +111,7 @@ class Secp256k1 extends Secp256k1Base<
105111
entropyArray = HeapBytesFfi(Secp256k1Base.entropySize);
106112
serializedSigArray = HeapBytesFfi(Secp256k1Base.sigSize);
107113
derSigArray = HeapBytesFfi(Secp256k1Base.derSigSize);
114+
muSigPubNonceArray = HeapBytesFfi(Secp256k1Base.muSigPubNonceSize);
108115

109116
// Set other heap data
110117
sizeT = HeapSizeFfi();
@@ -114,6 +121,7 @@ class Secp256k1 extends Secp256k1Base<
114121
keyPair = HeapFfi(malloc());
115122
xPubKey = HeapFfi(malloc());
116123
recId = HeapIntFfi();
124+
muSigPubNonce = HeapFfi(malloc());
117125

118126
nullPtr = nullptr;
119127

@@ -146,4 +154,7 @@ class Secp256k1 extends Secp256k1Base<
146154
return newCache;
147155
}
148156

157+
@override
158+
Heap<MuSigSecNoncePtr> allocMuSigSecNonce() => HeapFfi(malloc());
159+
149160
}

coinlib/lib/src/secp256k1/secp256k1_web.dart

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ import 'heap_wasm.dart';
55
import "secp256k1_base.dart";
66
import 'secp256k1.wasm.g.dart';
77

8-
typedef MuSigCache = MuSigCacheGeneric<int>;
8+
typedef MuSigCache = OpaqueGeneric<int>;
9+
typedef MuSigSecretNonce = OpaqueGeneric<int>;
910

1011
/// Loads and wraps WASM code to be run via the browser JS APIs
1112
class Secp256k1 extends Secp256k1Base<
12-
int, int, int, int, int, int, int, int, int, int, int, int
13+
int, int, int, int, int, int, int, int, int, int, int, int, int, int
1314
> {
1415

1516
static const _muSigCacheSize = 197;
17+
static const _muSigSecNonceSize = 132;
18+
static const _muSigPubNonceSize = 132;
1619

1720
late final HeapFactory _heapFactory;
1821

@@ -60,6 +63,9 @@ class Secp256k1 extends Secp256k1Base<
6063
extMuSigPubkeyAgg = wasm.field("secp256k1_musig_pubkey_agg");
6164
extMuSigPubkeyXOnlyTweakAdd
6265
= wasm.field("secp256k1_musig_pubkey_xonly_tweak_add");
66+
extMuSigNonceGen = wasm.field("secp256k1_musig_nonce_gen");
67+
extMuSigPubNonceSerialize
68+
= wasm.field("secp256k1_musig_pubnonce_serialize");
6369

6470
// Local functions for loading purposes
6571
final int Function(int) contextCreate
@@ -81,6 +87,7 @@ class Secp256k1 extends Secp256k1Base<
8187
);
8288
serializedSigArray = _heapFactory.bytes(Secp256k1Base.sigSize);
8389
derSigArray = _heapFactory.bytes(Secp256k1Base.derSigSize);
90+
muSigPubNonceArray = _heapFactory.bytes(Secp256k1Base.muSigPubNonceSize);
8491

8592
// Heap objects
8693
pubKey = _heapFactory.alloc(Secp256k1Base.pubkeySize);
@@ -90,6 +97,7 @@ class Secp256k1 extends Secp256k1Base<
9097
keyPair = _heapFactory.alloc(Secp256k1Base.keyPairSize);
9198
xPubKey = _heapFactory.alloc(Secp256k1Base.xonlySize);
9299
recId = _heapFactory.integer();
100+
muSigPubNonce = _heapFactory.alloc(_muSigPubNonceSize);
93101

94102
nullPtr = 0;
95103

@@ -118,4 +126,7 @@ class Secp256k1 extends Secp256k1Base<
118126
_muSigCacheSize, copyFrom: copyFrom,
119127
);
120128

129+
@override
130+
Heap<int> allocMuSigSecNonce() => _heapFactory.alloc(_muSigSecNonceSize);
131+
121132
}

coinlib/test/musig/keys_test.dart

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:typed_data';
22
import 'package:coinlib/coinlib.dart';
33
import 'package:test/test.dart';
4+
import '../vectors/keys.dart';
45

56
void main() {
67

@@ -13,18 +14,10 @@ void main() {
1314
() => expect(() => MuSigPublicKeys({}), throwsArgumentError),
1415
);
1516

16-
Set<ECPublicKey> getKeys(bool compressed) => Iterable.generate(
17-
3,
18-
(i) => ECPrivateKey(
19-
Uint8List(32)..last = i+1,
20-
compressed: compressed,
21-
).pubkey,
22-
).toSet();
23-
2417
test("aggregation works regardless of format", () {
2518

26-
final compressed = MuSigPublicKeys(getKeys(true)).aggregate;
27-
final uncompressed = MuSigPublicKeys(getKeys(false)).aggregate;
19+
final compressed = getMuSigKeys(true).aggregate;
20+
final uncompressed = getMuSigKeys(false).aggregate;
2821

2922
expect(compressed, uncompressed);
3023
expect(
@@ -36,7 +29,7 @@ void main() {
3629

3730
test(".tweak", () {
3831

39-
final musig = MuSigPublicKeys(getKeys(true));
32+
final musig = getMuSigKeys(true);
4033
final scalar = Uint8List(32)..last = 1;
4134

4235
// Do twice to ensure cache doesn't mutate
@@ -58,4 +51,3 @@ void main() {
5851
});
5952

6053
}
61-
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:coinlib/coinlib.dart';
4+
import 'package:coinlib/src/musig/signing.dart';
5+
import 'package:test/test.dart';
6+
import '../vectors/keys.dart';
7+
8+
void main() {
9+
10+
group("MuSigStatefulSigningSession", () {
11+
12+
setUpAll(loadCoinlib);
13+
14+
test("creates unique public nonces", () {
15+
16+
final keys = getMuSigKeys();
17+
final ourKey = keys.pubKeys.first;
18+
Uint8List getNonce() => MuSigStatefulSigningSession(
19+
keys: keys,
20+
ourPublicKey: ourKey,
21+
).ourPublicNonce;
22+
23+
expect(getNonce(), isNot(getNonce()));
24+
25+
});
26+
27+
});
28+
29+
}

0 commit comments

Comments
 (0)