diff --git a/.gitmodules b/.gitmodules index cc4481a..4996b1c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "native/secp256k1"] path = native/secp256k1 - url = https://github.com/jonasnick/secp256k1.git + url = https://github.com/bitcoin-core/secp256k1.git diff --git a/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h b/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h index 750d668..dea893d 100644 --- a/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h +++ b/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h @@ -203,6 +203,14 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1sc JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1gen (JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray); +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_nonce_gen_counter + * Signature: (JJ[B[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1gen_1counter + (JNIEnv *, jclass, jlong, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray); + /* * Class: fr_acinq_secp256k1_Secp256k1CFunctions * Method: secp256k1_musig_nonce_agg diff --git a/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c b/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c index cf9053c..7355a7a 100644 --- a/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c +++ b/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c @@ -549,9 +549,9 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 if (jpubkeys == NULL) return NULL; - count = (*penv)->GetArrayLength(penv, jpubkeys); - CHECKRESULT(count < 1, "pubkey array cannot be empty") - pubkeys = calloc(count, sizeof(secp256k1_pubkey *)); + count = (*penv)->GetArrayLength(penv, jpubkeys); + CHECKRESULT(count < 1, "pubkey array cannot be empty") + pubkeys = calloc(count, sizeof(secp256k1_pubkey *)); for (i = 0; i < count; i++) { @@ -907,6 +907,74 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 return jnonce; } +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1gen_1counter(JNIEnv *penv, jclass clazz, jlong jctx, jlong jcounter, jbyteArray jseckey, jbyteArray jmsg32, jbyteArray jkeyaggcache, jbyteArray jextra_input32) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + int result = 0; + size_t size; + secp256k1_musig_pubnonce pubnonce; + secp256k1_musig_secnonce secnonce; + jbyte *seckey; + unsigned char msg32[32]; + secp256k1_keypair keypair; + secp256k1_musig_keyagg_cache keyaggcache; + unsigned char extra_input32[32]; + jbyteArray jnonce; + jbyte *nonce_ptr = NULL; + unsigned char nonce[fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE + fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE]; + + if (jctx == 0) + return NULL; + + if (jseckey == NULL) + return NULL; + + seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); + result = secp256k1_keypair_create(ctx, &keypair, seckey); + (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); + CHECKRESULT(!result, "secp256k1_keypair_create failed"); + + size = (*penv)->GetArrayLength(penv, jseckey); + CHECKRESULT(size != 32, "invalid private key size"); + copy_bytes_from_java(penv, jseckey, size, seckey); + + if (jmsg32 != NULL) + { + size = (*penv)->GetArrayLength(penv, jmsg32); + CHECKRESULT(size != 32, "invalid message size"); + copy_bytes_from_java(penv, jmsg32, size, msg32); + } + + if (jkeyaggcache != NULL) + { + size = (*penv)->GetArrayLength(penv, jkeyaggcache); + CHECKRESULT(size != sizeof(secp256k1_musig_keyagg_cache), "invalid keyagg cache size"); + copy_bytes_from_java(penv, jkeyaggcache, size, keyaggcache.data); + } + + if (jextra_input32 != NULL) + { + size = (*penv)->GetArrayLength(penv, jextra_input32); + CHECKRESULT(size != 32, "invalid extra input size"); + copy_bytes_from_java(penv, jextra_input32, size, extra_input32); + } + + result = secp256k1_musig_nonce_gen_counter(ctx, &secnonce, &pubnonce, jcounter, + &keypair, + jmsg32 == NULL ? NULL : msg32, jkeyaggcache == NULL ? NULL : &keyaggcache, jextra_input32 == NULL ? NULL : extra_input32); + CHECKRESULT(!result, "secp256k1_musig_nonce_gen failed"); + + memcpy(nonce, secnonce.data, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE); + result = secp256k1_musig_pubnonce_serialize(ctx, nonce + fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE, &pubnonce); + CHECKRESULT(!result, "secp256k1_musig_pubnonce_serialize failed"); + + jnonce = (*penv)->NewByteArray(penv, sizeof(nonce)); + nonce_ptr = (*penv)->GetByteArrayElements(penv, jnonce, 0); + memcpy(nonce_ptr, nonce, sizeof(nonce)); + (*penv)->ReleaseByteArrayElements(penv, jnonce, nonce_ptr, 0); + return jnonce; +} + void free_nonces(secp256k1_musig_pubnonce **nonces, size_t count) { size_t i; diff --git a/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java b/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java index fd49e3e..5c12b22 100644 --- a/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java +++ b/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java @@ -89,7 +89,9 @@ public class Secp256k1CFunctions { public static native int secp256k1_schnorrsig_verify(long ctx, byte[] sig, byte[] msg, byte[] pubkey); - public static native byte[] secp256k1_musig_nonce_gen(long ctx, byte[] session_id32, byte[] seckey, byte[] pubkey, byte[] msg32, byte[] keyagg_cache, byte[] extra_input32); + public static native byte[] secp256k1_musig_nonce_gen(long ctx, byte[] session_rand32, byte[] seckey, byte[] pubkey, byte[] msg32, byte[] keyagg_cache, byte[] extra_input32); + + public static native byte[] secp256k1_musig_nonce_gen_counter(long ctx, long nonrepeating_cnt, byte[] seckey, byte[] msg32, byte[] keyagg_cache, byte[] extra_input32); public static native byte[] secp256k1_musig_nonce_agg(long ctx, byte[][] nonces); diff --git a/jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt b/jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt index bcbe03f..4966cf2 100644 --- a/jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt +++ b/jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt @@ -92,8 +92,12 @@ public object NativeSecp256k1 : Secp256k1 { return Secp256k1CFunctions.secp256k1_schnorrsig_sign(Secp256k1Context.getContext(), data, sec, auxrand32) } - override fun musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray { - return Secp256k1CFunctions.secp256k1_musig_nonce_gen(Secp256k1Context.getContext(), sessionId32, privkey, aggpubkey, msg32, keyaggCache, extraInput32) + override fun musigNonceGen(sessionRandom32: ByteArray, privkey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_nonce_gen(Secp256k1Context.getContext(), sessionRandom32, privkey, pubkey, msg32, keyaggCache, extraInput32) + } + + override fun musigNonceGenCounter(nonRepeatingCounter: ULong, privkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_nonce_gen_counter(Secp256k1Context.getContext(), nonRepeatingCounter.toLong(), privkey, msg32, keyaggCache, extraInput32) } override fun musigNonceAgg(pubnonces: Array): ByteArray { diff --git a/native/secp256k1 b/native/secp256k1 index dd4932b..0cdc758 160000 --- a/native/secp256k1 +++ b/native/secp256k1 @@ -1 +1 @@ -Subproject commit dd4932b67b573b2366e729e869918b17964f5f83 +Subproject commit 0cdc758a56360bf58a851fe91085a327ec97685a diff --git a/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt index 4ea9794..d2856e0 100644 --- a/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt +++ b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt @@ -159,15 +159,29 @@ public interface Secp256k1 { * This nonce must never be persisted or reused across signing sessions. * All optional arguments exist to enrich the quality of the randomness used, which is critical for security. * - * @param sessionId32 unique 32-byte session ID. + * @param sessionRandom32 unique 32-byte random data that must not be reused to generate other nonces * @param privkey (optional) signer's private key. - * @param aggpubkey aggregated public key of all participants in the signing session. + * @param pubkey signer's public key * @param msg32 (optional) 32-byte message that will be signed, if already known. * @param keyaggCache (optional) key aggregation cache data from the signing session. * @param extraInput32 (optional) additional 32-byte random data. * @return serialized version of the secret nonce and the corresponding public nonce. */ - public fun musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray + public fun musigNonceGen(sessionRandom32: ByteArray, privkey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray + + /** + * Alternative counter-based method for generating nonce. + * This nonce must never be persisted or reused across signing sessions. + * All optional arguments exist to enrich the quality of the randomness used, which is critical for security. + * + * @param nonRepeatingCounter non-repeating counter that must never be reused with the same private key + * @param privkey signer's private key. + * @param msg32 (optional) 32-byte message that will be signed, if already known. + * @param keyaggCache (optional) key aggregation cache data from the signing session. + * @param extraInput32 (optional) additional 32-byte random data. + * @return serialized version of the secret nonce and the corresponding public nonce. + */ + public fun musigNonceGenCounter(nonRepeatingCounter: ULong, privkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray /** * Aggregate public nonces from all participants of a signing session. diff --git a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt index 50e3360..fedcd53 100644 --- a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt +++ b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt @@ -291,8 +291,8 @@ public object Secp256k1Native : Secp256k1 { } } - override fun musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray { - require(sessionId32.size == 32) + override fun musigNonceGen(sessionRandom32: ByteArray, privkey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray { + require(sessionRandom32.size == 32) privkey?.let { require(it.size == 32) } msg32?.let { require(it.size == 32) } keyaggCache?.let { require(it.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) } @@ -301,17 +301,20 @@ public object Secp256k1Native : Secp256k1 { val nonce = memScoped { val secnonce = alloc() val pubnonce = alloc() - val nPubkey = allocPublicKey(aggpubkey) + val nPubkey = allocPublicKey(pubkey) val nKeyAggCache = keyaggCache?.let { val n = alloc() memcpy(n.ptr, toNat(it), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) n } + // we make a native copy of sessionRandom32, which will be zeroed by secp256k1_musig_nonce_gen + val sessionRand32 = allocArray(32) + memcpy(sessionRand32.pointed.ptr, toNat(sessionRandom32), 32u) secp256k1_musig_nonce_gen( ctx, secnonce.ptr, pubnonce.ptr, - toNat(sessionId32), + sessionRand32, privkey?.let { toNat(it) }, nPubkey.ptr, msg32?.let { toNat(it) }, @@ -324,6 +327,29 @@ public object Secp256k1Native : Secp256k1 { return nonce } + override fun musigNonceGenCounter(nonRepeatingCounter: ULong, privkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray { + require(privkey.size ==32) + msg32?.let { require(it.size == 32) } + keyaggCache?.let { require(it.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) } + extraInput32?.let { require(it.size == 32) } + val nonce = memScoped { + val secnonce = alloc() + val pubnonce = alloc() + val nKeypair = alloc() + secp256k1_keypair_create(ctx, nKeypair.ptr, toNat(privkey)) + val nKeyAggCache = keyaggCache?.let { + val n = alloc() + memcpy(n.ptr, toNat(it), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + n + } + secp256k1_musig_nonce_gen_counter(ctx, secnonce.ptr, pubnonce.ptr, nonRepeatingCounter, nKeypair.ptr, msg32?.let { toNat(it) },nKeyAggCache?.ptr, extraInput32?.let { toNat(it) }).requireSuccess("secp256k1_musig_nonce_gen_counter() failed") + val nPubnonce = allocArray(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + secp256k1_musig_pubnonce_serialize(ctx, nPubnonce, pubnonce.ptr).requireSuccess("secp256k1_musig_pubnonce_serialize failed") + secnonce.ptr.readBytes(Secp256k1.MUSIG2_SECRET_NONCE_SIZE) + nPubnonce.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + } + return nonce + } + override fun musigNonceAgg(pubnonces: Array): ByteArray { require(pubnonces.isNotEmpty()) pubnonces.forEach { require(it.size == Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) } diff --git a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt index 584476c..380cfc6 100644 --- a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt +++ b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt @@ -90,6 +90,16 @@ class Musig2Test { } } + @Test + fun `generate secret nonce from counter`() { + val sk = Hex.decode("EEC1CB7D1B7254C5CAB0D9C61AB02E643D464A59FE6C96A7EFE871F07C5AEF54") + val nonce = Secp256k1.musigNonceGenCounter(0UL, sk, null, null, null) + val secnonce = nonce.copyOfRange(0, Secp256k1.MUSIG2_SECRET_NONCE_SIZE) + val pubnonce = nonce.copyOfRange(Secp256k1.MUSIG2_SECRET_NONCE_SIZE, Secp256k1.MUSIG2_SECRET_NONCE_SIZE + Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + assertContentEquals(secnonce.copyOfRange(4, 4 + 64), Hex.decode("842F1380CD17A198FC3DAD3B7DA7492941F46976F2702FF7C66F24F472036AF1DA3F952DDE4A2DA6B6325707CE87A4E3616D06FC5F81A9C99386D20A99CECF99")) + assertContentEquals(pubnonce, Hex.decode("03A5B9B6907942EACDDA49A366016EC2E62404A1BF4AB6D4DB82067BC3ADF086D7033205DB9EB34D5C7CE02848CAC68A83ED73E3883477F563F23CE9A11A7721EC64")) + } + @Test fun `aggregate nonces`() { val tests = TestHelpers.readResourceAsJson("musig2/nonce_agg_vectors.json")