Skip to content

Commit 7b9c200

Browse files
authored
Add documentation to musig2 functions (#97)
Usage of the Musig2 functions isn't intuitive at all, especially with the key aggregation cache and session data. It's important to provide accurate documentation to help users understand how to correctly produce musig2 signatures. We also change argument names to match Kotlin best practices instead of using the same argument names as C functions.
1 parent a3355d2 commit 7b9c200

File tree

6 files changed

+195
-81
lines changed

6 files changed

+195
-81
lines changed

jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c

+2-4
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,6 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
687687
secp256k1_context *ctx = (secp256k1_context *)jctx;
688688
jbyte *sig;
689689
secp256k1_ecdsa_signature signature;
690-
;
691690
unsigned char der[73];
692691
size_t size;
693692
int result = 0;
@@ -858,7 +857,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
858857
if (jseckey != NULL)
859858
{
860859
size = (*penv)->GetArrayLength(penv, jseckey);
861-
CHECKRESULT(size != 32, "invalid session_id size");
860+
CHECKRESULT(size != 32, "invalid private key size");
862861
copy_bytes_from_java(penv, jseckey, size, seckey);
863862
}
864863

@@ -1016,7 +1015,6 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
10161015
free_pubkeys(pubkeys, count);
10171016
CHECKRESULT(!result, "secp256k1_musig_pubkey_agg failed");
10181017

1019-
size = 32;
10201018
jpubkey = (*penv)->NewByteArray(penv, 32);
10211019
pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0);
10221020
result = secp256k1_xonly_pubkey_serialize(ctx, (unsigned char *)pub, &combined);
@@ -1149,7 +1147,7 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256
11491147
CHECKRESULT((*penv)->GetArrayLength(penv, jmsg32) != 32, "invalid message size");
11501148
if (jkeyaggcache == NULL)
11511149
return NULL;
1152-
CHECKRESULT((*penv)->GetArrayLength(penv, jkeyaggcache) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE, "invalid nonce size");
1150+
CHECKRESULT((*penv)->GetArrayLength(penv, jkeyaggcache) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE, "invalid keyagg cache size");
11531151

11541152
ptr = (*penv)->GetByteArrayElements(penv, jaggnonce, 0);
11551153
result = secp256k1_musig_aggnonce_parse(ctx, &aggnonce, ptr);

jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,32 @@ public class Secp256k1CFunctions {
2929
public static final int SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION);
3030
public static final int SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION);
3131

32+
/**
33+
* A musig2 public nonce is simply two elliptic curve points.
34+
*/
3235
public static final int SECP256K1_MUSIG_PUBLIC_NONCE_SIZE = 66;
3336

37+
/**
38+
* A musig2 private nonce is basically two scalars, but should be treated as an opaque blob.
39+
*/
3440
public static final int SECP256K1_MUSIG_SECRET_NONCE_SIZE = 132;
3541

42+
/**
43+
* When aggregating public keys, we cache information in an opaque blob (must not be interpreted).
44+
*/
3645
public static final int SECP256K1_MUSIG_KEYAGG_CACHE_SIZE = 197;
3746

47+
/**
48+
* When creating partial signatures and aggregating them, session data is kept in an opaque blob (must not be interpreted).
49+
*/
3850
public static final int SECP256K1_MUSIG_SESSION_SIZE = 133;
3951

4052
public static native long secp256k1_context_create(int flags);
4153

4254
public static native void secp256k1_context_destroy(long ctx);
4355

4456
public static native int secp256k1_ec_seckey_verify(long ctx, byte[] seckey);
45-
57+
4658
public static native byte[] secp256k1_ec_pubkey_parse(long ctx, byte[] pubkey);
4759

4860
public static native byte[] secp256k1_ec_pubkey_create(long ctx, byte[] seckey);
@@ -93,5 +105,5 @@ public class Secp256k1CFunctions {
93105

94106
public static native int secp256k1_musig_partial_sig_verify(long ctx, byte[] psig, byte[] pubnonce, byte[] pubkey, byte[] keyagg_cache, byte[] session);
95107

96-
public static native byte[] secp256k1_musig_partial_sig_agg(long ctx, byte[] session, byte[][] psigs);
108+
public static native byte[] secp256k1_musig_partial_sig_agg(long ctx, byte[] session, byte[][] psigs);
97109
}

jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt

+14-14
Original file line numberDiff line numberDiff line change
@@ -92,36 +92,36 @@ public object NativeSecp256k1 : Secp256k1 {
9292
return Secp256k1CFunctions.secp256k1_schnorrsig_sign(Secp256k1Context.getContext(), data, sec, auxrand32)
9393
}
9494

