Skip to content

Commit 46d5820

Browse files
committed
Add partial signing
1 parent c437027 commit 46d5820

File tree

8 files changed

+195
-28
lines changed

8 files changed

+195
-28
lines changed

coinlib/bin/build_wasm.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ RUN ${WASI_SDK_PATH}/bin/wasm-ld \
8686
--export secp256k1_musig_pubnonce_serialize \
8787
--export secp256k1_musig_nonce_agg \
8888
--export secp256k1_musig_nonce_process \
89+
--export secp256k1_musig_partial_sign \
90+
--export secp256k1_musig_partial_sig_parse \
91+
--export secp256k1_musig_partial_sig_serialize \
8992
# The secp256k1 library file
9093
build/lib/libsecp256k1.a \
9194
# Need to include libc for wasi here as it isn't done for us

coinlib/lib/src/musig/signing.dart

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,30 @@ class MuSigPublicNonce {
2828

2929
}
3030

31+
class InvalidMuSigPartialSig implements Exception {}
32+
33+
/// The partial signature from a participant that needs to be shared.
34+
class MuSigPartialSig {
35+
36+
final OpaqueMuSigPartialSig _underlying;
37+
final Uint8List bytes;
38+
39+
MuSigPartialSig._(this._underlying, this.bytes);
40+
MuSigPartialSig._fromUnderlying(this._underlying)
41+
: bytes = secp256k1.muSigSerialisePartialSig(_underlying);
42+
43+
/// Creates the partial signature from the [bytes]. If the [bytes] are
44+
/// invalid, [InvalidMuSigPartialSig] will be thrown.
45+
factory MuSigPartialSig.fromBytes(Uint8List bytes) {
46+
try {
47+
return MuSigPartialSig._(secp256k1.muSigParsePartialSig(bytes), bytes);
48+
} on Secp256k1Exception {
49+
throw InvalidMuSigPartialSig();
50+
}
51+
}
52+
53+
}
54+
3155
/// A MuSig signing session state to be used for one signing session only.
3256
///
3357
/// This class is stateful unlike most of the classes in the library. This is to
@@ -51,8 +75,8 @@ class MuSigStatefulSigningSession {
5175
/// key for the signer with [ourPublicKey].
5276
///
5377
/// [ourPublicNonce] needs to be shared with other signers. Once all details
54-
/// and public nonces have been obtained, the signing session can be prepared
55-
/// with [prepare].
78+
/// and public nonces have been obtained, [sign] can be called to create a
79+
/// partial signature.
5680
MuSigStatefulSigningSession({
5781
required this.keys,
5882
required this.ourPublicKey,
@@ -72,25 +96,37 @@ class MuSigStatefulSigningSession {
7296

7397
}
7498

75-
/// Prepares the MuSig signing session with details required to produce
76-
/// partial signatures. This can only be done once.
99+
/// Produces a partial signature with the required details. This can only be
100+
/// done once or a [StateError] will be thrown.
77101
///
78102
/// [otherNonces] must map the public key of all other participants with their
79103
/// shared public nonces.
80104
///
81105
/// [hash] must be the 32-byte hash to be signed.
82106
///
107+
/// The [privKey] must be paired with [ourPublicKey].
108+
///
83109
/// An optional [adaptor] point can be provided to produce an adaptor
84110
/// signature.
85-
void prepare({
111+
MuSigPartialSig sign({
86112
required KeyToNonceMap otherNonces,
87113
required Uint8List hash,
114+
required ECPrivateKey privKey,
88115
ECPublicKey? adaptor,
89116
}) {
90117

91118
checkBytes(hash, 32);
92119

93-
// Ensure we haven't already aggregated the nonces
120+
// Check private key matches the participant's public key
121+
if (privKey.pubkey != ourPublicKey) {
122+
throw ArgumentError.value(
123+
privKey,
124+
"privKey",
125+
"doesn't match outPublicKey",
126+
);
127+
}
128+
129+
// Ensure we haven't already produced a partial signature
94130
if (_underlyingSession != null) {
95131
throw StateError("Already prepared signing session");
96132
}
@@ -120,6 +156,13 @@ class MuSigStatefulSigningSession {
120156
);
121157
_otherNonces = otherNonces;
122158

159+
// Produce partial signature
160+
return MuSigPartialSig._fromUnderlying(
161+
secp256k1.muSigPartialSign(
162+
_ourSecretNonce, privKey.data, keys._aggCache, _underlyingSession!,
163+
),
164+
);
165+
123166
}
124167

125168
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import 'secp256k1_web.dart' if (dart.library.io) 'secp256k1_io.dart';
22
export 'secp256k1_web.dart' if (dart.library.io) 'secp256k1_io.dart'
33
show OpaqueMuSigCache, OpaqueMuSigSecretNonce, OpaqueMuSigPublicNonce,
4-
OpaqueMuSigSession;
4+
OpaqueMuSigSession, OpaqueMuSigPartialSig;
55

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

@@ -24,7 +25,7 @@ abstract class Secp256k1Base<
2425
CtxPtr, UCharPtr, PubKeyPtr, SizeTPtr, SignaturePtr,
2526
RecoverableSignaturePtr, KeyPairPtr, XPubKeyPtr, IntPtr, MuSigAggCachePtr,
2627
PubKeyPtrPtr, MuSigSecNoncePtr, MuSigPubNoncePtr, MuSigAggNoncePtr,
27-
MuSigPubNoncePtrPtr, MuSigSessionPtr, NullPtr
28+
MuSigPubNoncePtrPtr, MuSigSessionPtr, MuSigPartialSigPtr, NullPtr
2829
> {
2930

3031
static const contextNone = 1;
@@ -39,6 +40,7 @@ abstract class Secp256k1Base<
3940
static const sigSize = 64;
4041
static const derSigSize = 72;
4142
static const muSigPubNonceSize = 66;
43+
static const muSigPartialSigSize = 32;
4244
static const recSigSize = 65;
4345
static const keyPairSize = 96;
4446
static const xonlySize = 32;
@@ -125,6 +127,16 @@ abstract class Secp256k1Base<
125127
CtxPtr, MuSigSessionPtr, MuSigAggNoncePtr, UCharPtr, MuSigAggCachePtr,
126128
NullPtr,
127129
) extMuSigNonceProcess;
130+
late int Function(
131+
CtxPtr, MuSigPartialSigPtr, MuSigSecNoncePtr, KeyPairPtr, MuSigAggCachePtr,
132+
MuSigSessionPtr,
133+
) extMuSigPartialSign;
134+
late int Function(
135+
CtxPtr, MuSigPartialSigPtr, UCharPtr,
136+
) extMuSigPartialSigParse;
137+
late int Function(
138+
CtxPtr, UCharPtr, MuSigPartialSigPtr,
139+
) extMuSigPartialSigSerialize;
128140

129141
// Heap arrays
130142

@@ -640,8 +652,8 @@ abstract class Secp256k1Base<
640652

641653
}
642654

643-
/// Returns an opaque allocated public MuSig2 nonce from the 66-byte [bytes].
644-
/// If the nonce is invalid, [Secp256k1Exception] may be thrown.
655+
/// Returns an opaque public MuSig2 nonce from the 66-byte [bytes]. If the
656+
/// nonce is invalid, [Secp256k1Exception] may be thrown.
645657
OpaqueGeneric<MuSigPubNoncePtr> muSigParsePublicNonce(Uint8List bytes) {
646658
_requireLoad();
647659

@@ -660,6 +672,7 @@ abstract class Secp256k1Base<
660672

661673
}
662674

675+
/// Obtains the 66 byte serialised public MuSig nonce
663676
Uint8List muSigSerialisePublicNonce(OpaqueGeneric<MuSigPubNoncePtr> nonce) {
664677
_requireLoad();
665678
extMuSigPubNonceSerialize(ctxPtr, muSigPubNonceArray.ptr, nonce._heap.ptr);
@@ -712,6 +725,65 @@ abstract class Secp256k1Base<
712725

713726
}
714727

728+
/// Produces a partial signature for the MuSig signing [session] using the
729+
/// [secNonce], key [cache] and [privKeyBytes].
730+
///
731+
/// The caller must ensure that the private key scalar is 32-bytes.
732+
OpaqueGeneric<MuSigPartialSigPtr> muSigPartialSign(
733+
OpaqueGeneric<MuSigSecNoncePtr> secNonce,
734+
Uint8List privKeyBytes,
735+
OpaqueGeneric<MuSigAggCachePtr> cache,
736+
OpaqueGeneric<MuSigSessionPtr> session,
737+
) {
738+
_requireLoad();
739+
740+
_parsePrivKeyIntoKeyPairPtr(privKeyBytes);
741+
742+
final partialSig = allocMuSigPartialSig();
743+
744+
if (
745+
extMuSigPartialSign(
746+
ctxPtr, partialSig.ptr, secNonce._heap.ptr, keyPair.ptr,
747+
cache._heap.ptr, session._heap.ptr,
748+
) != 1
749+
) {
750+
throw Secp256k1Exception("Could not create partial MuSig signature");
751+
}
752+
753+
return OpaqueGeneric(partialSig);
754+
755+
}
756+
757+
/// Returns an opaque MuSig2 partial signature from the 32-byte [bytes].
758+
/// If the partial signature is invalid, [Secp256k1Exception] may be thrown.
759+
OpaqueGeneric<MuSigPartialSigPtr> muSigParsePartialSig(Uint8List bytes) {
760+
_requireLoad();
761+
762+
if (bytes.length != muSigPartialSigSize) {
763+
throw Secp256k1Exception("MuSig partial signature size must be 32");
764+
}
765+
766+
// Reuse scalarArray as it is 32 bytes
767+
scalarArray.load(bytes);
768+
final partialSig = allocMuSigPartialSig();
769+
770+
if (extMuSigPartialSigParse(ctxPtr, partialSig.ptr, scalarArray.ptr) != 1) {
771+
throw Secp256k1Exception("Invalid MuSig partial signature");
772+
}
773+
774+
return OpaqueGeneric(partialSig);
775+
776+
}
777+
778+
/// Obtains the serialised 32 bytes of a MuSig partial signature
779+
Uint8List muSigSerialisePartialSig(
780+
OpaqueGeneric<MuSigPartialSigPtr> partialSig,
781+
) {
782+
_requireLoad();
783+
extMuSigPartialSigSerialize(ctxPtr, scalarArray.ptr, partialSig._heap.ptr);
784+
return scalarArray.copy;
785+
}
786+
715787
/// Specialised sub-classes should override to allocate a [size] number of
716788
/// secp256k1_pubkey and then alloate and set an array of pointers on the heap
717789
/// to them.
@@ -745,4 +817,8 @@ abstract class Secp256k1Base<
745817
/// secp256k1_musig_session on the heap.
746818
Heap<MuSigSessionPtr> allocMuSigSession();
747819

820+
/// Specialised sub-classes should override to allocate an
821+
/// secp256k1_musig_partial_sig on the heap.
822+
Heap<MuSigPartialSigPtr> allocMuSigPartialSig();
823+
748824
}

coinlib/lib/src/secp256k1/secp256k1_io.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ typedef MuSigAggCachePtr = Pointer<secp256k1_musig_keyagg_cache>;
4343
typedef MuSigSecNoncePtr = Pointer<secp256k1_musig_secnonce>;
4444
typedef MuSigPublicNoncePtr = Pointer<secp256k1_musig_pubnonce>;
4545
typedef MuSigSessionPtr = Pointer<secp256k1_musig_session>;
46+
typedef MuSigPartialSigPtr = Pointer<secp256k1_musig_partial_sig>;
4647

4748
typedef OpaqueMuSigCache = OpaqueGeneric<MuSigAggCachePtr>;
4849
typedef OpaqueMuSigSecretNonce = OpaqueGeneric<MuSigSecNoncePtr>;
4950
typedef OpaqueMuSigPublicNonce = OpaqueGeneric<MuSigPublicNoncePtr>;
5051
typedef OpaqueMuSigSession = OpaqueGeneric<MuSigSessionPtr>;
52+
typedef OpaqueMuSigPartialSig = OpaqueGeneric<MuSigPartialSigPtr>;
5153

5254
/// Specialises Secp256k1Base to use the FFI
5355
class Secp256k1 extends Secp256k1Base<
@@ -67,6 +69,7 @@ class Secp256k1 extends Secp256k1Base<
6769
Pointer<secp256k1_musig_aggnonce>,
6870
Pointer<MuSigPublicNoncePtr>,
6971
MuSigSessionPtr,
72+
MuSigPartialSigPtr,
7073
Pointer<Never>
7174
> {
7275

@@ -112,6 +115,9 @@ class Secp256k1 extends Secp256k1Base<
112115
extMuSigPubNonceSerialize = _lib.secp256k1_musig_pubnonce_serialize;
113116
extMuSigNonceAgg = _lib.secp256k1_musig_nonce_agg;
114117
extMuSigNonceProcess = _lib.secp256k1_musig_nonce_process;
118+
extMuSigPartialSign = _lib.secp256k1_musig_partial_sign;
119+
extMuSigPartialSigParse = _lib.secp256k1_musig_partial_sig_parse;
120+
extMuSigPartialSigSerialize = _lib.secp256k1_musig_partial_sig_serialize;
115121

116122
// Set heap arrays
117123
key32Array = HeapBytesFfi(Secp256k1Base.privkeySize);
@@ -179,4 +185,7 @@ class Secp256k1 extends Secp256k1Base<
179185
@override
180186
Heap<MuSigSessionPtr> allocMuSigSession() => HeapFfi(malloc());
181187

188+
@override
189+
Heap<MuSigPartialSigPtr> allocMuSigPartialSig() => HeapFfi(malloc());
190+
182191
}

coinlib/lib/src/secp256k1/secp256k1_web.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@ typedef OpaqueMuSigCache = OpaqueGeneric<int>;
99
typedef OpaqueMuSigSecretNonce = OpaqueGeneric<int>;
1010
typedef OpaqueMuSigPublicNonce = OpaqueGeneric<int>;
1111
typedef OpaqueMuSigSession = OpaqueGeneric<int>;
12+
typedef OpaqueMuSigPartialSig = OpaqueGeneric<int>;
1213

1314
/// Loads and wraps WASM code to be run via the browser JS APIs
1415
class Secp256k1 extends Secp256k1Base<
1516
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
16-
int, int
17+
int, int, int
1718
> {
1819

1920
static const _muSigCacheSize = 197;
2021
static const _muSigNonceSize = 132;
2122
static const _muSigSessionSize = 133;
23+
static const _muSigPartialSigSize = 36;
2224

2325
late final HeapFactory _heapFactory;
2426

@@ -72,6 +74,10 @@ class Secp256k1 extends Secp256k1Base<
7274
= wasm.field("secp256k1_musig_pubnonce_serialize");
7375
extMuSigNonceAgg = wasm.field("secp256k1_musig_nonce_agg");
7476
extMuSigNonceProcess = wasm.field("secp256k1_musig_nonce_process");
77+
extMuSigPartialSign = wasm.field("secp256k1_musig_partial_sign");
78+
extMuSigPartialSigParse = wasm.field("secp256k1_musig_partial_sig_parse");
79+
extMuSigPartialSigSerialize
80+
= wasm.field("secp256k1_musig_partial_sig_serialize");
7581

7682
// Local functions for loading purposes
7783
final int Function(int) contextCreate
@@ -146,4 +152,7 @@ class Secp256k1 extends Secp256k1Base<
146152
@override
147153
Heap<int> allocMuSigSession() => _heapFactory.alloc(_muSigSessionSize);
148154

155+
@override
156+
Heap<int> allocMuSigPartialSig() => _heapFactory.alloc(_muSigPartialSigSize);
157+
149158
}

0 commit comments

Comments
 (0)