From 4203b0600b1cbb5f23a9a88a26f491892c88f19b Mon Sep 17 00:00:00 2001 From: sstone Date: Mon, 11 Mar 2024 15:10:29 +0100 Subject: [PATCH] Implement secp256k1_musig_nonce_gen_counter() --- .../fr_acinq_secp256k1_Secp256k1CFunctions.h | 8 ++ .../fr_acinq_secp256k1_Secp256k1CFunctions.c | 80 ++++++++++++++++++- .../acinq/secp256k1/Secp256k1CFunctions.java | 4 +- .../fr/acinq/secp256k1/NativeSecp256k1.kt | 8 +- native/secp256k1 | 2 +- .../kotlin/fr/acinq/secp256k1/Secp256k1.kt | 21 ++++- .../fr/acinq/secp256k1/Secp256k1Native.kt | 32 +++++++- .../kotlin/fr/acinq/secp256k1/Musig2Test.kt | 11 +++ 8 files changed, 152 insertions(+), 14 deletions(-) diff --git a/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h b/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h index 750d668..b3189eb 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)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1gen_1counter + (JNIEnv *, jclass, jlong, jlong, jbyteArray, 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..5014a88 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,80 @@ 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 jpubkey, 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 *pubkey_ptr; + secp256k1_pubkey pubkey; + unsigned char seckey[32]; + unsigned char msg32[32]; + 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; + + size = (*penv)->GetArrayLength(penv, jseckey); + CHECKRESULT(size != 32, "invalid private key size"); + copy_bytes_from_java(penv, jseckey, size, seckey); + + if (jpubkey == NULL) + return NULL; + + size = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); + pubkey_ptr = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char *)pubkey_ptr, size); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkey_ptr, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); + + 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, + seckey, &pubkey, + 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..cec3684 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[] pubkey, 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..77c8eef 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, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_nonce_gen_counter(Secp256k1Context.getContext(), nonRepeatingCounter.toLong(), privkey, pubkey, msg32, keyaggCache, extraInput32) } override fun musigNonceAgg(pubnonces: Array): ByteArray { diff --git a/native/secp256k1 b/native/secp256k1 index 4619706..2512e4b 160000 --- a/native/secp256k1 +++ b/native/secp256k1 @@ -1 +1 @@ -Subproject commit 461970682f56a8e15fc71ecab18d4537e50441fc +Subproject commit 2512e4b9431363cbd2998ce2ac43ab90c8134d37 diff --git a/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt index 4ea9794..15fe42a 100644 --- a/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt +++ b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt @@ -159,15 +159,30 @@ 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 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 musigNonceGenCounter(nonRepeatingCounter: ULong, privkey: ByteArray, pubkey: 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..678effb 100644 --- a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt +++ b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt @@ -3,6 +3,7 @@ package fr.acinq.secp256k1 import kotlinx.cinterop.* import platform.posix.memcpy import platform.posix.size_tVar +import platform.posix.uint64_t import secp256k1.* @OptIn(ExperimentalUnsignedTypes::class, ExperimentalForeignApi::class) @@ -291,8 +292,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,7 +302,7 @@ 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()) @@ -311,7 +312,7 @@ public object Secp256k1Native : Secp256k1 { ctx, secnonce.ptr, pubnonce.ptr, - toNat(sessionId32), + toNat(sessionRandom32), privkey?.let { toNat(it) }, nPubkey.ptr, msg32?.let { toNat(it) }, @@ -324,6 +325,29 @@ public object Secp256k1Native : Secp256k1 { return nonce } + override fun musigNonceGenCounter(nonRepeatingCounter: ULong, privkey: ByteArray, pubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray { + require(privkey.size ==32) + require(pubkey.size == 33 || pubkey.size == 65) + 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 nPubkey = allocPublicKey(pubkey) + 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, toNat(privkey), nPubkey.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 7630df4..68718ff 100644 --- a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt +++ b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt @@ -108,6 +108,17 @@ class Musig2Test { } } + @Test + fun `generate secret nonce from counter`() { + val sk = Hex.decode("EEC1CB7D1B7254C5CAB0D9C61AB02E643D464A59FE6C96A7EFE871F07C5AEF54") + val pk = Secp256k1.pubkeyCreate(sk) + val nonce = Secp256k1.musigNonceGenCounter(0UL, sk, pk, 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 = readData("musig2/nonce_agg_vectors.json")