95-
override fun musigNonceGen(session_id32: ByteArray, seckey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyagg_cache: ByteArray?, extra_input32: ByteArray?): ByteArray {
96-
return Secp256k1CFunctions.secp256k1_musig_nonce_gen(Secp256k1Context.getContext(), session_id32, seckey, pubkey, msg32, keyagg_cache, extra_input32)
95+
override fun musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray {
96+
return Secp256k1CFunctions.secp256k1_musig_nonce_gen(Secp256k1Context.getContext(), sessionId32, privkey, aggpubkey, msg32, keyaggCache, extraInput32)
9797
}
9898

9999
override fun musigNonceAgg(pubnonces: Array<ByteArray>): ByteArray {
100100
return Secp256k1CFunctions.secp256k1_musig_nonce_agg(Secp256k1Context.getContext(), pubnonces)
101101
}
102102

103-
override fun musigPubkeyAgg(pubkeys: Array<ByteArray>, keyagg_cache: ByteArray?): ByteArray {
104-
return Secp256k1CFunctions.secp256k1_musig_pubkey_agg(Secp256k1Context.getContext(), pubkeys, keyagg_cache)
103+
override fun musigPubkeyAgg(pubkeys: Array<ByteArray>, keyaggCache: ByteArray?): ByteArray {
104+
return Secp256k1CFunctions.secp256k1_musig_pubkey_agg(Secp256k1Context.getContext(), pubkeys, keyaggCache)
105105
}
106106

107-
override fun musigPubkeyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray {
108-
return Secp256k1CFunctions.secp256k1_musig_pubkey_ec_tweak_add(Secp256k1Context.getContext(), keyagg_cache, tweak32)
107+
override fun musigPubkeyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray {
108+
return Secp256k1CFunctions.secp256k1_musig_pubkey_ec_tweak_add(Secp256k1Context.getContext(), keyaggCache, tweak32)
109109
}
110110

111-
override fun musigPubkeyXonlyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray {
112-
return Secp256k1CFunctions.secp256k1_musig_pubkey_xonly_tweak_add(Secp256k1Context.getContext(), keyagg_cache, tweak32)
111+
override fun musigPubkeyXonlyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray {
112+
return Secp256k1CFunctions.secp256k1_musig_pubkey_xonly_tweak_add(Secp256k1Context.getContext(), keyaggCache, tweak32)
113113
}
114114

115-
override fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyagg_cache: ByteArray,): ByteArray {
116-
return Secp256k1CFunctions.secp256k1_musig_nonce_process(Secp256k1Context.getContext(), aggnonce, msg32, keyagg_cache)
115+
override fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray {
116+
return Secp256k1CFunctions.secp256k1_musig_nonce_process(Secp256k1Context.getContext(), aggnonce, msg32, keyaggCache)
117117
}
118118

119-
override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyagg_cache: ByteArray, session: ByteArray): ByteArray {
120-
return Secp256k1CFunctions.secp256k1_musig_partial_sign(Secp256k1Context.getContext(), secnonce, privkey, keyagg_cache, session)
119+
override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray {
120+
return Secp256k1CFunctions.secp256k1_musig_partial_sign(Secp256k1Context.getContext(), secnonce, privkey, keyaggCache, session)
121121
}
122122

123-
override fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyagg_cache: ByteArray, session: ByteArray): Int {
124-
return Secp256k1CFunctions.secp256k1_musig_partial_sig_verify(Secp256k1Context.getContext(), psig, pubnonce, pubkey, keyagg_cache, session)
123+
override fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): Int {
124+
return Secp256k1CFunctions.secp256k1_musig_partial_sig_verify(Secp256k1Context.getContext(), psig, pubnonce, pubkey, keyaggCache, session)
125125
}
126126

127127
override fun musigPartialSigAgg(session: ByteArray, psigs: Array<ByteArray>): ByteArray {

src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt

+88-9
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public interface Secp256k1 {
5555
*/
5656
public fun signSchnorr(data: ByteArray, sec: ByteArray, auxrand32: ByteArray?): ByteArray
5757

58-
/**
58+
/**
5959
* Convert an ECDSA signature to a normalized lower-S form (bitcoin standardness rule).
6060
* Returns the normalized signature and a boolean set to true if the input signature was not normalized.
6161
*
@@ -149,29 +149,108 @@ public interface Secp256k1 {
149149
compressed[0] = if (pubkey.last() % 2 == 0) 2.toByte() else 3.toByte()
150150
compressed
151151
}
152+
152153
else -> throw Secp256k1Exception("invalid public key")
153154
}
154155
}
155156

156-
public fun musigNonceGen(session_id32: ByteArray, seckey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyagg_cache: ByteArray?, extra_input32: ByteArray?): ByteArray
157+
/**
158+
* Generate a secret nonce to be used in a musig2 signing session.
159+
* This nonce must never be persisted or reused across signing sessions.
160+
* All optional arguments exist to enrich the quality of the randomness used, which is critical for security.
161+
*
162+
* @param sessionId32 unique 32-byte session ID.
163+
* @param privkey (optional) signer's private key.
164+
* @param aggpubkey aggregated public key of all participants in the signing session.
165+
* @param msg32 (optional) 32-byte message that will be signed, if already known.
166+
* @param keyaggCache (optional) key aggregation cache data from the signing session.
167+
* @param extraInput32 (optional) additional 32-byte random data.
168+
* @return serialized version of the secret nonce and the corresponding public nonce.
169+
*/
170+
public fun musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray
157171

172+
/**
173+
* Aggregate public nonces from all participants of a signing session.
174+
*
175+
* @param pubnonces public nonces (one per participant).
176+
* @return 66-byte aggregate public nonce (two public keys) or throws an exception is a nonce is invalid.
177+
*/
158178
public fun musigNonceAgg(pubnonces: Array<ByteArray>): ByteArray
159179

160-
public fun musigPubkeyAgg(pubkeys: Array<ByteArray>, keyagg_cache: ByteArray?): ByteArray
180+
/**
181+
* Aggregate public keys from all participants of a signing session.
182+
*
183+
* @param pubkeys public keys of all participants in the signing session.
184+
* @param keyaggCache (optional) key aggregation cache data from the signing session. If an empty byte array is
185+
* provided, it will be filled with key aggregation data that can be used for the next steps of the signing process.
186+
* @return 32-byte x-only public key.
187+
*/
188+
public fun musigPubkeyAgg(pubkeys: Array<ByteArray>, keyaggCache: ByteArray?): ByteArray
161189

162-
public fun musigPubkeyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray
190+
/**
191+
* Tweak the aggregated public key of a signing session.
192+
*
193+
* @param keyaggCache key aggregation cache filled by [musigPubkeyAgg].
194+
* @param tweak32 private key tweak to apply.
195+
* @return P + tweak32 * G (where P is the aggregated public key from [keyaggCache]). The key aggregation cache will
196+
* be updated with the tweaked public key.
197+
*/
198+
public fun musigPubkeyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray
163199

164-
public fun musigPubkeyXonlyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray
200+
/**
201+
* Tweak the aggregated public key of a signing session, treating it as an x-only public key (e.g. when using taproot).
202+
*
203+
* @param keyaggCache key aggregation cache filled by [musigPubkeyAgg].
204+
* @param tweak32 private key tweak to apply.
205+
* @return with_even_y(P) + tweak32 * G (where P is the aggregated public key from [keyaggCache]). The key aggregation
206+
* cache will be updated with the tweaked public key.
207+
*/
208+
public fun musigPubkeyXonlyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray
165209

166-
public fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyagg_cache: ByteArray): ByteArray
210+
/**
211+
* Create a signing session context based on the public information from all participants.
212+
*
213+
* @param aggnonce aggregated public nonce (see [musigNonceAgg]).
214+
* @param msg32 32-byte message that will be signed.
215+
* @param keyaggCache aggregated public key cache filled by calling [musigPubkeyAgg] with the public keys of all participants.
216+
* @return signing session context that can be used to create partial signatures and aggregate them.
217+
*/
218+
public fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray
167219

168-
public fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyagg_cache: ByteArray, session: ByteArray): ByteArray
220+
/**
221+
* Create a partial signature.
222+
*
223+
* @param secnonce signer's secret nonce (see [musigNonceGen]).
224+
* @param privkey signer's private key.
225+
* @param keyaggCache aggregated public key cache filled by calling [musigPubkeyAgg] with the public keys of all participants.
226+
* @param session signing session context (see [musigNonceProcess]).
227+
* @return 32-byte partial signature.
228+
*/
229+
public fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray
169230

170-
public fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyagg_cache: ByteArray, session: ByteArray): Int
231+
/**
232+
* Verify the partial signature from one of the signing session's participants.
233+
*
234+
* @param psig 32-byte partial signature.
235+
* @param pubnonce individual public nonce of the signing participant.
236+
* @param pubkey individual public key of the signing participant.
237+
* @param keyaggCache aggregated public key cache filled by calling [musigPubkeyAgg] with the public keys of all participants.
238+
* @param session signing session context (see [musigNonceProcess]).
239+
* @return result code (1 if the partial signature is valid, 0 otherwise).
240+
*/
241+
public fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): Int
171242

243+
/**
244+
* Aggregate partial signatures from all participants into a single schnorr signature. If some of the partial
245+
* signatures are invalid, this function will return an invalid aggregated signature without raising an error.
246+
* It is recommended to use [musigPartialSigVerify] to verify partial signatures first.
247+
*
248+
* @param session signing session context (see [musigNonceProcess]).
249+
* @param psigs list of 32-byte partial signatures.
250+
* @return 64-byte aggregated schnorr signature.
251+
*/
172252
public fun musigPartialSigAgg(session: ByteArray, psigs: Array<ByteArray>): ByteArray
173253

174-
175254
/**
176255
* Delete the secp256k1 context from dynamic memory.
177256
*/

0 commit comments

Comments
 (0)