From 61df0e8a9a969c9a673f24ad0ef0dd0d508fd657 Mon Sep 17 00:00:00 2001 From: sstone Date: Mon, 15 Apr 2024 19:12:18 +0200 Subject: [PATCH] Set a "noop" illegal callback The default "illegal" callback calls abort, which will crash the JVM or native app. We check arguments before calling secp256k1 so it should never happen, except when trying to create a partial musig2 signature with an secret nonce that does not match the private key. Methods that could be used to verify that the secret nonce does match the private key are not exported, hence the choice to set a custom callback. --- .../fr_acinq_secp256k1_Secp256k1CFunctions.c | 9 +++++++- .../fr/acinq/secp256k1/Secp256k1Native.kt | 5 +++- .../fr/acinq/secp256k1/Secp256k1Test.kt | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c b/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c index cf9053c..e5df78c 100644 --- a/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c +++ b/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c @@ -44,6 +44,11 @@ void JNI_ThrowByName(JNIEnv *penv, const char *name, const char *msg) } \ } +static void secp256k1_noop_illegal_callback_fn(const char* str, void* data) { + (void)str; + (void)data; +} + /* * Class: fr_acinq_bitcoin_Secp256k1Bindings * Method: secp256k1_context_create @@ -51,7 +56,9 @@ void JNI_ThrowByName(JNIEnv *penv, const char *name, const char *msg) */ JNIEXPORT jlong JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1context_1create(JNIEnv *penv, jclass clazz, jint flags) { - return (jlong)secp256k1_context_create(flags); + jlong ctx = (jlong)secp256k1_context_create(flags); + secp256k1_context_set_illegal_callback(ctx, &secp256k1_noop_illegal_callback_fn, NULL); + return ctx; } /* diff --git a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt index 48468fe..b6182bd 100644 --- a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt +++ b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt @@ -9,8 +9,11 @@ import secp256k1.* public object Secp256k1Native : Secp256k1 { private val ctx: CPointer by lazy { - secp256k1_context_create((SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_SIGN or SECP256K1_FLAGS_BIT_CONTEXT_VERIFY).toUInt()) + val c = secp256k1_context_create((SECP256K1_FLAGS_TYPE_CONTEXT or SECP256K1_FLAGS_BIT_CONTEXT_SIGN or SECP256K1_FLAGS_BIT_CONTEXT_VERIFY).toUInt()) ?: error("Could not create secp256k1 context") + val callback = staticCFunction { _: CPointer?, _: COpaquePointer? -> } + secp256k1_context_set_illegal_callback(c, callback, null) + c } private fun Int.requireSuccess(message: String): Int = if (this != 1) throw Secp256k1Exception(message) else this diff --git a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt index 5cc71f8..5ea7450 100644 --- a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt +++ b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt @@ -520,6 +520,29 @@ class Secp256k1Test { -1 ) } + assertFails { + val privkeys = listOf( + "0101010101010101010101010101010101010101010101010101010101010101", + "0202020202020202020202020202020202020202020202020202020202020202", + ).map { Hex.decode(it) }.toTypedArray() + val pubkeys = privkeys.map { Secp256k1.pubkeyCreate(it) } + + val sessionId = Hex.decode("0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F") + val nonces = pubkeys.map { Secp256k1.musigNonceGen(sessionId, null, it, null, null, null) } + val secnonces = nonces.map { it.copyOfRange(0, 132) } + val pubnonces = nonces.map { it.copyOfRange(132, 132 + 66) } + val aggnonce = Secp256k1.musigNonceAgg(pubnonces.toTypedArray()) + + val keyaggCaches = (0 until 2).map { ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) } + val aggpubkey = Secp256k1.musigPubkeyAgg(pubkeys.toTypedArray(), keyaggCaches[0]) + assertContentEquals(aggpubkey, Secp256k1.musigPubkeyAgg(pubkeys.toTypedArray(), keyaggCaches[1])) + assertContentEquals(keyaggCaches[0], keyaggCaches[1]) + val msg32 = Hex.decode("0303030303030303030303030303030303030303030303030303030303030303") + val sessions = (0 until 2).map { Secp256k1.musigNonceProcess(aggnonce, msg32, keyaggCaches[it]) } + + // we sign with the wrong secret nonce. it should fail (i.e. trigger an exception) but not crash the JVM + Secp256k1.musigPartialSign(secnonces[1], privkeys[0], keyaggCaches[0], sessions[0]) + } } @Test