From 202b0c94b6ca3007a3934c43b5733364a2e11792 Mon Sep 17 00:00:00 2001 From: Fabrice Drouin Date: Wed, 14 Feb 2024 13:28:22 +0100 Subject: [PATCH] Add support for musig2 (#93) * Use Jonas Nick's musig2 branch * Reformat c code (no functional changes) * Implement musig2 * 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. * Add musig2 reference tests (no functional changes) --------- Co-authored-by: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> --- .gitmodules | 2 +- build.gradle.kts | 2 +- .../fr_acinq_secp256k1_Secp256k1CFunctions.h | 100 ++ .../fr_acinq_secp256k1_Secp256k1CFunctions.c | 1589 +++++++++++------ .../acinq/secp256k1/Secp256k1CFunctions.java | 40 +- .../fr/acinq/secp256k1/NativeSecp256k1.kt | 36 + native/build-android.sh | 2 +- native/build-ios.sh | 2 +- native/build.sh | 2 +- native/secp256k1 | 2 +- .../kotlin/fr/acinq/secp256k1/Secp256k1.kt | 107 +- src/nativeInterop/cinterop/libsecp256k1.def | 4 +- .../fr/acinq/secp256k1/Secp256k1Native.kt | 190 +- tests/build.gradle.kts | 30 +- .../kotlin/fr/acinq/secp256k1/Musig2Test.kt | 298 ++++ .../fr/acinq/secp256k1/Secp256k1Test.kt | 138 ++ .../resources/musig2/det_sign_vectors.json | 144 ++ .../resources/musig2/key_agg_vectors.json | 88 + .../resources/musig2/key_sort_vectors.json | 18 + .../resources/musig2/nonce_agg_vectors.json | 51 + .../resources/musig2/nonce_gen_vectors.json | 44 + .../resources/musig2/sig_agg_vectors.json | 151 ++ .../resources/musig2/sign_verify_vectors.json | 212 +++ .../resources/musig2/tweak_vectors.json | 84 + 24 files changed, 2818 insertions(+), 518 deletions(-) create mode 100644 tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt create mode 100644 tests/src/commonTest/resources/musig2/det_sign_vectors.json create mode 100644 tests/src/commonTest/resources/musig2/key_agg_vectors.json create mode 100644 tests/src/commonTest/resources/musig2/key_sort_vectors.json create mode 100644 tests/src/commonTest/resources/musig2/nonce_agg_vectors.json create mode 100644 tests/src/commonTest/resources/musig2/nonce_gen_vectors.json create mode 100644 tests/src/commonTest/resources/musig2/sig_agg_vectors.json create mode 100644 tests/src/commonTest/resources/musig2/sign_verify_vectors.json create mode 100644 tests/src/commonTest/resources/musig2/tweak_vectors.json diff --git a/.gitmodules b/.gitmodules index 4996b1c..cc4481a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "native/secp256k1"] path = native/secp256k1 - url = https://github.com/bitcoin-core/secp256k1.git + url = https://github.com/jonasnick/secp256k1.git diff --git a/build.gradle.kts b/build.gradle.kts index 4087897..190f259 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ buildscript { allprojects { group = "fr.acinq.secp256k1" - version = "0.14.0-SNAPSHOT" + version = "0.14.0-MUSIG2-SNAPSHOT" repositories { google() diff --git a/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h b/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h index e9d9efe..750d668 100644 --- a/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h +++ b/jni/c/headers/java/fr_acinq_secp256k1_Secp256k1CFunctions.h @@ -7,6 +7,34 @@ #ifdef __cplusplus extern "C" { #endif +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_TYPE_CONTEXT +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_TYPE_CONTEXT 1L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_TYPE_COMPRESSION +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_TYPE_COMPRESSION 2L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_CONTEXT_VERIFY +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_CONTEXT_VERIFY 256L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_CONTEXT_SIGN +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_CONTEXT_SIGN 512L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_COMPRESSION +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_FLAGS_BIT_COMPRESSION 256L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_VERIFY +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_VERIFY 257L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_SIGN +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_SIGN 513L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_NONE +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_CONTEXT_NONE 1L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_EC_COMPRESSED +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_EC_COMPRESSED 258L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_EC_UNCOMPRESSED +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_EC_UNCOMPRESSED 2L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE 66L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE 132L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE 197L +#undef fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE +#define fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE 133L /* * Class: fr_acinq_secp256k1_Secp256k1CFunctions * Method: secp256k1_context_create @@ -167,6 +195,78 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1verify (JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray); +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_nonce_gen + * Signature: (J[B[B[B[B[B[B)[B + */ +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_agg + * Signature: (J[[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1agg + (JNIEnv *, jclass, jlong, jobjectArray); + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_pubkey_agg + * Signature: (J[[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1agg + (JNIEnv *, jclass, jlong, jobjectArray, jbyteArray); + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_pubkey_ec_tweak_add + * Signature: (J[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1ec_1tweak_1add + (JNIEnv *, jclass, jlong, jbyteArray, jbyteArray); + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_pubkey_xonly_tweak_add + * Signature: (J[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1xonly_1tweak_1add + (JNIEnv *, jclass, jlong, jbyteArray, jbyteArray); + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_nonce_process + * Signature: (J[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1process + (JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray); + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_partial_sign + * Signature: (J[B[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1partial_1sign + (JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray); + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_partial_sig_verify + * Signature: (J[B[B[B[B[B)I + */ +JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1partial_1sig_1verify + (JNIEnv *, jclass, jlong, jbyteArray, jbyteArray, jbyteArray, jbyteArray, jbyteArray); + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_partial_sig_agg + * Signature: (J[B[[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1partial_1sig_1agg + (JNIEnv *, jclass, jlong, jbyteArray, jobjectArray); + #ifdef __cplusplus } #endif diff --git a/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c b/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c index 4bc7b0a..cf9053c 100644 --- a/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c +++ b/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c @@ -8,45 +8,50 @@ #include "include/secp256k1_ecdh.h" #include "include/secp256k1_recovery.h" #include "include/secp256k1_schnorrsig.h" +#include "include/secp256k1_musig.h" #include "fr_acinq_secp256k1_Secp256k1CFunctions.h" #define SIG_FORMAT_UNKNOWN 0 #define SIG_FORMAT_COMPACT 1 #define SIG_FORMAT_DER 2 -void JNI_ThrowByName(JNIEnv *penv, const char* name, const char* msg) - { - jclass cls = (*penv)->FindClass(penv, name); - if (cls != NULL) { - (*penv)->ThrowNew(penv, cls, msg); - (*penv)->DeleteLocalRef(penv, cls); - } - } - -#define CHECKRESULT(errorcheck, message) { \ - if (errorcheck) { \ - JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1Exception", message); \ - return 0; \ - } \ +void JNI_ThrowByName(JNIEnv *penv, const char *name, const char *msg) +{ + jclass cls = (*penv)->FindClass(penv, name); + if (cls != NULL) + { + (*penv)->ThrowNew(penv, cls, msg); + (*penv)->DeleteLocalRef(penv, cls); + } } -#define CHECKRESULT1(errorcheck, message, dosomething) { \ - if (errorcheck) { \ - dosomething; \ - JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1Exception", message); \ - return 0; \ - } \ -} +#define CHECKRESULT(errorcheck, message) \ + { \ + if (errorcheck) \ + { \ + JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1Exception", message); \ + return 0; \ + } \ + } + +#define CHECKRESULT1(errorcheck, message, dosomething) \ + { \ + if (errorcheck) \ + { \ + dosomething; \ + JNI_ThrowByName(penv, "fr/acinq/secp256k1/Secp256k1Exception", message); \ + return 0; \ + } \ + } /* * Class: fr_acinq_bitcoin_Secp256k1Bindings * Method: secp256k1_context_create * Signature: (I)J */ -JNIEXPORT jlong JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1context_1create - (JNIEnv *penv, jclass clazz, jint flags) +JNIEXPORT jlong JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1context_1create(JNIEnv *penv, jclass clazz, jint flags) { - return (jlong) secp256k1_context_create(flags); + return (jlong)secp256k1_context_create(flags); } /* @@ -54,12 +59,12 @@ JNIEXPORT jlong JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1c * Method: secp256k1_context_destroy * Signature: (J)V */ -JNIEXPORT void JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1context_1destroy - (JNIEnv *penv, jclass clazz, jlong ctx) +JNIEXPORT void JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1context_1destroy(JNIEnv *penv, jclass clazz, jlong ctx) { - if (ctx != 0) { - secp256k1_context_destroy((secp256k1_context*)ctx); - } + if (ctx != 0) + { + secp256k1_context_destroy((secp256k1_context *)ctx); + } } /* @@ -67,21 +72,23 @@ JNIEXPORT void JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1co * Method: secp256k1_ec_seckey_verify * Signature: (J[B)I */ -JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1seckey_1verify - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey) +JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1seckey_1verify(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *seckey; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *seckey; + int result = 0; - if (jctx == 0) return 0; - if (jseckey == NULL) return 0; - if ((*penv)->GetArrayLength(penv, jseckey) != 32) return 0; + if (jctx == 0) + return 0; + if (jseckey == NULL) + return 0; + if ((*penv)->GetArrayLength(penv, jseckey) != 32) + return 0; - seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); - result = secp256k1_ec_seckey_verify(ctx, (unsigned char*)seckey); - (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); - return result; + seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); + result = secp256k1_ec_seckey_verify(ctx, (unsigned char *)seckey); + (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); + return result; } /* @@ -89,33 +96,34 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec * Method: secp256k1_ec_pubkey_parse * Signature: (J[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1parse - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1parse(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *pubkeyBytes; - secp256k1_pubkey pubkey; - size_t size; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *pubkeyBytes; + secp256k1_pubkey pubkey; + size_t size; + int result = 0; - if (jctx == 0) return 0; - if (jpubkey == NULL) return 0; + if (jctx == 0) + return 0; + if (jpubkey == NULL) + return 0; - size = (*penv)->GetArrayLength(penv, jpubkey); - CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); + size = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); - pubkeyBytes = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char*) pubkeyBytes, size); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkeyBytes, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); + pubkeyBytes = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char *)pubkeyBytes, size); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkeyBytes, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); - size = 65; - jpubkey = (*penv)->NewByteArray(penv, 65); - pubkeyBytes = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char*) pubkeyBytes, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkeyBytes, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); - return jpubkey; + size = 65; + jpubkey = (*penv)->NewByteArray(penv, 65); + pubkeyBytes = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char *)pubkeyBytes, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkeyBytes, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + return jpubkey; } /* @@ -123,31 +131,32 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_ec_pubkey_create * Signature: (J[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1create - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1create(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *seckey, *pubkey; - secp256k1_pubkey pub; - int result = 0; - size_t len; - jbyteArray jpubkey = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *seckey, *pubkey; + secp256k1_pubkey pub; + int result = 0; + size_t len; + jbyteArray jpubkey = 0; - if (jseckey == NULL) return NULL; - if (jctx == 0) return NULL; + if (jseckey == NULL) + return NULL; + if (jctx == 0) + return NULL; - CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); - seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); - result = secp256k1_ec_pubkey_create(ctx, &pub, (unsigned char*)seckey); - (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_create failed"); - jpubkey = (*penv)->NewByteArray(penv, 65); - pubkey = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - len = 65; - result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char*)pubkey, &len, &pub, SECP256K1_EC_UNCOMPRESSED); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkey, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); - return jpubkey; + CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); + seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); + result = secp256k1_ec_pubkey_create(ctx, &pub, (unsigned char *)seckey); + (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_create failed"); + jpubkey = (*penv)->NewByteArray(penv, 65); + pubkey = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + len = 65; + result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char *)pubkey, &len, &pub, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkey, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + return jpubkey; } /* @@ -155,42 +164,46 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_ecdsa_sign * Signature: (J[B[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1sign - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jmsg, jbyteArray jseckey) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1sign(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jmsg, jbyteArray jseckey) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *seckey, *msg, *sig; - secp256k1_ecdsa_signature signature; - int result = 0; - jbyteArray jsig; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *seckey, *msg, *sig; + secp256k1_ecdsa_signature signature; + int result = 0; + jbyteArray jsig; - if (jctx == 0) return NULL; - if (jmsg == NULL) return NULL; - if (jseckey == NULL) return NULL; + if (jctx == 0) + return NULL; + if (jmsg == NULL) + return NULL; + if (jseckey == NULL) + return NULL; - CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); - CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message key must be 32 bytes"); - seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); - msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); + CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); + CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message key must be 32 bytes"); + seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); + msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); - result = secp256k1_ecdsa_sign(ctx, &signature, (unsigned char*)msg, (unsigned char*)seckey, NULL, NULL); - (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); - (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_sign failed"); + result = secp256k1_ecdsa_sign(ctx, &signature, (unsigned char *)msg, (unsigned char *)seckey, NULL, NULL); + (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); + (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_sign failed"); - jsig = (*penv)->NewByteArray(penv, 64); - sig = (*penv)->GetByteArrayElements(penv, jsig, 0); - result = secp256k1_ecdsa_signature_serialize_compact(ctx, (unsigned char*)sig, &signature); - (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_serialize_compact failed"); - return jsig; + jsig = (*penv)->NewByteArray(penv, 64); + sig = (*penv)->GetByteArrayElements(penv, jsig, 0); + result = secp256k1_ecdsa_signature_serialize_compact(ctx, (unsigned char *)sig, &signature); + (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_serialize_compact failed"); + return jsig; } int GetSignatureFormat(size_t size) { - if (size == 64) return SIG_FORMAT_COMPACT; - if (size < 64) return SIG_FORMAT_UNKNOWN; - return SIG_FORMAT_DER; + if (size == 64) + return SIG_FORMAT_COMPACT; + if (size < 64) + return SIG_FORMAT_UNKNOWN; + return SIG_FORMAT_DER; } /* @@ -198,53 +211,57 @@ int GetSignatureFormat(size_t size) * Method: secp256k1_ecdsa_verify * Signature: (J[B[B[B)I */ -JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1verify - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jbyteArray jpubkey) +JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1verify(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jbyteArray jpubkey) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *pub, *msg, *sig; - secp256k1_ecdsa_signature signature; - secp256k1_pubkey pubkey; - size_t sigSize, pubSize; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *pub, *msg, *sig; + secp256k1_ecdsa_signature signature; + secp256k1_pubkey pubkey; + size_t sigSize, pubSize; + int result = 0; - if (jctx == 0) return 0; - if (jsig == NULL) return 0; - if (jmsg == NULL) return 0; - if (jpubkey == NULL) return 0; + if (jctx == 0) + return 0; + if (jsig == NULL) + return 0; + if (jmsg == NULL) + return 0; + if (jpubkey == NULL) + return 0; - sigSize = (*penv)->GetArrayLength(penv, jsig); - int sigFormat = GetSignatureFormat(sigSize); - CHECKRESULT(sigFormat == SIG_FORMAT_UNKNOWN, "invalid signature size"); + sigSize = (*penv)->GetArrayLength(penv, jsig); + int sigFormat = GetSignatureFormat(sigSize); + CHECKRESULT(sigFormat == SIG_FORMAT_UNKNOWN, "invalid signature size"); - pubSize = (*penv)->GetArrayLength(penv, jpubkey); - CHECKRESULT((pubSize != 33) && (pubSize != 65), "invalid public key size"); + pubSize = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT((pubSize != 33) && (pubSize != 65), "invalid public key size"); - CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); + CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); - sig = (*penv)->GetByteArrayElements(penv, jsig, 0); - switch(sigFormat) { - case SIG_FORMAT_COMPACT: - result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature, (unsigned char*)sig); - (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_compact failed"); - break; - case SIG_FORMAT_DER: - result = secp256k1_ecdsa_signature_parse_der(ctx, &signature, (unsigned char*)sig, sigSize); - (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_der failed"); - break; - } + sig = (*penv)->GetByteArrayElements(penv, jsig, 0); + switch (sigFormat) + { + case SIG_FORMAT_COMPACT: + result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature, (unsigned char *)sig); + (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_compact failed"); + break; + case SIG_FORMAT_DER: + result = secp256k1_ecdsa_signature_parse_der(ctx, &signature, (unsigned char *)sig, sigSize); + (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_der failed"); + break; + } - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char*)pub, pubSize); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char *)pub, pubSize); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); - msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); - result = secp256k1_ecdsa_verify(ctx, &signature, (unsigned char*)msg, &pubkey); - (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); - return result; + msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); + result = secp256k1_ecdsa_verify(ctx, &signature, (unsigned char *)msg, &pubkey); + (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); + return result; } /* @@ -252,46 +269,49 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec * Method: secp256k1_ecdsa_signature_normalize * Signature: (J[B[B)I */ -JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1signature_1normalize - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsigin, jbyteArray jsigout) +JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1signature_1normalize(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsigin, jbyteArray jsigout) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *sig; - secp256k1_ecdsa_signature signature_in, signature_out; - size_t size; - int result = 0; - int return_value = 0; - int sigFormat = SIG_FORMAT_UNKNOWN; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *sig; + secp256k1_ecdsa_signature signature_in, signature_out; + size_t size; + int result = 0; + int return_value = 0; + int sigFormat = SIG_FORMAT_UNKNOWN; - if (jctx == 0) return 0; - if (jsigin == NULL) return 0; - if (jsigout == NULL) return 0; + if (jctx == 0) + return 0; + if (jsigin == NULL) + return 0; + if (jsigout == NULL) + return 0; - size = (*penv)->GetArrayLength(penv, jsigin); - sigFormat = GetSignatureFormat(size); - CHECKRESULT(sigFormat == SIG_FORMAT_UNKNOWN, "invalid signature size"); - CHECKRESULT((*penv)->GetArrayLength(penv, jsigout) != 64, "output signature length must be 64 bytes"); + size = (*penv)->GetArrayLength(penv, jsigin); + sigFormat = GetSignatureFormat(size); + CHECKRESULT(sigFormat == SIG_FORMAT_UNKNOWN, "invalid signature size"); + CHECKRESULT((*penv)->GetArrayLength(penv, jsigout) != 64, "output signature length must be 64 bytes"); - sig = (*penv)->GetByteArrayElements(penv, jsigin, 0); - switch(sigFormat) { - case SIG_FORMAT_COMPACT: - result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature_in, (unsigned char*)sig); - (*penv)->ReleaseByteArrayElements(penv, jsigin, sig, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_compact failed"); - break; - case SIG_FORMAT_DER: - result = secp256k1_ecdsa_signature_parse_der(ctx, &signature_in, (unsigned char*)sig, size); - (*penv)->ReleaseByteArrayElements(penv, jsigin, sig, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_der failed"); - break; - } - return_value = secp256k1_ecdsa_signature_normalize(ctx, &signature_out, &signature_in); - sig = (*penv)->GetByteArrayElements(penv, jsigout, 0); - result = secp256k1_ecdsa_signature_serialize_compact(ctx, (unsigned char*)sig, &signature_out); - (*penv)->ReleaseByteArrayElements(penv, jsigout, sig, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_serialize_compact failed"); + sig = (*penv)->GetByteArrayElements(penv, jsigin, 0); + switch (sigFormat) + { + case SIG_FORMAT_COMPACT: + result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature_in, (unsigned char *)sig); + (*penv)->ReleaseByteArrayElements(penv, jsigin, sig, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_compact failed"); + break; + case SIG_FORMAT_DER: + result = secp256k1_ecdsa_signature_parse_der(ctx, &signature_in, (unsigned char *)sig, size); + (*penv)->ReleaseByteArrayElements(penv, jsigin, sig, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_der failed"); + break; + } + return_value = secp256k1_ecdsa_signature_normalize(ctx, &signature_out, &signature_in); + sig = (*penv)->GetByteArrayElements(penv, jsigout, 0); + result = secp256k1_ecdsa_signature_serialize_compact(ctx, (unsigned char *)sig, &signature_out); + (*penv)->ReleaseByteArrayElements(penv, jsigout, sig, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_serialize_compact failed"); - return return_value; + return return_value; } /* @@ -299,21 +319,22 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec * Method: secp256k1_ec_privkey_negate * Signature: (J[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1negate - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1negate(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *seckey; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *seckey; + int result = 0; - if (jctx == 0) return 0; - if (jseckey == NULL) return 0; - CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); - seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); - result = secp256k1_ec_seckey_negate(ctx, (unsigned char*)seckey); - (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); - CHECKRESULT(!result, "secp256k1_ec_seckey_negate failed"); - return jseckey; + if (jctx == 0) + return 0; + if (jseckey == NULL) + return 0; + CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); + seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); + result = secp256k1_ec_seckey_negate(ctx, (unsigned char *)seckey); + (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); + CHECKRESULT(!result, "secp256k1_ec_seckey_negate failed"); + return jseckey; } /* @@ -321,35 +342,36 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_ec_pubkey_negate * Signature: (J[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1negate - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1negate(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *pub; - secp256k1_pubkey pubkey; - size_t size; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *pub; + secp256k1_pubkey pubkey; + size_t size; + int result = 0; - if (jctx == 0) return 0; - if (jpubkey == NULL) return 0; + if (jctx == 0) + return 0; + if (jpubkey == NULL) + return 0; - size = (*penv)->GetArrayLength(penv, jpubkey); - CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char*)pub, size); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); + size = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char *)pub, size); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); - result = secp256k1_ec_pubkey_negate(ctx, &pubkey); - CHECKRESULT(!result, "secp256k1_ec_pubkey_negate failed"); + result = secp256k1_ec_pubkey_negate(ctx, &pubkey); + CHECKRESULT(!result, "secp256k1_ec_pubkey_negate failed"); - size = 65; - jpubkey = (*penv)->NewByteArray(penv, 65); - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char*)pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); - return jpubkey; + size = 65; + jpubkey = (*penv)->NewByteArray(penv, 65); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char *)pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + return jpubkey; } /* @@ -357,26 +379,28 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_ec_privkey_tweak_add * Signature: (J[B[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1tweak_1add - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jtweak) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1tweak_1add(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jtweak) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *seckey, *tweak; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *seckey, *tweak; + int result = 0; - if (jctx == 0) return NULL; - if (jseckey == NULL) return NULL; - if (jtweak == NULL) return NULL; + if (jctx == 0) + return NULL; + if (jseckey == NULL) + return NULL; + if (jtweak == NULL) + return NULL; - CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); - CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); - seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); - tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0); - result = secp256k1_ec_seckey_tweak_add(ctx, (unsigned char*)seckey, (unsigned char*)tweak); - (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); - (*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0); - CHECKRESULT(!result, "secp256k1_ec_seckey_tweak_add failed"); - return jseckey; + CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); + CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); + seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); + tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0); + result = secp256k1_ec_seckey_tweak_add(ctx, (unsigned char *)seckey, (unsigned char *)tweak); + (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); + (*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0); + CHECKRESULT(!result, "secp256k1_ec_seckey_tweak_add failed"); + return jseckey; } /* @@ -384,40 +408,42 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_ec_pubkey_tweak_add * Signature: (J[B[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1tweak_1add - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey, jbyteArray jtweak) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1tweak_1add(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey, jbyteArray jtweak) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *pub, *tweak; - secp256k1_pubkey pubkey; - size_t size; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *pub, *tweak; + secp256k1_pubkey pubkey; + size_t size; + int result = 0; - if (jctx == 0) return NULL; - if (jpubkey == NULL) return NULL; - if (jtweak == NULL) return NULL; + if (jctx == 0) + return NULL; + if (jpubkey == NULL) + return NULL; + if (jtweak == NULL) + return NULL; - size = (*penv)->GetArrayLength(penv, jpubkey); - CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); - CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); + size = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); + CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char*)pub, size); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char *)pub, size); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); - tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0); - result = secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, (unsigned char*)tweak); - (*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_tweak_add failed"); + tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0); + result = secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, (unsigned char *)tweak); + (*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_tweak_add failed"); - size = 65; - jpubkey = (*penv)->NewByteArray(penv, 65); - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char*)pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); - return jpubkey; + size = 65; + jpubkey = (*penv)->NewByteArray(penv, 65); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char *)pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + return jpubkey; } /* @@ -425,26 +451,28 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_ec_privkey_tweak_mul * Signature: (J[B[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1tweak_1mul - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jtweak) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1privkey_1tweak_1mul(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jtweak) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *seckey, *tweak; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *seckey, *tweak; + int result = 0; - if (jctx == 0) return NULL; - if (jseckey == NULL) return NULL; - if (jtweak == NULL) return NULL; + if (jctx == 0) + return NULL; + if (jseckey == NULL) + return NULL; + if (jtweak == NULL) + return NULL; - CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); - CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); - seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); - tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0); - result = secp256k1_ec_seckey_tweak_mul(ctx, (unsigned char*)seckey, (unsigned char*)tweak); - CHECKRESULT(!result, "secp256k1_ec_seckey_tweak_mul failed"); - (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); - (*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0); - return jseckey; + CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); + CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); + seckey = (*penv)->GetByteArrayElements(penv, jseckey, 0); + tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0); + result = secp256k1_ec_seckey_tweak_mul(ctx, (unsigned char *)seckey, (unsigned char *)tweak); + CHECKRESULT(!result, "secp256k1_ec_seckey_tweak_mul failed"); + (*penv)->ReleaseByteArrayElements(penv, jseckey, seckey, 0); + (*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0); + return jseckey; } /* @@ -452,48 +480,52 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_ec_pubkey_tweak_mul * Signature: (J[B[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1tweak_1mul - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey, jbyteArray jtweak) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1tweak_1mul(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpubkey, jbyteArray jtweak) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *pub, *tweak; - secp256k1_pubkey pubkey; - size_t size; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *pub, *tweak; + secp256k1_pubkey pubkey; + size_t size; + int result = 0; - if (jctx == 0) return NULL; - if (jpubkey == NULL) return NULL; - if (jtweak == NULL) return NULL; + if (jctx == 0) + return NULL; + if (jpubkey == NULL) + return NULL; + if (jtweak == NULL) + return NULL; - size = (*penv)->GetArrayLength(penv, jpubkey); - CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); - CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char*)pub, size); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); + size = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); + CHECKRESULT((*penv)->GetArrayLength(penv, jtweak) != 32, "tweak must be 32 bytes"); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char *)pub, size); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); - tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0); - result = secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, (unsigned char*)tweak); - (*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_tweak_mul failed"); + tweak = (*penv)->GetByteArrayElements(penv, jtweak, 0); + result = secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, (unsigned char *)tweak); + (*penv)->ReleaseByteArrayElements(penv, jtweak, tweak, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_tweak_mul failed"); - size = 65; - jpubkey = (*penv)->NewByteArray(penv, 65); - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char*)pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); - return jpubkey; + size = 65; + jpubkey = (*penv)->NewByteArray(penv, 65); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char *)pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + return jpubkey; } void free_pubkeys(secp256k1_pubkey **pubkeys, size_t count) { - size_t i; - for(i = 0; i < count; i++) { - if (pubkeys[i] != NULL) free(pubkeys[i]); - } - free(pubkeys); + size_t i; + for (i = 0; i < count; i++) + { + if (pubkeys[i] != NULL) + free(pubkeys[i]); + } + free(pubkeys); } /* @@ -501,46 +533,48 @@ void free_pubkeys(secp256k1_pubkey **pubkeys, size_t count) * Method: secp256k1_ec_pubkey_combine * Signature: (J[[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1combine - (JNIEnv *penv, jclass clazz, jlong jctx, jobjectArray jpubkeys) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ec_1pubkey_1combine(JNIEnv *penv, jclass clazz, jlong jctx, jobjectArray jpubkeys) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *pub; - secp256k1_pubkey **pubkeys; - secp256k1_pubkey combined; - jbyteArray jpubkey; - size_t size, count; - size_t i; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *pub; + secp256k1_pubkey **pubkeys; + secp256k1_pubkey combined; + jbyteArray jpubkey; + size_t size, count; + size_t i; + int result = 0; - if (jctx == 0) return NULL; - if (jpubkeys == NULL) return NULL; + if (jctx == 0) + return NULL; + if (jpubkeys == NULL) + return NULL; count = (*penv)->GetArrayLength(penv, jpubkeys); CHECKRESULT(count < 1, "pubkey array cannot be empty") - pubkeys = calloc(count, sizeof(secp256k1_pubkey*)); + pubkeys = calloc(count, sizeof(secp256k1_pubkey *)); - for(i = 0; i < count; i++) { - pubkeys[i] = calloc(1, sizeof(secp256k1_pubkey)); - jpubkey = (jbyteArray) (*penv)->GetObjectArrayElement(penv, jpubkeys, i); - size = (*penv)->GetArrayLength(penv, jpubkey); - CHECKRESULT1((size != 33) && (size != 65), "invalid public key size", free_pubkeys(pubkeys, count)); - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_parse(ctx, pubkeys[i], (unsigned char*)pub, size); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT1(!result, "secp256k1_ec_pubkey_parse failed", free_pubkeys(pubkeys, count)); - } - result = secp256k1_ec_pubkey_combine(ctx, &combined, (const secp256k1_pubkey * const *)pubkeys, count); - free_pubkeys(pubkeys, count); - CHECKRESULT(!result, "secp256k1_ec_pubkey_combine failed"); - - size = 65; - jpubkey = (*penv)->NewByteArray(penv, 65); + for (i = 0; i < count; i++) + { + pubkeys[i] = calloc(1, sizeof(secp256k1_pubkey)); + jpubkey = (jbyteArray)(*penv)->GetObjectArrayElement(penv, jpubkeys, i); + size = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT1((size != 33) && (size != 65), "invalid public key size", free_pubkeys(pubkeys, count)); pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char*)pub, &size, &combined, SECP256K1_EC_UNCOMPRESSED); + result = secp256k1_ec_pubkey_parse(ctx, pubkeys[i], (unsigned char *)pub, size); (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); - return jpubkey; + CHECKRESULT1(!result, "secp256k1_ec_pubkey_parse failed", free_pubkeys(pubkeys, count)); + } + result = secp256k1_ec_pubkey_combine(ctx, &combined, (const secp256k1_pubkey *const *)pubkeys, count); + free_pubkeys(pubkeys, count); + CHECKRESULT(!result, "secp256k1_ec_pubkey_combine failed"); + + size = 65; + jpubkey = (*penv)->NewByteArray(penv, 65); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char *)pub, &size, &combined, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + return jpubkey; } /* @@ -548,36 +582,38 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_ecdh * Signature: (J[B[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdh - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jpubkey) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdh(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jseckey, jbyteArray jpubkey) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte* seckeyBytes, *pubkeyBytes, *output; - secp256k1_pubkey pubkey; - jbyteArray joutput; - size_t size; - int result; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *seckeyBytes, *pubkeyBytes, *output; + secp256k1_pubkey pubkey; + jbyteArray joutput; + size_t size; + int result; - if (jctx == 0) return NULL; - if (jseckey == NULL) return NULL; - if (jpubkey == NULL) return NULL; + if (jctx == 0) + return NULL; + if (jseckey == NULL) + return NULL; + if (jpubkey == NULL) + return NULL; - CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "invalid private key size"); + CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "invalid private key size"); - size = (*penv)->GetArrayLength(penv, jpubkey); - CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); - pubkeyBytes = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char*)pubkeyBytes, size); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkeyBytes, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); + size = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT((size != 33) && (size != 65), "invalid public key size"); + pubkeyBytes = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, &pubkey, (unsigned char *)pubkeyBytes, size); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pubkeyBytes, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); - seckeyBytes = (*penv)->GetByteArrayElements(penv, jseckey, 0); - joutput = (*penv)->NewByteArray(penv, 32); - output = (*penv)->GetByteArrayElements(penv, joutput, 0); - result = secp256k1_ecdh(ctx, (unsigned char*)output, &pubkey, (unsigned char*)seckeyBytes, NULL, NULL); - (*penv)->ReleaseByteArrayElements(penv, joutput, output, 0); - (*penv)->ReleaseByteArrayElements(penv, jseckey, seckeyBytes, 0); - return joutput; + seckeyBytes = (*penv)->GetByteArrayElements(penv, jseckey, 0); + joutput = (*penv)->NewByteArray(penv, 32); + output = (*penv)->GetByteArrayElements(penv, joutput, 0); + result = secp256k1_ecdh(ctx, (unsigned char *)output, &pubkey, (unsigned char *)seckeyBytes, NULL, NULL); + (*penv)->ReleaseByteArrayElements(penv, joutput, output, 0); + (*penv)->ReleaseByteArrayElements(penv, jseckey, seckeyBytes, 0); + return joutput; } /* @@ -585,57 +621,60 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_ecdsa_recover * Signature: (J[B[BI)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1recover - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jint recid) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1ecdsa_1recover(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jint recid) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte* sig, *msg, *pub; - jbyteArray jpubkey; - secp256k1_pubkey pubkey; - secp256k1_ecdsa_recoverable_signature signature; - secp256k1_ecdsa_signature dummy; - unsigned char dummyBytes[64]; - size_t sigSize, size; - int result; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *sig, *msg, *pub; + jbyteArray jpubkey; + secp256k1_pubkey pubkey; + secp256k1_ecdsa_recoverable_signature signature; + secp256k1_ecdsa_signature dummy; + unsigned char dummyBytes[64]; + size_t sigSize, size; + int result; - if (jctx == 0) return NULL; - if (jsig == NULL) return NULL; - if (jmsg == NULL) return NULL; - CHECKRESULT(recid < 0 || recid > 3, "invalid recovery id"); + if (jctx == 0) + return NULL; + if (jsig == NULL) + return NULL; + if (jmsg == NULL) + return NULL; + CHECKRESULT(recid < 0 || recid > 3, "invalid recovery id"); - sigSize = (*penv)->GetArrayLength(penv, jsig); - int sigFormat = GetSignatureFormat(sigSize); - CHECKRESULT(sigFormat == SIG_FORMAT_UNKNOWN, "invalid signature size"); - CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); - sig = (*penv)->GetByteArrayElements(penv, jsig, 0); - switch(sigFormat) { - case SIG_FORMAT_COMPACT: - result = secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &signature, (unsigned char*)sig, recid); - (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_recoverable_signature_parse_compact failed"); - break; - case SIG_FORMAT_DER: - result = secp256k1_ecdsa_signature_parse_der(ctx, &dummy, (unsigned char*)sig, sigSize); - (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_der failed"); - result = secp256k1_ecdsa_signature_serialize_compact(ctx, dummyBytes, &dummy); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_serialize_compact failed"); - result = secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &signature, dummyBytes, recid); - CHECKRESULT(!result, "secp256k1_ecdsa_recoverable_signature_parse_compact failed"); - break; - } - msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); - result = secp256k1_ecdsa_recover(ctx, &pubkey, &signature, (unsigned char*)msg); - (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_recover failed"); + sigSize = (*penv)->GetArrayLength(penv, jsig); + int sigFormat = GetSignatureFormat(sigSize); + CHECKRESULT(sigFormat == SIG_FORMAT_UNKNOWN, "invalid signature size"); + CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); + sig = (*penv)->GetByteArrayElements(penv, jsig, 0); + switch (sigFormat) + { + case SIG_FORMAT_COMPACT: + result = secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &signature, (unsigned char *)sig, recid); + (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_recoverable_signature_parse_compact failed"); + break; + case SIG_FORMAT_DER: + result = secp256k1_ecdsa_signature_parse_der(ctx, &dummy, (unsigned char *)sig, sigSize); + (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_der failed"); + result = secp256k1_ecdsa_signature_serialize_compact(ctx, dummyBytes, &dummy); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_serialize_compact failed"); + result = secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, &signature, dummyBytes, recid); + CHECKRESULT(!result, "secp256k1_ecdsa_recoverable_signature_parse_compact failed"); + break; + } + msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); + result = secp256k1_ecdsa_recover(ctx, &pubkey, &signature, (unsigned char *)msg); + (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_recover failed"); - size = 65; - jpubkey = (*penv)->NewByteArray(penv, 65); - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char*)pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); - return jpubkey; + size = 65; + jpubkey = (*penv)->NewByteArray(penv, 65); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_serialize(ctx, (unsigned char *)pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + return jpubkey; } /* @@ -643,34 +682,35 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_compact_to_der * Signature: (J[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1compact_1to_1der - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1compact_1to_1der(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *sig; - secp256k1_ecdsa_signature signature;; - unsigned char der[73]; - size_t size; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *sig; + secp256k1_ecdsa_signature signature; + unsigned char der[73]; + size_t size; + int result = 0; - if (jctx == 0) return 0; - if (jsig == NULL) return 0; - CHECKRESULT((*penv)->GetArrayLength(penv, jsig) != 64, "invalid signature size"); + if (jctx == 0) + return 0; + if (jsig == NULL) + return 0; + CHECKRESULT((*penv)->GetArrayLength(penv, jsig) != 64, "invalid signature size"); - size = (*penv)->GetArrayLength(penv, jsig); - sig = (*penv)->GetByteArrayElements(penv, jsig, 0); - result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature, (unsigned char*)sig); - (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_compact failed"); + size = (*penv)->GetArrayLength(penv, jsig); + sig = (*penv)->GetByteArrayElements(penv, jsig, 0); + result = secp256k1_ecdsa_signature_parse_compact(ctx, &signature, (unsigned char *)sig); + (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_parse_compact failed"); - size = 73; - result = secp256k1_ecdsa_signature_serialize_der(ctx, der, &size, &signature); - CHECKRESULT(!result, "secp256k1_ecdsa_signature_serialize_der failed"); - jsig = (*penv)->NewByteArray(penv, size); - sig = (*penv)->GetByteArrayElements(penv, jsig, 0); - memcpy(sig, der, size); - (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); - return jsig; + size = 73; + result = secp256k1_ecdsa_signature_serialize_der(ctx, der, &size, &signature); + CHECKRESULT(!result, "secp256k1_ecdsa_signature_serialize_der failed"); + jsig = (*penv)->NewByteArray(penv, size); + sig = (*penv)->GetByteArrayElements(penv, jsig, 0); + memcpy(sig, der, size); + (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); + return jsig; } /* @@ -678,47 +718,52 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_schnorrsig_sign * Signature: (J[B[B[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1sign - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jmsg, jbyteArray jseckey, jbyteArray jauxrand32) +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1sign(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jmsg, jbyteArray jseckey, jbyteArray jauxrand32) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *seckey, *msg, *sig, *auxrand32 = NULL; - secp256k1_keypair keypair; - unsigned char signature[64]; - int result = 0; - jbyteArray jsig; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *seckey, *msg, *sig, *auxrand32 = NULL; + secp256k1_keypair keypair; + unsigned char signature[64]; + int result = 0; + jbyteArray jsig; - if (jctx == 0) return NULL; - if (jmsg == NULL) return NULL; - if (jseckey == NULL) return NULL; + if (jctx == 0) + return NULL; + if (jmsg == NULL) + return NULL; + if (jseckey == NULL) + return NULL; - CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); - CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); - if (jauxrand32 != 0) { - CHECKRESULT((*penv)->GetArrayLength(penv, jauxrand32) != 32, "auxiliary random data must be 32 bytes"); - } - 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"); + CHECKRESULT((*penv)->GetArrayLength(penv, jseckey) != 32, "secret key must be 32 bytes"); + CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); + if (jauxrand32 != 0) + { + CHECKRESULT((*penv)->GetArrayLength(penv, jauxrand32) != 32, "auxiliary random data must be 32 bytes"); + } + 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"); - msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); - if (jauxrand32 != 0) { - auxrand32 = (*penv)->GetByteArrayElements(penv, jauxrand32, 0); - } + msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); + if (jauxrand32 != 0) + { + auxrand32 = (*penv)->GetByteArrayElements(penv, jauxrand32, 0); + } - result = secp256k1_schnorrsig_sign32(ctx, signature, (unsigned char*)msg, &keypair, auxrand32); - (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); - if (auxrand32 != 0) { - (*penv)->ReleaseByteArrayElements(penv, jauxrand32, auxrand32, 0); - } - CHECKRESULT(!result, "secp256k1_schnorrsig_sign failed"); + result = secp256k1_schnorrsig_sign32(ctx, signature, (unsigned char *)msg, &keypair, auxrand32); + (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); + if (auxrand32 != 0) + { + (*penv)->ReleaseByteArrayElements(penv, jauxrand32, auxrand32, 0); + } + CHECKRESULT(!result, "secp256k1_schnorrsig_sign failed"); - jsig = (*penv)->NewByteArray(penv, 64); - sig = (*penv)->GetByteArrayElements(penv, jsig, 0); - memcpy(sig, signature, 64); - (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); - return jsig; + jsig = (*penv)->NewByteArray(penv, 64); + sig = (*penv)->GetByteArrayElements(penv, jsig, 0); + memcpy(sig, signature, 64); + (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); + return jsig; } /* @@ -726,32 +771,568 @@ JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256 * Method: secp256k1_schnorrsig_verify * Signature: (J[B[B[B)I */ -JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1verify - (JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jbyteArray jpubkey) +JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1schnorrsig_1verify(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsig, jbyteArray jmsg, jbyteArray jpubkey) { - secp256k1_context* ctx = (secp256k1_context *)jctx; - jbyte *pub, *msg, *sig; - secp256k1_xonly_pubkey pubkey; - int result = 0; + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *pub, *msg, *sig; + secp256k1_xonly_pubkey pubkey; + int result = 0; - if (jctx == 0) return 0; - if (jsig == NULL) return 0; - if (jmsg == NULL) return 0; - if (jpubkey == NULL) return 0; + if (jctx == 0) + return 0; + if (jsig == NULL) + return 0; + if (jmsg == NULL) + return 0; + if (jpubkey == NULL) + return 0; - CHECKRESULT((*penv)->GetArrayLength(penv, jsig) != 64, "signature must be 64 bytes"); - CHECKRESULT((*penv)->GetArrayLength(penv, jpubkey) != 32, "public key must be 32 bytes"); - CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); + CHECKRESULT((*penv)->GetArrayLength(penv, jsig) != 64, "signature must be 64 bytes"); + CHECKRESULT((*penv)->GetArrayLength(penv, jpubkey) != 32, "public key must be 32 bytes"); + CHECKRESULT((*penv)->GetArrayLength(penv, jmsg) != 32, "message must be 32 bytes"); - pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); - result = secp256k1_xonly_pubkey_parse(ctx, &pubkey, (unsigned char*)pub); - (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_xonly_pubkey_parse(ctx, &pubkey, (unsigned char *)pub); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); - sig = (*penv)->GetByteArrayElements(penv, jsig, 0); - msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); - result = secp256k1_schnorrsig_verify(ctx, (unsigned char*)sig, (unsigned char*)msg, 32, &pubkey); - (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); - (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); - return result; + sig = (*penv)->GetByteArrayElements(penv, jsig, 0); + msg = (*penv)->GetByteArrayElements(penv, jmsg, 0); + result = secp256k1_schnorrsig_verify(ctx, (unsigned char *)sig, (unsigned char *)msg, 32, &pubkey); + (*penv)->ReleaseByteArrayElements(penv, jsig, sig, 0); + (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); + return result; +} + +static void copy_bytes_from_java(JNIEnv *penv, jbyteArray source, size_t size, unsigned char *dest) +{ + jbyte *ptr = NULL; + if (source == NULL) + return; // nothing to do + ptr = (*penv)->GetByteArrayElements(penv, source, 0); + memcpy(dest, ptr, size); + (*penv)->ReleaseByteArrayElements(penv, source, ptr, 0); +} + +static void copy_bytes_to_java(JNIEnv *penv, jbyteArray dest, size_t size, unsigned char *source) +{ + jbyte *ptr = (*penv)->GetByteArrayElements(penv, dest, 0); + memcpy(ptr, source, size); + (*penv)->ReleaseByteArrayElements(penv, dest, ptr, 0); +} + +// session_id32: ByteArray, seckey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyagg_cache: ByteArray?, extra_input32: ByteArray? +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_nonce_gen + * Signature: (J[B[B[B[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1gen(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsession_id32, 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; + unsigned char session_id32[32]; + 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 (jsession_id32 == 0) + return NULL; + size = (*penv)->GetArrayLength(penv, jsession_id32); + CHECKRESULT(size != 32, "invalid session_id size"); + copy_bytes_from_java(penv, jsession_id32, size, session_id32); + + if (jseckey != 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(ctx, &secnonce, &pubnonce, session_id32, + jseckey == NULL ? NULL : 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; + for (i = 0; i < count; i++) + { + if (nonces[i] != NULL) + free(nonces[i]); + } + free(nonces); +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_nonce_agg + * Signature: (J[[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1agg(JNIEnv *penv, jclass clazz, jlong jctx, jobjectArray jnonces) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *in66; + secp256k1_musig_pubnonce **pubnonces; + secp256k1_musig_aggnonce combined; + jbyteArray jnonce; + size_t size, count; + size_t i; + int result = 0; + + if (jctx == 0) + return NULL; + if (jnonces == NULL) + return NULL; + + count = (*penv)->GetArrayLength(penv, jnonces); + CHECKRESULT(count <= 0, "public nonces count cannot be 0"); + + pubnonces = calloc(count, sizeof(secp256k1_musig_pubnonce *)); + + for (i = 0; i < count; i++) + { + pubnonces[i] = calloc(1, sizeof(secp256k1_musig_pubnonce)); + jnonce = (jbyteArray)(*penv)->GetObjectArrayElement(penv, jnonces, i); + size = (*penv)->GetArrayLength(penv, jnonce); + CHECKRESULT1(size != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE, "invalid public nonce size", free_nonces(pubnonces, count)); + in66 = (*penv)->GetByteArrayElements(penv, jnonce, 0); + result = secp256k1_musig_pubnonce_parse(ctx, pubnonces[i], (unsigned char *)in66); + (*penv)->ReleaseByteArrayElements(penv, jnonce, in66, 0); + CHECKRESULT1(!result, "secp256k1_musig_pubnonce_parse failed", free_nonces(pubnonces, count)); + } + result = secp256k1_musig_nonce_agg(ctx, &combined, (const secp256k1_musig_pubnonce *const *)pubnonces, count); + free_nonces(pubnonces, count); + CHECKRESULT(!result, "secp256k1_musig_nonce_agg failed"); + + jnonce = (*penv)->NewByteArray(penv, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE); + in66 = (*penv)->GetByteArrayElements(penv, jnonce, 0); + result = secp256k1_musig_aggnonce_serialize(ctx, (unsigned char *)in66, &combined); + (*penv)->ReleaseByteArrayElements(penv, jnonce, in66, 0); + CHECKRESULT(!result, "secp256k1_musig_aggnonce_serialize failed"); + return jnonce; +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_pubkey_agg + * Signature: (J[[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1agg(JNIEnv *penv, jclass clazz, jlong jctx, jobjectArray jpubkeys, jbyteArray jkeyaggcache) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *pub; + secp256k1_pubkey **pubkeys; + secp256k1_xonly_pubkey combined; + secp256k1_musig_keyagg_cache keyaggcache; + jbyteArray jpubkey; + size_t size, count; + size_t i; + int result = 0; + + if (jctx == 0) + return NULL; + if (jpubkeys == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jpubkeys) <= 0, "pubkeys count cannot be 0"); + + 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); + } + + count = (*penv)->GetArrayLength(penv, jpubkeys); + pubkeys = calloc(count, sizeof(secp256k1_pubkey *)); + + for (i = 0; i < count; i++) + { + pubkeys[i] = calloc(1, sizeof(secp256k1_pubkey)); + jpubkey = (jbyteArray)(*penv)->GetObjectArrayElement(penv, jpubkeys, i); + size = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT1((size != 33) && (size != 65), "invalid public key size", free_pubkeys(pubkeys, count)); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, pubkeys[i], (unsigned char *)pub, size); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT1(!result, "secp256k1_ec_pubkey_parse failed", free_pubkeys(pubkeys, count)); + } + result = secp256k1_musig_pubkey_agg(ctx, &combined, jkeyaggcache == NULL ? NULL : &keyaggcache, (const secp256k1_pubkey *const *)pubkeys, count); + free_pubkeys(pubkeys, count); + CHECKRESULT(!result, "secp256k1_musig_pubkey_agg failed"); + + jpubkey = (*penv)->NewByteArray(penv, 32); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_xonly_pubkey_serialize(ctx, (unsigned char *)pub, &combined); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_xonly_pubkey_serialize failed"); + + if (jkeyaggcache != NULL) + { + pub = (*penv)->GetByteArrayElements(penv, jkeyaggcache, 0); + memcpy(pub, keyaggcache.data, sizeof(secp256k1_musig_keyagg_cache)); + (*penv)->ReleaseByteArrayElements(penv, jkeyaggcache, pub, 0); + } + return jpubkey; +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_pubkey_ec_tweak_add + * Signature: (J[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1ec_1tweak_1add(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jkeyaggcache, jbyteArray jtweak32) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *tweak32, *pub; + secp256k1_pubkey pubkey; + secp256k1_musig_keyagg_cache keyaggcache; + jbyteArray jpubkey; + size_t size; + int result = 0; + + if (jctx == 0) + return NULL; + if (jkeyaggcache == NULL) + return 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 (jtweak32 == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jtweak32) != 32, "tweak must be 32 bytes"); + tweak32 = (*penv)->GetByteArrayElements(penv, jtweak32, 0); + + result = secp256k1_musig_pubkey_ec_tweak_add(ctx, &pubkey, &keyaggcache, tweak32); + (*penv)->ReleaseByteArrayElements(penv, jtweak32, tweak32, 0); + CHECKRESULT(!result, "secp256k1_musig_pubkey_ec_tweak_add failed"); + + jpubkey = (*penv)->NewByteArray(penv, 65); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + size = 65; + result = secp256k1_ec_pubkey_serialize(ctx, pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + + pub = (*penv)->GetByteArrayElements(penv, jkeyaggcache, 0); + memcpy(pub, keyaggcache.data, sizeof(secp256k1_musig_keyagg_cache)); + (*penv)->ReleaseByteArrayElements(penv, jkeyaggcache, pub, 0); + + return jpubkey; +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_pubkey_xonly_tweak_add + * Signature: (J[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1xonly_1tweak_1add(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jkeyaggcache, jbyteArray jtweak32) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *tweak32, *pub; + secp256k1_pubkey pubkey; + secp256k1_musig_keyagg_cache keyaggcache; + jbyteArray jpubkey; + size_t size; + int result = 0; + + if (jctx == 0) + return NULL; + if (jkeyaggcache == NULL) + return 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 (jtweak32 == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jtweak32) != 32, "tweak must be 32 bytes"); + tweak32 = (*penv)->GetByteArrayElements(penv, jtweak32, 0); + + result = secp256k1_musig_pubkey_xonly_tweak_add(ctx, &pubkey, &keyaggcache, tweak32); + (*penv)->ReleaseByteArrayElements(penv, jtweak32, tweak32, 0); + CHECKRESULT(!result, "secp256k1_musig_pubkey_xonly_tweak_add failed"); + + jpubkey = (*penv)->NewByteArray(penv, 65); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + size = 65; + result = secp256k1_ec_pubkey_serialize(ctx, pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + + pub = (*penv)->GetByteArrayElements(penv, jkeyaggcache, 0); + memcpy(pub, keyaggcache.data, sizeof(secp256k1_musig_keyagg_cache)); + (*penv)->ReleaseByteArrayElements(penv, jkeyaggcache, pub, 0); + + return jpubkey; +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_nonce_process + * Signature: (J[B[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1process(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jaggnonce, jbyteArray jmsg32, jbyteArray jkeyaggcache) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + secp256k1_musig_keyagg_cache keyaggcache; + secp256k1_musig_aggnonce aggnonce; + secp256k1_musig_session session; + unsigned char msg32[32]; + jbyteArray jsession; + jbyte *ptr; + size_t size; + int result = 0; + + if (jctx == 0) + return NULL; + if (jaggnonce == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jaggnonce) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE, "invalid nonce size"); + if (jmsg32 == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jmsg32) != 32, "invalid message size"); + if (jkeyaggcache == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jkeyaggcache) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE, "invalid keyagg cache size"); + + ptr = (*penv)->GetByteArrayElements(penv, jaggnonce, 0); + result = secp256k1_musig_aggnonce_parse(ctx, &aggnonce, ptr); + (*penv)->ReleaseByteArrayElements(penv, jaggnonce, ptr, 0); + CHECKRESULT(!result, "secp256k1_musig_aggnonce_parse failed"); + + copy_bytes_from_java(penv, jmsg32, 32, msg32); + copy_bytes_from_java(penv, jkeyaggcache, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE, keyaggcache.data); + + result = secp256k1_musig_nonce_process(ctx, &session, &aggnonce, msg32, &keyaggcache); + CHECKRESULT(!result, "secp256k1_musig_nonce_process failed"); + + jsession = (*penv)->NewByteArray(penv, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE); + copy_bytes_to_java(penv, jsession, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE, session.data); + return jsession; +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_partial_sign + * Signature: (J[B[B[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1partial_1sign(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsecnonce, jbyteArray jprivkey, jbyteArray jkeyaggcache, jbyteArray jsession) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + secp256k1_musig_partial_sig psig; + secp256k1_musig_secnonce secnonce; + unsigned char seckey[32]; + secp256k1_keypair keypair; + secp256k1_musig_keyagg_cache keyaggcache; + secp256k1_musig_session session; + jbyteArray jpsig; + jbyte *ptr; + int result = 0; + + if (jctx == 0) + return NULL; + if (jsecnonce == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jsecnonce) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE, "invalid secret nonce size"); + if (jprivkey == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jprivkey) != 32, "invalid private key size"); + if (jkeyaggcache == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jkeyaggcache) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE, "invalid cache size"); + if (jsession == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jsession) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE, "invalid session size"); + + copy_bytes_from_java(penv, jsecnonce, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE, secnonce.data); + + copy_bytes_from_java(penv, jprivkey, 32, seckey); + result = secp256k1_keypair_create(ctx, &keypair, seckey); + CHECKRESULT(!result, "secp256k1_keypair_create failed"); + + copy_bytes_from_java(penv, jkeyaggcache, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE, keyaggcache.data); + copy_bytes_from_java(penv, jsession, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE, session.data); + + result = secp256k1_musig_partial_sign(ctx, &psig, &secnonce, &keypair, &keyaggcache, &session); + CHECKRESULT(!result, "secp256k1_musig_partial_sign failed"); + + result = secp256k1_musig_partial_sig_serialize(ctx, seckey, &psig); + CHECKRESULT(!result, "secp256k1_musig_partial_sig_serialize failed"); + + jpsig = (*penv)->NewByteArray(penv, 32); + copy_bytes_to_java(penv, jpsig, 32, seckey); + return jpsig; +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_partial_sig_verify + * Signature: (J[B[B[B[B[B)I + */ +JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1partial_1sig_1verify(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jpsig, jbyteArray jpubnonce, jbyteArray jpubkey, jbyteArray jkeyaggcache, jbyteArray jsession) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + secp256k1_musig_partial_sig psig; + secp256k1_musig_pubnonce pubnonce; + secp256k1_pubkey pubkey; + secp256k1_musig_keyagg_cache keyaggcache; + secp256k1_musig_session session; + jbyte *ptr; + int result = 0; + + if (jctx == 0) + return 0; + if (jpsig == NULL) + return 0; + CHECKRESULT((*penv)->GetArrayLength(penv, jpsig) != 32, "invalid partial signature size"); + if (jpubnonce == NULL) + return 0; + CHECKRESULT((*penv)->GetArrayLength(penv, jpubnonce) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE, "invalid public nonce size"); + if (jpubkey == NULL) + return 0; + CHECKRESULT(((*penv)->GetArrayLength(penv, jpubkey) != 33) && ((*penv)->GetArrayLength(penv, jpubkey) != 65), "invalid public key size"); + if (jkeyaggcache == NULL) + return 0; + CHECKRESULT((*penv)->GetArrayLength(penv, jkeyaggcache) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE, "invalid cache size"); + if (jsession == NULL) + return 0; + CHECKRESULT((*penv)->GetArrayLength(penv, jsession) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE, "invalid session size"); + + ptr = (*penv)->GetByteArrayElements(penv, jpsig, 0); + result = secp256k1_musig_partial_sig_parse(ctx, &psig, ptr); + (*penv)->ReleaseByteArrayElements(penv, jpsig, ptr, 0); + CHECKRESULT(!result, "secp256k1_musig_partial_sig_parse failed"); + + ptr = (*penv)->GetByteArrayElements(penv, jpubnonce, 0); + result = secp256k1_musig_pubnonce_parse(ctx, &pubnonce, ptr); + (*penv)->ReleaseByteArrayElements(penv, jpubnonce, ptr, 0); + CHECKRESULT(!result, "secp256k1_musig_pubnonce_parse failed"); + + ptr = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, &pubkey, ptr, (*penv)->GetArrayLength(penv, jpubkey)); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, ptr, 0); + CHECKRESULT(!result, "secp256k1_musig_pubkey_parse failed"); + + copy_bytes_from_java(penv, jkeyaggcache, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_KEYAGG_CACHE_SIZE, keyaggcache.data); + copy_bytes_from_java(penv, jsession, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE, session.data); + + result = secp256k1_musig_partial_sig_verify(ctx, &psig, &pubnonce, &pubkey, &keyaggcache, &session); + return result; +} + +void free_partial_sigs(secp256k1_musig_partial_sig **psigs, size_t count) +{ + size_t i; + for (i = 0; i < count; i++) + { + if (psigs[i] != NULL) + free(psigs[i]); + } + free(psigs); +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_partial_sig_agg + * Signature: (J[B[[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1partial_1sig_1agg(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsession, jobjectArray jpsigs) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + secp256k1_musig_session session; + secp256k1_musig_partial_sig **psigs; + unsigned char sig64[64]; + secp256k1_musig_keyagg_cache keyaggcache; + jbyteArray jpsig; + jbyte *ptr; + size_t size, count; + size_t i; + int result = 0; + + if (jctx == 0) + return NULL; + if (jsession == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jsession) != fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE, "invalid session size"); + copy_bytes_from_java(penv, jsession, fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SESSION_SIZE, session.data); + if (jpsigs == NULL) + return NULL; + + count = (*penv)->GetArrayLength(penv, jpsigs); + CHECKRESULT(count <= 0, "partial sigs count cannot be 0"); + + psigs = calloc(count, sizeof(secp256k1_musig_partial_sig *)); + + for (i = 0; i < count; i++) + { + psigs[i] = calloc(1, sizeof(secp256k1_musig_partial_sig)); + jpsig = (jbyteArray)(*penv)->GetObjectArrayElement(penv, jpsigs, i); + size = (*penv)->GetArrayLength(penv, jpsig); + CHECKRESULT1(size != 32, "invalid partial signature size", free_partial_sigs(psigs, count)); + ptr = (*penv)->GetByteArrayElements(penv, jpsig, 0); + result = secp256k1_musig_partial_sig_parse(ctx, psigs[i], (unsigned char *)ptr); + (*penv)->ReleaseByteArrayElements(penv, jpsig, ptr, 0); + CHECKRESULT1(!result, "secp256k1_musig_partial_sig_parse failed", free_partial_sigs(psigs, count)); + } + result = secp256k1_musig_partial_sig_agg(ctx, sig64, &session, (const secp256k1_musig_partial_sig *const *)psigs, count); + free_partial_sigs(psigs, count); + CHECKRESULT(!result, "secp256k1_musig_pubkey_agg failed"); + + jpsig = (*penv)->NewByteArray(penv, 64); + copy_bytes_to_java(penv, jpsig, 64, sig64); + return jpsig; } diff --git a/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java b/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java index d82a294..fd49e3e 100644 --- a/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java +++ b/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java @@ -29,12 +29,32 @@ public class Secp256k1CFunctions { public static final int SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION); public static final int SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION); + /** + * A musig2 public nonce is simply two elliptic curve points. + */ + public static final int SECP256K1_MUSIG_PUBLIC_NONCE_SIZE = 66; + + /** + * A musig2 private nonce is basically two scalars, but should be treated as an opaque blob. + */ + public static final int SECP256K1_MUSIG_SECRET_NONCE_SIZE = 132; + + /** + * When aggregating public keys, we cache information in an opaque blob (must not be interpreted). + */ + public static final int SECP256K1_MUSIG_KEYAGG_CACHE_SIZE = 197; + + /** + * When creating partial signatures and aggregating them, session data is kept in an opaque blob (must not be interpreted). + */ + public static final int SECP256K1_MUSIG_SESSION_SIZE = 133; + public static native long secp256k1_context_create(int flags); public static native void secp256k1_context_destroy(long ctx); public static native int secp256k1_ec_seckey_verify(long ctx, byte[] seckey); - + public static native byte[] secp256k1_ec_pubkey_parse(long ctx, byte[] pubkey); public static native byte[] secp256k1_ec_pubkey_create(long ctx, byte[] seckey); @@ -68,4 +88,22 @@ public class Secp256k1CFunctions { public static native byte[] secp256k1_schnorrsig_sign(long ctx, byte[] msg, byte[] seckey, byte[] aux_rand32); 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_agg(long ctx, byte[][] nonces); + + public static native byte[] secp256k1_musig_pubkey_agg(long ctx, byte[][] pubkeys, byte[] keyagg_cache); + + public static native byte[] secp256k1_musig_pubkey_ec_tweak_add(long ctx, byte[] keyagg_cache, byte[] tweak32); + + public static native byte[] secp256k1_musig_pubkey_xonly_tweak_add(long ctx, byte[] keyagg_cache, byte[] tweak32); + + public static native byte[] secp256k1_musig_nonce_process(long ctx, byte[] aggnonce, byte[] msg32, byte[] keyagg_cache); + + public static native byte[] secp256k1_musig_partial_sign(long ctx, byte[] secnonce, byte[] privkey, byte[] keyagg_cache, byte[] session); + + public static native int secp256k1_musig_partial_sig_verify(long ctx, byte[] psig, byte[] pubnonce, byte[] pubkey, byte[] keyagg_cache, byte[] session); + + public static native byte[] secp256k1_musig_partial_sig_agg(long ctx, byte[] session, byte[][] psigs); } diff --git a/jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt b/jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt index c456653..20ca735 100644 --- a/jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt +++ b/jni/src/main/kotlin/fr/acinq/secp256k1/NativeSecp256k1.kt @@ -92,6 +92,42 @@ 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 musigNonceAgg(pubnonces: Array): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_nonce_agg(Secp256k1Context.getContext(), pubnonces) + } + + override fun musigPubkeyAgg(pubkeys: Array, keyaggCache: ByteArray?): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_pubkey_agg(Secp256k1Context.getContext(), pubkeys, keyaggCache) + } + + override fun musigPubkeyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_pubkey_ec_tweak_add(Secp256k1Context.getContext(), keyaggCache, tweak32) + } + + override fun musigPubkeyXonlyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_pubkey_xonly_tweak_add(Secp256k1Context.getContext(), keyaggCache, tweak32) + } + + override fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_nonce_process(Secp256k1Context.getContext(), aggnonce, msg32, keyaggCache) + } + + override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_partial_sign(Secp256k1Context.getContext(), secnonce, privkey, keyaggCache, session) + } + + override fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): Int { + return Secp256k1CFunctions.secp256k1_musig_partial_sig_verify(Secp256k1Context.getContext(), psig, pubnonce, pubkey, keyaggCache, session) + } + + override fun musigPartialSigAgg(session: ByteArray, psigs: Array): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_partial_sig_agg(Secp256k1Context.getContext(), session, psigs) + } + override fun cleanup() { return Secp256k1CFunctions.secp256k1_context_destroy(Secp256k1Context.getContext()) } diff --git a/native/build-android.sh b/native/build-android.sh index 8a94c17..83b582c 100755 --- a/native/build-android.sh +++ b/native/build-android.sh @@ -33,7 +33,7 @@ export STRIP=$ANDROID_NDK/toolchains/llvm/prebuilt/$TOOLCHAIN/bin/llvm-strip cd secp256k1 ./autogen.sh -./configure CFLAGS=-fpic --host=$TARGET --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no +./configure CFLAGS=-fpic --host=$TARGET --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no make clean make diff --git a/native/build-ios.sh b/native/build-ios.sh index 3cf2b25..8c8e617 100755 --- a/native/build-ios.sh +++ b/native/build-ios.sh @@ -6,7 +6,7 @@ cp xconfigure.sh secp256k1 cd secp256k1 ./autogen.sh -sh xconfigure.sh --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no +sh xconfigure.sh --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no mkdir -p ../build/ios cp -v _build/universal/ios/* ../build/ios/ diff --git a/native/build.sh b/native/build.sh index 7e67763..283f27b 100755 --- a/native/build.sh +++ b/native/build.sh @@ -23,7 +23,7 @@ else fi ./autogen.sh -./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no +./configure $CONF_OPTS --enable-experimental --enable-module_ecdh --enable-module-recovery --enable-module-schnorrsig --enable-module-musig --enable-benchmark=no --enable-shared=no --enable-exhaustive-tests=no --enable-tests=no make clean make diff --git a/native/secp256k1 b/native/secp256k1 index 1ad5185..dd4932b 160000 --- a/native/secp256k1 +++ b/native/secp256k1 @@ -1 +1 @@ -Subproject commit 1ad5185cd42c0636104129fcc9f6a4bf9c67cc40 +Subproject commit dd4932b67b573b2366e729e869918b17964f5f83 diff --git a/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt index 8911051..fe7c010 100644 --- a/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt +++ b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt @@ -55,7 +55,7 @@ public interface Secp256k1 { */ public fun signSchnorr(data: ByteArray, sec: ByteArray, auxrand32: ByteArray?): ByteArray - /** + /** * Convert an ECDSA signature to a normalized lower-S form (bitcoin standardness rule). * Returns the normalized signature and a boolean set to true if the input signature was not normalized. * @@ -149,10 +149,108 @@ public interface Secp256k1 { compressed[0] = if (pubkey.last() % 2 == 0) 2.toByte() else 3.toByte() compressed } + else -> throw Secp256k1Exception("invalid public key") } } + /** + * Generate a secret nonce to be used in a musig2 signing session. + * 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 privkey (optional) signer's private key. + * @param aggpubkey aggregated public key of all participants in the signing session. + * @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 + + /** + * Aggregate public nonces from all participants of a signing session. + * + * @param pubnonces public nonces (one per participant). + * @return 66-byte aggregate public nonce (two public keys) or throws an exception is a nonce is invalid. + */ + public fun musigNonceAgg(pubnonces: Array): ByteArray + + /** + * Aggregate public keys from all participants of a signing session. + * + * @param pubkeys public keys of all participants in the signing session. + * @param keyaggCache (optional) key aggregation cache data from the signing session. If an empty byte array is + * provided, it will be filled with key aggregation data that can be used for the next steps of the signing process. + * @return 32-byte x-only public key. + */ + public fun musigPubkeyAgg(pubkeys: Array, keyaggCache: ByteArray?): ByteArray + + /** + * Tweak the aggregated public key of a signing session. + * + * @param keyaggCache key aggregation cache filled by [musigPubkeyAgg]. + * @param tweak32 private key tweak to apply. + * @return P + tweak32 * G (where P is the aggregated public key from [keyaggCache]). The key aggregation cache will + * be updated with the tweaked public key. + */ + public fun musigPubkeyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray + + /** + * Tweak the aggregated public key of a signing session, treating it as an x-only public key (e.g. when using taproot). + * + * @param keyaggCache key aggregation cache filled by [musigPubkeyAgg]. + * @param tweak32 private key tweak to apply. + * @return with_even_y(P) + tweak32 * G (where P is the aggregated public key from [keyaggCache]). The key aggregation + * cache will be updated with the tweaked public key. + */ + public fun musigPubkeyXonlyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray + + /** + * Create a signing session context based on the public information from all participants. + * + * @param aggnonce aggregated public nonce (see [musigNonceAgg]). + * @param msg32 32-byte message that will be signed. + * @param keyaggCache aggregated public key cache filled by calling [musigPubkeyAgg] with the public keys of all participants. + * @return signing session context that can be used to create partial signatures and aggregate them. + */ + public fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray + + /** + * Create a partial signature. + * + * @param secnonce signer's secret nonce (see [musigNonceGen]). + * @param privkey signer's private key. + * @param keyaggCache aggregated public key cache filled by calling [musigPubkeyAgg] with the public keys of all participants. + * @param session signing session context (see [musigNonceProcess]). + * @return 32-byte partial signature. + */ + public fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray + + /** + * Verify the partial signature from one of the signing session's participants. + * + * @param psig 32-byte partial signature. + * @param pubnonce individual public nonce of the signing participant. + * @param pubkey individual public key of the signing participant. + * @param keyaggCache aggregated public key cache filled by calling [musigPubkeyAgg] with the public keys of all participants. + * @param session signing session context (see [musigNonceProcess]). + * @return result code (1 if the partial signature is valid, 0 otherwise). + */ + public fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): Int + + /** + * Aggregate partial signatures from all participants into a single schnorr signature. If some of the partial + * signatures are invalid, this function will return an invalid aggregated signature without raising an error. + * It is recommended to use [musigPartialSigVerify] to verify partial signatures first. + * + * @param session signing session context (see [musigNonceProcess]). + * @param psigs list of 32-byte partial signatures. + * @return 64-byte aggregated schnorr signature. + */ + public fun musigPartialSigAgg(session: ByteArray, psigs: Array): ByteArray + /** * Delete the secp256k1 context from dynamic memory. */ @@ -161,6 +259,13 @@ public interface Secp256k1 { public companion object : Secp256k1 by getSecpk256k1() { @JvmStatic public fun get(): Secp256k1 = this + + // @formatter:off + public const val MUSIG2_SECRET_NONCE_SIZE: Int = 132 + public const val MUSIG2_PUBLIC_NONCE_SIZE: Int = 66 + public const val MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE: Int = 197 + public const val MUSIG2_PUBLIC_SESSION_SIZE: Int = 133 + // @formatter:on } } diff --git a/src/nativeInterop/cinterop/libsecp256k1.def b/src/nativeInterop/cinterop/libsecp256k1.def index cdf1c79..6a1ad38 100644 --- a/src/nativeInterop/cinterop/libsecp256k1.def +++ b/src/nativeInterop/cinterop/libsecp256k1.def @@ -1,7 +1,7 @@ package = secp256k1 -headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h -headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h secp256k1.h +headers = secp256k1.h secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h secp256k1_musig.h +headerFilter = secp256k1/** secp256k1_ecdh.h secp256k1_recovery.h secp256k1_extrakeys.h secp256k1_schnorrsig.h secp256k1_musig.h secp256k1.h staticLibraries.linux = libsecp256k1.a libraryPaths.linux = c/secp256k1/build/linux/ native/build/linux/ native/build/darwin/ diff --git a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt index a06fde7..48468fe 100644 --- a/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt +++ b/src/nativeMain/kotlin/fr/acinq/secp256k1/Secp256k1Native.kt @@ -1,6 +1,7 @@ package fr.acinq.secp256k1 import kotlinx.cinterop.* +import platform.posix.memcpy import platform.posix.size_tVar import secp256k1.* @@ -40,6 +41,20 @@ public object Secp256k1Native : Secp256k1 { return pub } + private fun MemScope.allocPublicNonce(pubnonce: ByteArray): secp256k1_musig_pubnonce { + val nat = toNat(pubnonce) + val nPubnonce = alloc() + secp256k1_musig_pubnonce_parse(ctx, nPubnonce.ptr, nat).requireSuccess("secp256k1_musig_pubnonce_parse() failed") + return nPubnonce + } + + private fun MemScope.allocPartialSig(psig: ByteArray): secp256k1_musig_partial_sig { + val nat = toNat(psig) + val nPsig = alloc() + secp256k1_musig_partial_sig_parse(ctx, nPsig.ptr, nat).requireSuccess("secp256k1_musig_partial_sig_parse() failed") + return nPsig + } + private fun MemScope.serializePubkey(pubkey: secp256k1_pubkey): ByteArray { val serialized = allocArray(65) val outputLen = alloc() @@ -48,6 +63,24 @@ public object Secp256k1Native : Secp256k1 { return serialized.readBytes(outputLen.value.convert()) } + private fun MemScope.serializeXonlyPubkey(pubkey: secp256k1_xonly_pubkey): ByteArray { + val serialized = allocArray(32) + secp256k1_xonly_pubkey_serialize(ctx, serialized, pubkey.ptr).requireSuccess("secp256k1_xonly_pubkey_serialize() failed") + return serialized.readBytes(32) + } + + private fun MemScope.serializePubnonce(pubnonce: secp256k1_musig_pubnonce): ByteArray { + val serialized = allocArray(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + secp256k1_musig_pubnonce_serialize(ctx, serialized, pubnonce.ptr).requireSuccess("secp256k1_musig_pubnonce_serialize() failed") + return serialized.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + } + + private fun MemScope.serializeAggnonce(aggnonce: secp256k1_musig_aggnonce): ByteArray { + val serialized = allocArray(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + secp256k1_musig_aggnonce_serialize(ctx, serialized, aggnonce.ptr).requireSuccess("secp256k1_musig_aggnonce_serialize() failed") + return serialized.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + } + private fun DeferScope.toNat(bytes: ByteArray): CPointer { val ubytes = bytes.asUByteArray() val pinned = ubytes.pin() @@ -257,12 +290,163 @@ public object Secp256k1Native : Secp256k1 { return nSig.readBytes(64) } } - + + override fun musigNonceGen(sessionId32: ByteArray, privkey: ByteArray?, aggpubkey: ByteArray, msg32: ByteArray?, keyaggCache: ByteArray?, extraInput32: ByteArray?): ByteArray { + require(sessionId32.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) } + extraInput32?.let { require(it.size == 32) } + + val nonce = memScoped { + val secnonce = alloc() + val pubnonce = alloc() + val nPubkey = allocPublicKey(aggpubkey) + val nKeyAggCache = keyaggCache?.let { + val n = alloc() + memcpy(n.ptr, toNat(it), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + n + } + secp256k1_musig_nonce_gen(ctx, secnonce.ptr, pubnonce.ptr, toNat(sessionId32), privkey?.let { toNat(it) }, nPubkey.ptr, msg32?.let { toNat(it) },nKeyAggCache?.ptr, extraInput32?.let { toNat(it) }).requireSuccess("secp256k1_musig_nonce_gen() 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) } + memScoped { + val nPubnonces = pubnonces.map { allocPublicNonce(it).ptr } + val combined = alloc() + secp256k1_musig_nonce_agg(ctx, combined.ptr, nPubnonces.toCValues(), pubnonces.size.convert()).requireSuccess("secp256k1_musig_nonce_agg() failed") + return serializeAggnonce(combined) + } + } + + override fun musigPubkeyAgg(pubkeys: Array, keyaggCache: ByteArray?): ByteArray { + require(pubkeys.isNotEmpty()) + pubkeys.forEach { require(it.size == 33 || it.size == 65) } + keyaggCache?.let { require(it.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) } + memScoped { + val nPubkeys = pubkeys.map { allocPublicKey(it).ptr } + val combined = alloc() + val nKeyAggCache = keyaggCache?.let { + val n = alloc() + memcpy(n.ptr, toNat(it), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + n + } + secp256k1_musig_pubkey_agg(ctx, combined.ptr, nKeyAggCache?.ptr, nPubkeys.toCValues(), pubkeys.size.convert()).requireSuccess("secp256k1_musig_nonce_agg() failed") + val agg = serializeXonlyPubkey(combined) + keyaggCache?.let { blob -> nKeyAggCache?.let { memcpy(toNat(blob), it.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) } } + return agg + } + } + + override fun musigPubkeyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray { + require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + require(tweak32.size == 32) + memScoped { + val nKeyAggCache = alloc() + memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + val nPubkey = alloc() + secp256k1_musig_pubkey_ec_tweak_add(ctx, nPubkey.ptr, nKeyAggCache.ptr, toNat(tweak32)).requireSuccess("secp256k1_musig_pubkey_ec_tweak_add() failed") + memcpy(toNat(keyaggCache), nKeyAggCache.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + return serializePubkey(nPubkey) + } + } + + override fun musigPubkeyXonlyTweakAdd(keyaggCache: ByteArray, tweak32: ByteArray): ByteArray { + require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + require(tweak32.size == 32) + memScoped { + val nKeyAggCache = alloc() + memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + val nPubkey = alloc() + secp256k1_musig_pubkey_xonly_tweak_add(ctx, nPubkey.ptr, nKeyAggCache.ptr, toNat(tweak32)).requireSuccess("secp256k1_musig_pubkey_xonly_tweak_add() failed") + memcpy(toNat(keyaggCache), nKeyAggCache.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + return serializePubkey(nPubkey) + } + } + + override fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyaggCache: ByteArray): ByteArray { + require(aggnonce.size == Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + require(msg32.size == 32) + memScoped { + val nKeyAggCache = alloc() + memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + val nSession = alloc() + val nAggnonce = alloc() + secp256k1_musig_aggnonce_parse(ctx, nAggnonce.ptr, toNat(aggnonce)).requireSuccess("secp256k1_musig_aggnonce_parse() failed") + secp256k1_musig_nonce_process(ctx, nSession.ptr, nAggnonce.ptr, toNat(msg32), nKeyAggCache.ptr).requireSuccess("secp256k1_musig_nonce_process() failed") + val session = ByteArray(Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE) + memcpy(toNat(session), nSession.ptr, Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong()) + return session + } + } + + override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): ByteArray { + require(secnonce.size == Secp256k1.MUSIG2_SECRET_NONCE_SIZE) + require(privkey.size == 32) + require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + require(session.size == Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE) + + memScoped { + val nSecnonce = alloc() + memcpy(nSecnonce.ptr, toNat(secnonce), Secp256k1.MUSIG2_SECRET_NONCE_SIZE.toULong()) + val nKeypair = alloc() + secp256k1_keypair_create(ctx, nKeypair.ptr, toNat(privkey)) + val nPsig = alloc() + val nKeyAggCache = alloc() + memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + val nSession = alloc() + memcpy(nSession.ptr, toNat(session), Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong()) + secp256k1_musig_partial_sign(ctx, nPsig.ptr, nSecnonce.ptr, nKeypair.ptr, nKeyAggCache.ptr, nSession.ptr).requireSuccess("secp256k1_musig_partial_sign failed") + val psig = ByteArray(32) + secp256k1_musig_partial_sig_serialize(ctx, toNat(psig), nPsig.ptr).requireSuccess("secp256k1_musig_partial_sig_serialize() failed") + return psig + } + } + + override fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyaggCache: ByteArray, session: ByteArray): Int { + require(psig.size == 32) + require(pubnonce.size == Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + require(pubkey.size == 33 || pubkey.size == 65) + require(keyaggCache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + require(session.size == Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE) + + memScoped { + val nPSig = allocPartialSig(psig) + val nPubnonce = allocPublicNonce(pubnonce) + val nPubkey = allocPublicKey(pubkey) + val nKeyAggCache = alloc() + memcpy(nKeyAggCache.ptr, toNat(keyaggCache), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + val nSession = alloc() + memcpy(nSession.ptr, toNat(session), Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong()) + return secp256k1_musig_partial_sig_verify(ctx, nPSig.ptr, nPubnonce.ptr, nPubkey.ptr, nKeyAggCache.ptr, nSession.ptr) + } + } + + override fun musigPartialSigAgg(session: ByteArray, psigs: Array): ByteArray { + require(session.size == Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE) + require(psigs.isNotEmpty()) + psigs.forEach { require(it.size == 32) } + memScoped { + val nSession = alloc() + memcpy(nSession.ptr, toNat(session), Secp256k1.MUSIG2_PUBLIC_SESSION_SIZE.toULong()) + val nPsigs = psigs.map { allocPartialSig(it).ptr } + val sig64 = ByteArray(64) + secp256k1_musig_partial_sig_agg(ctx, toNat(sig64), nSession.ptr, nPsigs.toCValues(), psigs.size.convert()).requireSuccess("secp256k1_musig_partial_sig_agg() failed") + return sig64 + } + } + public override fun cleanup() { secp256k1_context_destroy(ctx) } - - } internal actual fun getSecpk256k1(): Secp256k1 = Secp256k1Native diff --git a/tests/build.gradle.kts b/tests/build.gradle.kts index 60005e5..d4ec0ce 100644 --- a/tests/build.gradle.kts +++ b/tests/build.gradle.kts @@ -1,3 +1,7 @@ +import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeHostTest +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest + plugins { kotlin("multiplatform") if (System.getProperty("includeAndroid")?.toBoolean() == true) { @@ -19,6 +23,8 @@ kotlin { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) + implementation("org.kodein.memory:klio-files:0.12.0") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") } } @@ -78,4 +84,26 @@ if (includeAndroid) { } } } -} \ No newline at end of file +} + +afterEvaluate { + tasks.withType { + testLogging { + events("passed", "skipped", "failed", "standard_out", "standard_error") + showExceptions = true + showStackTraces = true + } + } + + tasks.withType { + environment("TEST_RESOURCES_PATH", projectDir.resolve("src/commonTest/resources")) + } + + tasks.withType { + environment("TEST_RESOURCES_PATH", projectDir.resolve("src/commonTest/resources")) + } + + tasks.withType { + environment("SIMCTL_CHILD_TEST_RESOURCES_PATH", projectDir.resolve("src/commonTest/resources")) + } +} diff --git a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt new file mode 100644 index 0000000..7630df4 --- /dev/null +++ b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Musig2Test.kt @@ -0,0 +1,298 @@ +package fr.acinq.secp256k1 + +import kotlinx.serialization.json.* +import org.kodein.memory.file.FileSystem +import org.kodein.memory.file.Path +import org.kodein.memory.file.openReadableFile +import org.kodein.memory.file.resolve +import org.kodein.memory.system.Environment +import org.kodein.memory.text.readString +import org.kodein.memory.use +import kotlin.test.* + +class Musig2Test { + fun resourcesDir() = + Environment.findVariable("TEST_RESOURCES_PATH")?.let { Path(it) } + ?: FileSystem.workingDir().resolve("src/commonTest/resources") + + fun readData(filename: String): JsonElement { + val file = resourcesDir().resolve(filename) + val raw = file.openReadableFile().use { it.readString() } + val format = Json { ignoreUnknownKeys = true } + return format.parseToJsonElement(raw) + } + + @Test + fun `aggregate public keys`() { + val tests = readData("musig2/key_agg_vectors.json") + val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + + tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach { + val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content) + val keyAggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + val aggkey = Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyAggCache) + assertContentEquals(expected, aggkey) + } + tests.jsonObject["error_test_cases"]!!.jsonArray.forEach { + val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val tweakIndex = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int }.firstOrNull() + val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean } + when (tweakIndex) { + null -> { + // One of the public keys is invalid, so key aggregation will fail. + // Callers must verify that public keys are valid before aggregating them. + assertFails { + val keyAggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyAggCache) + } + } + + else -> { + // The tweak cannot be applied, it would result in an invalid public key. + assertFails { + val keyAggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyAggCache) + if (isXonly[0]) + Secp256k1.musigPubkeyXonlyTweakAdd(keyAggCache, tweaks[tweakIndex]) + else + Secp256k1.musigPubkeyTweakAdd(keyAggCache, tweaks[tweakIndex]) + } + } + } + } + } + + /** Secret nonces in test vectors use a custom encoding. */ + private fun deserializeSecretNonce(hex: String): ByteArray { + val serialized = Hex.decode(hex) + require(serialized.size == 97) { "secret nonce from test vector should be serialized using 97 bytes" } + // In test vectors, secret nonces are serialized as: + val compressedPublicKey = serialized.takeLast(33).toByteArray() + // We expect secret nonces serialized as: + // Where we use a different endianness for the public key coordinates than the test vectors. + val uncompressedPublicKey = Secp256k1.pubkeyParse(compressedPublicKey) + val publicKeyX = uncompressedPublicKey.drop(1).take(32).reversed().toByteArray() + val publicKeyY = uncompressedPublicKey.takeLast(32).reversed().toByteArray() + val magic = Hex.decode("220EDCF1") + return magic + serialized.take(64) + publicKeyX + publicKeyY + } + + @Test + fun `generate secret nonce`() { + val tests = readData("musig2/nonce_gen_vectors.json") + tests.jsonObject["test_cases"]!!.jsonArray.forEach { + val randprime = Hex.decode(it.jsonObject["rand_"]!!.jsonPrimitive.content) + val sk = it.jsonObject["sk"]?.jsonPrimitive?.contentOrNull?.let { Hex.decode(it) } + val pk = Hex.decode(it.jsonObject["pk"]!!.jsonPrimitive.content) + val keyagg = it.jsonObject["aggpk"]?.jsonPrimitive?.contentOrNull?.let { + // The test vectors directly provide an aggregated public key: we must manually create the corresponding + // key aggregation cache to correctly test. + val agg = ByteArray(1) { 2.toByte() } + Hex.decode(it) + val magic = Hex.decode("f4adbbdf") + magic + Secp256k1.pubkeyParse(agg).drop(1) + ByteArray(129) { 0x00 } + } + val msg = it.jsonObject["msg"]?.jsonPrimitive?.contentOrNull?.let { Hex.decode(it) } + val extraInput = it.jsonObject["extra_in"]?.jsonPrimitive?.contentOrNull?.let { Hex.decode(it) } + val expectedSecnonce = deserializeSecretNonce(it.jsonObject["expected_secnonce"]!!.jsonPrimitive.content) + val expectedPubnonce = Hex.decode(it.jsonObject["expected_pubnonce"]!!.jsonPrimitive.content) + // secp256k1 only supports signing 32-byte messages (when provided), which excludes some of the test vectors. + if (msg == null || msg.size == 32) { + val nonce = Secp256k1.musigNonceGen(randprime, sk, pk, msg, keyagg, extraInput) + 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(expectedPubnonce, pubnonce) + assertContentEquals(expectedSecnonce, secnonce) + } + } + } + + @Test + fun `aggregate nonces`() { + val tests = readData("musig2/nonce_agg_vectors.json") + val nonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach { + val nonceIndices = it.jsonObject["pnonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content) + val agg = Secp256k1.musigNonceAgg(nonceIndices.map { nonces[it] }.toTypedArray()) + assertNotNull(agg) + assertContentEquals(expected, agg) + } + tests.jsonObject["error_test_cases"]!!.jsonArray.forEach { + val nonceIndices = it.jsonObject["pnonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + assertFails { + Secp256k1.musigNonceAgg(nonceIndices.map { nonces[it] }.toTypedArray()) + } + } + } + + @Test + fun sign() { + val tests = readData("musig2/sign_verify_vectors.json") + val sk = Hex.decode(tests.jsonObject["sk"]!!.jsonPrimitive.content) + val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val secnonces = tests.jsonObject["secnonces"]!!.jsonArray.map { deserializeSecretNonce(it.jsonPrimitive.content) } + val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val aggnonces = tests.jsonObject["aggnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val msgs = tests.jsonObject["msgs"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + + tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach { + val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content) + val signerIndex = it.jsonObject["signer_index"]!!.jsonPrimitive.int + val messageIndex = it.jsonObject["msg_index"]!!.jsonPrimitive.int + val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray()) + assertNotNull(aggnonce) + assertContentEquals(aggnonces[it.jsonObject["aggnonce_index"]!!.jsonPrimitive.int], aggnonce) + val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg) + // We only support signing 32-byte messages. + if (msgs[messageIndex].size == 32) { + val session = Secp256k1.musigNonceProcess(aggnonce, msgs[messageIndex], keyagg) + assertNotNull(session) + val psig = Secp256k1.musigPartialSign(secnonces[keyIndices[signerIndex]], sk, keyagg, session) + assertContentEquals(expected, psig) + assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pnonces[nonceIndices[signerIndex]], pubkeys[keyIndices[signerIndex]], keyagg, session)) + } + } + tests.jsonObject["verify_fail_test_cases"]!!.jsonArray.forEach { + val psig = Hex.decode(it.jsonObject["sig"]!!.jsonPrimitive.content) + val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val signerIndex = it.jsonObject["signer_index"]!!.jsonPrimitive.int + val messageIndex = it.jsonObject["msg_index"]!!.jsonPrimitive.int + if (msgs[messageIndex].size == 32) { + val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray()) + assertNotNull(aggnonce) + val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg) + val session = Secp256k1.musigNonceProcess(aggnonce, msgs[messageIndex], keyagg) + assertNotNull(session) + assertFails { + require(Secp256k1.musigPartialSigVerify(psig, pnonces[nonceIndices[signerIndex]], pubkeys[keyIndices[signerIndex]], keyagg, session) == 1) + } + } + } + } + + @Test + fun `aggregate signatures`() { + val tests = readData("musig2/sig_agg_vectors.json") + val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val psigs = tests.jsonObject["psigs"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val msg = Hex.decode(tests.jsonObject["msg"]!!.jsonPrimitive.content) + + tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach { + val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val psigIndices = it.jsonObject["psig_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content) + val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray()) + assertNotNull(aggnonce) + val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean } + assertContentEquals(Hex.decode(it.jsonObject["aggnonce"]!!.jsonPrimitive.content), aggnonce) + val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg) + tweakIndices + .zip(isXonly) + .map { tweaks[it.first] to it.second } + .forEach { + if (it.second) + Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, it.first) + else + Secp256k1.musigPubkeyTweakAdd(keyagg, it.first) + } + val session = Secp256k1.musigNonceProcess(aggnonce, msg, keyagg) + val aggsig = Secp256k1.musigPartialSigAgg(session, psigIndices.map { psigs[it] }.toTypedArray()) + assertContentEquals(expected, aggsig) + } + tests.jsonObject["error_test_cases"]!!.jsonArray.forEach { + val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val psigIndices = it.jsonObject["psig_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val aggnonce = Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray()) + assertNotNull(aggnonce) + val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean } + assertContentEquals(Hex.decode(it.jsonObject["aggnonce"]!!.jsonPrimitive.content), aggnonce) + val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg) + tweakIndices + .zip(isXonly) + .map { tweaks[it.first] to it.second } + .forEach { + if (it.second) + Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, it.first) + else + Secp256k1.musigPubkeyTweakAdd(keyagg, it.first) + } + val session = Secp256k1.musigNonceProcess(aggnonce, msg, keyagg) + assertFails { + Secp256k1.musigPartialSigAgg(session, psigIndices.map { psigs[it] }.toTypedArray()) + } + } + } + + @Test + fun `tweak tests`() { + val tests = readData("musig2/tweak_vectors.json") + val sk = Hex.decode(tests.jsonObject["sk"]!!.jsonPrimitive.content) + val pubkeys = tests.jsonObject["pubkeys"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val pnonces = tests.jsonObject["pnonces"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val tweaks = tests.jsonObject["tweaks"]!!.jsonArray.map { Hex.decode(it.jsonPrimitive.content) } + val msg = Hex.decode(tests.jsonObject["msg"]!!.jsonPrimitive.content) + + val secnonce = deserializeSecretNonce(tests.jsonObject["secnonce"]!!.jsonPrimitive.content) + val aggnonce = Hex.decode(tests.jsonObject["aggnonce"]!!.jsonPrimitive.content) + + assertContentEquals(aggnonce, Secp256k1.musigNonceAgg(arrayOf(pnonces[0], pnonces[1], pnonces[2]))) + + tests.jsonObject["valid_test_cases"]!!.jsonArray.forEach { + val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val expected = Hex.decode(it.jsonObject["expected"]!!.jsonPrimitive.content) + assertContentEquals(aggnonce, Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())) + val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean } + val signerIndex = it.jsonObject["signer_index"]!!.jsonPrimitive.int + val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg) + tweakIndices + .zip(isXonly) + .map { tweaks[it.first] to it.second } + .forEach { + if (it.second) + Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, it.first) + else + Secp256k1.musigPubkeyTweakAdd(keyagg, it.first) + } + val session = Secp256k1.musigNonceProcess(aggnonce, msg, keyagg) + assertNotNull(session) + val psig = Secp256k1.musigPartialSign(secnonce, sk, keyagg, session) + assertContentEquals(expected, psig) + assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pnonces[nonceIndices[signerIndex]], pubkeys[keyIndices[signerIndex]], keyagg, session)) + } + tests.jsonObject["error_test_cases"]!!.jsonArray.forEach { + val keyIndices = it.jsonObject["key_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + val nonceIndices = it.jsonObject["nonce_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + assertContentEquals(aggnonce, Secp256k1.musigNonceAgg(nonceIndices.map { pnonces[it] }.toTypedArray())) + val tweakIndices = it.jsonObject["tweak_indices"]!!.jsonArray.map { it.jsonPrimitive.int } + assertEquals(1, tweakIndices.size) + val tweak = tweaks[tweakIndices.first()] + val isXonly = it.jsonObject["is_xonly"]!!.jsonArray.map { it.jsonPrimitive.boolean }.first() + val keyagg = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + Secp256k1.musigPubkeyAgg(keyIndices.map { pubkeys[it] }.toTypedArray(), keyagg) + assertFails { + if (isXonly) + Secp256k1.musigPubkeyXonlyTweakAdd(keyagg, tweak) + else + Secp256k1.musigPubkeyTweakAdd(keyagg, tweak) + } + } + } +} \ No newline at end of file diff --git a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt index cf07ae1..5cc71f8 100644 --- a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt +++ b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt @@ -352,6 +352,144 @@ class Secp256k1Test { } } + @Test + fun testMusig2GenerateNonce() { + val privkey = Hex.decode("0000000000000000000000000000000000000000000000000000000000000003") + val pubkey = Hex.decode("02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9") + val sessionId = Hex.decode("0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F") + val nonce = Secp256k1.musigNonceGen(sessionId, null, pubkey, null, null, null) + val pubnonce = Hex.encode(nonce.copyOfRange(132, 132 + 66)).uppercase() + assertEquals("02C96E7CB1E8AA5DAC64D872947914198F607D90ECDE5200DE52978AD5DED63C000299EC5117C2D29EDEE8A2092587C3909BE694D5CFF0667D6C02EA4059F7CD9786", pubnonce) + assertNotEquals(nonce, Secp256k1.musigNonceGen(sessionId, privkey, pubkey, null, null, null)) + assertNotEquals(nonce, Secp256k1.musigNonceGen(sessionId, null, pubkey, sessionId, null, null)) + } + + @Test + fun testMusig2AggregateNonce() { + val nonces = listOf( + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + // The following nonces are invalid. + "04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" + ).map { Hex.decode(it) } + val agg1 = Secp256k1.musigNonceAgg(arrayOf(nonces[0], nonces[1])) + assertEquals("035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8", Hex.encode(agg1).uppercase()) + + val agg2 = Secp256k1.musigNonceAgg(arrayOf(nonces[2], nonces[3])) + assertEquals("035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000", Hex.encode(agg2).uppercase()) + + assertFails { + Secp256k1.musigNonceAgg(arrayOf(nonces[0], nonces[4])) + } + assertFails { + Secp256k1.musigNonceAgg(arrayOf(nonces[5], nonces[1])) + } + assertFails { + Secp256k1.musigNonceAgg(arrayOf(nonces[6], nonces[1])) + } + } + + @Test + fun testMusig2AggregatePubkey() { + val pubkeys = listOf( + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", + "020000000000000000000000000000000000000000000000000000000000000005", + "02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", + "04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9" + ).map { Hex.decode(it) } + + val agg1 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), null) + assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg1).uppercase()) + + // We provide an empty cache, which will be filled when aggregating public keys. + val keyaggCache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + val agg2 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), keyaggCache) + assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg2).uppercase()) + assertTrue(keyaggCache.count { it.toInt() != 0 } > 100) // the cache has been filled with key aggregation data + + // We can reuse the key aggregation cache to speed up computation. + val agg3 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), keyaggCache) + assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg3).uppercase()) + + val agg4 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[2], pubkeys[1], pubkeys[0]), null) + assertEquals("6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B", Hex.encode(agg4).uppercase()) + + val agg5 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[0]), null) + assertEquals("B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935", Hex.encode(agg5).uppercase()) + + val agg6 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), null) + assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg6).uppercase()) + + // If we provide the key aggregation cache for a different session, it is ignored and overwritten. + val agg7 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), keyaggCache) + assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg7).uppercase()) + + // If we provide random data in the key aggregation cache, it is ignored and overwritten. + val agg8 = Secp256k1.musigPubkeyAgg(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), Random.nextBytes(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE)) + assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg8).uppercase()) + } + + @Test + fun testMusig2TweakPubkeys() { + val pubkeys = listOf( + "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", + "02531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337" + ).map { Hex.decode(it) }.toTypedArray() + val cache = ByteArray(Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + val agg1 = Secp256k1.musigPubkeyAgg(pubkeys, cache) + assertEquals("b6d830642403fc82511aca5ff98a5e76fcef0f89bffc1aadbe78ee74cd5a5716", Hex.encode(agg1)) + val agg2 = Secp256k1.musigPubkeyTweakAdd(cache, Hex.decode("7468697320636f756c64206265206120424950333220747765616b2e2e2e2e00")) + assertEquals("04791e4f22a21f19bd9798eceab92ad2ccc18f2d6660e91ae4c0709aaebf1aa9023701f468b0eddf8973495a5327f2169d9c6a50eb6a0f87c0fbee90a4067eb230", Hex.encode(agg2)) + val agg3 = Secp256k1.musigPubkeyXonlyTweakAdd(cache, Hex.decode("7468697320636f756c64206265206120746170726f6f7420747765616b2e2e00")) + assertEquals("04537a081a8d32ff700ca86aaa77a423e9b8d1480938076b645c68ee39d263c93948026928799b2d942cb5851db397015b26b1759de1b9ab2c691ced64a2eef836", Hex.encode(agg3)) + } + + @Test + fun testMusig2SigningSession() { + 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]) } + val psigs = (0 until 2).map { + val psig = Secp256k1.musigPartialSign(secnonces[it], privkeys[it], keyaggCaches[it], sessions[it]) + assertEquals(1, Secp256k1.musigPartialSigVerify(psig, pubnonces[it], pubkeys[it], keyaggCaches[it], sessions[it])) + assertEquals(0, Secp256k1.musigPartialSigVerify(Random.nextBytes(32), pubnonces[it], pubkeys[it], keyaggCaches[it], sessions[it])) + psig + } + + val sig = Secp256k1.musigPartialSigAgg(sessions[0], psigs.toTypedArray()) + assertContentEquals(sig, Secp256k1.musigPartialSigAgg(sessions[1], psigs.toTypedArray())) + assertTrue(Secp256k1.verifySchnorr(sig, msg32, aggpubkey)) + + val invalidSig1 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(psigs[0], psigs[0])) + assertFalse(Secp256k1.verifySchnorr(invalidSig1, msg32, aggpubkey)) + val invalidSig2 = Secp256k1.musigPartialSigAgg(sessions[0], arrayOf(Random.nextBytes(32), Random.nextBytes(32))) + assertFalse(Secp256k1.verifySchnorr(invalidSig2, msg32, aggpubkey)) + } + @Test fun testInvalidArguments() { assertFails { diff --git a/tests/src/commonTest/resources/musig2/det_sign_vectors.json b/tests/src/commonTest/resources/musig2/det_sign_vectors.json new file mode 100644 index 0000000..261669c --- /dev/null +++ b/tests/src/commonTest/resources/musig2/det_sign_vectors.json @@ -0,0 +1,144 @@ +{ + "sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671", + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "020000000000000000000000000000000000000000000000000000000000000007" + ], + "msgs": [ + "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "2626262626262626262626262626262626262626262626262626262626262626262626262626" + ], + "valid_test_cases": [ + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [0, 1, 2], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "03D96275257C2FCCBB6EEB77BDDF51D3C88C26EE1626C6CDA8999B9D34F4BA13A60309BE2BF883C6ABE907FA822D9CA166D51A3DCC28910C57528F6983FC378B7843", + "41EA65093F71D084785B20DC26A887CD941C9597860A21660CBDB9CC2113CAD3" + ] + }, + { + "rand": null, + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 0, 2], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 1, + "expected": [ + "028FBCCF5BB73A7B61B270BAD15C0F9475D577DD85C2157C9D38BEF1EC922B48770253BE3638C87369BC287E446B7F2C8CA5BEB9FFBD1EA082C62913982A65FC214D", + "AEAA31262637BFA88D5606679018A0FEEEC341F3107D1199857F6C81DE61B8DD" + ] + }, + { + "rand": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "aggothernonce": "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "key_indices": [1, 2, 0], + "tweaks": [], + "is_xonly": [], + "msg_index": 1, + "signer_index": 2, + "expected": [ + "024FA8D774F0C8743FAA77AFB4D08EE5A013C2E8EEAD8A6F08A77DDD2D28266DB803050905E8C994477F3F2981861A2E3791EF558626E645FBF5AA131C5D6447C2C2", + "FEE28A56B8556B7632E42A84122C51A4861B1F2DEC7E81B632195E56A52E3E13" + ], + "comment": "Message longer than 32 bytes" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046", + "key_indices": [0, 1, 2], + "tweaks": ["E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB"], + "is_xonly": [true], + "msg_index": 0, + "signer_index": 0, + "expected": [ + "031E07C0D11A0134E55DB1FC16095ADCBD564236194374AA882BFB3C78273BF673039D0336E8CA6288C00BFC1F8B594563529C98661172B9BC1BE85C23A4CE1F616B", + "7B1246C5889E59CB0375FA395CC86AC42D5D7D59FD8EAB4FDF1DCAB2B2F006EA" + ], + "comment": "Tweaked public key" + } + ], + "error_test_cases": [ + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 0, 3], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 1, + "error": { + "type": "invalid_contribution", + "signer": 2, + "contrib": "pubkey" + }, + "comment": "Signer 2 provided an invalid public key" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 2], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 1, + "error": { + "type": "value", + "message": "The signer's pubkey must be included in the list of pubkeys." + }, + "comment": "The signers pubkey is not in the list of pubkeys" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0437C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 2, 0], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 2, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggothernonce" + }, + "comment": "aggothernonce is invalid due wrong tag, 0x04, in the first half" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0000000000000000000000000000000000000000000000000000000000000000000287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 2, 0], + "tweaks": [], + "is_xonly": [], + "msg_index": 0, + "signer_index": 2, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggothernonce" + }, + "comment": "aggothernonce is invalid because first half corresponds to point at infinity" + }, + { + "rand": "0000000000000000000000000000000000000000000000000000000000000000", + "aggothernonce": "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "key_indices": [1, 2, 0], + "tweaks": ["FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"], + "is_xonly": [false], + "msg_index": 0, + "signer_index": 2, + "error": { + "type": "value", + "message": "The tweak must be less than n." + }, + "comment": "Tweak is invalid because it exceeds group size" + } + ] +} diff --git a/tests/src/commonTest/resources/musig2/key_agg_vectors.json b/tests/src/commonTest/resources/musig2/key_agg_vectors.json new file mode 100644 index 0000000..b2e623d --- /dev/null +++ b/tests/src/commonTest/resources/musig2/key_agg_vectors.json @@ -0,0 +1,88 @@ +{ + "pubkeys": [ + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", + "020000000000000000000000000000000000000000000000000000000000000005", + "02FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", + "04F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9" + ], + "tweaks": [ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "252E4BD67410A76CDF933D30EAA1608214037F1B105A013ECCD3C5C184A6110B" + ], + "valid_test_cases": [ + { + "key_indices": [0, 1, 2], + "expected": "90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C" + }, + { + "key_indices": [2, 1, 0], + "expected": "6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B" + }, + { + "key_indices": [0, 0, 0], + "expected": "B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935" + }, + { + "key_indices": [0, 0, 1, 1], + "expected": "69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E" + } + ], + "error_test_cases": [ + { + "key_indices": [0, 3], + "tweak_indices": [], + "is_xonly": [], + "error": { + "type": "invalid_contribution", + "signer": 1, + "contrib": "pubkey" + }, + "comment": "Invalid public key" + }, + { + "key_indices": [0, 4], + "tweak_indices": [], + "is_xonly": [], + "error": { + "type": "invalid_contribution", + "signer": 1, + "contrib": "pubkey" + }, + "comment": "Public key exceeds field size" + }, + { + "key_indices": [5, 0], + "tweak_indices": [], + "is_xonly": [], + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubkey" + }, + "comment": "First byte of public key is not 2 or 3" + }, + { + "key_indices": [0, 1], + "tweak_indices": [0], + "is_xonly": [true], + "error": { + "type": "value", + "message": "The tweak must be less than n." + }, + "comment": "Tweak is out of range" + }, + { + "key_indices": [6], + "tweak_indices": [1], + "is_xonly": [false], + "error": { + "type": "value", + "message": "The result of tweaking cannot be infinity." + }, + "comment": "Intermediate tweaking result is point at infinity" + } + ] +} diff --git a/tests/src/commonTest/resources/musig2/key_sort_vectors.json b/tests/src/commonTest/resources/musig2/key_sort_vectors.json new file mode 100644 index 0000000..de088a7 --- /dev/null +++ b/tests/src/commonTest/resources/musig2/key_sort_vectors.json @@ -0,0 +1,18 @@ +{ + "pubkeys": [ + "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", + "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF", + "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8" + ], + "sorted_pubkeys": [ + "023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038CA66", + "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", + "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", + "02DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EFF", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ] +} diff --git a/tests/src/commonTest/resources/musig2/nonce_agg_vectors.json b/tests/src/commonTest/resources/musig2/nonce_agg_vectors.json new file mode 100644 index 0000000..1c04b88 --- /dev/null +++ b/tests/src/commonTest/resources/musig2/nonce_agg_vectors.json @@ -0,0 +1,51 @@ +{ + "pnonces": [ + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "04FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B831", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A602FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" + ], + "valid_test_cases": [ + { + "pnonce_indices": [0, 1], + "expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B024725377345BDE0E9C33AF3C43C0A29A9249F2F2956FA8CFEB55C8573D0262DC8" + }, + { + "pnonce_indices": [2, 3], + "expected": "035FE1873B4F2967F52FEA4A06AD5A8ECCBE9D0FD73068012C894E2E87CCB5804B000000000000000000000000000000000000000000000000000000000000000000", + "comment": "Sum of second points encoded in the nonces is point at infinity which is serialized as 33 zero bytes" + } + ], + "error_test_cases": [ + { + "pnonce_indices": [0, 4], + "error": { + "type": "invalid_contribution", + "signer": 1, + "contrib": "pubnonce" + }, + "comment": "Public nonce from signer 1 is invalid due wrong tag, 0x04, in the first half" + }, + { + "pnonce_indices": [5, 1], + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubnonce" + }, + "comment": "Public nonce from signer 0 is invalid because the second half does not correspond to an X coordinate" + }, + { + "pnonce_indices": [6, 1], + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubnonce" + }, + "comment": "Public nonce from signer 0 is invalid because second half exceeds field size" + } + ] +} diff --git a/tests/src/commonTest/resources/musig2/nonce_gen_vectors.json b/tests/src/commonTest/resources/musig2/nonce_gen_vectors.json new file mode 100644 index 0000000..ced946f --- /dev/null +++ b/tests/src/commonTest/resources/musig2/nonce_gen_vectors.json @@ -0,0 +1,44 @@ +{ + "test_cases": [ + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "sk": "0202020202020202020202020202020202020202020202020202020202020202", + "pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "aggpk": "0707070707070707070707070707070707070707070707070707070707070707", + "msg": "0101010101010101010101010101010101010101010101010101010101010101", + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "B114E502BEAA4E301DD08A50264172C84E41650E6CB726B410C0694D59EFFB6495B5CAF28D045B973D63E3C99A44B807BDE375FD6CB39E46DC4A511708D0E9D2024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "expected_pubnonce": "02F7BE7089E8376EB355272368766B17E88E7DB72047D05E56AA881EA52B3B35DF02C29C8046FDD0DED4C7E55869137200FBDBFE2EB654267B6D7013602CAED3115A" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "sk": "0202020202020202020202020202020202020202020202020202020202020202", + "pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "aggpk": "0707070707070707070707070707070707070707070707070707070707070707", + "msg": "", + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "E862B068500320088138468D47E0E6F147E01B6024244AE45EAC40ACE5929B9F0789E051170B9E705D0B9EB49049A323BBBBB206D8E05C19F46C6228742AA7A9024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "expected_pubnonce": "023034FA5E2679F01EE66E12225882A7A48CC66719B1B9D3B6C4DBD743EFEDA2C503F3FD6F01EB3A8E9CB315D73F1F3D287CAFBB44AB321153C6287F407600205109" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "sk": "0202020202020202020202020202020202020202020202020202020202020202", + "pk": "024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "aggpk": "0707070707070707070707070707070707070707070707070707070707070707", + "msg": "2626262626262626262626262626262626262626262626262626262626262626262626262626", + "extra_in": "0808080808080808080808080808080808080808080808080808080808080808", + "expected_secnonce": "3221975ACBDEA6820EABF02A02B7F27D3A8EF68EE42787B88CBEFD9AA06AF3632EE85B1A61D8EF31126D4663A00DD96E9D1D4959E72D70FE5EBB6E7696EBA66F024D4B6CD1361032CA9BD2AEB9D900AA4D45D9EAD80AC9423374C451A7254D0766", + "expected_pubnonce": "02E5BBC21C69270F59BD634FCBFA281BE9D76601295345112C58954625BF23793A021307511C79F95D38ACACFF1B4DA98228B77E65AA216AD075E9673286EFB4EAF3" + }, + { + "rand_": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F", + "sk": null, + "pk": "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "aggpk": null, + "msg": null, + "extra_in": null, + "expected_secnonce": "89BDD787D0284E5E4D5FC572E49E316BAB7E21E3B1830DE37DFE80156FA41A6D0B17AE8D024C53679699A6FD7944D9C4A366B514BAF43088E0708B1023DD289702F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "expected_pubnonce": "02C96E7CB1E8AA5DAC64D872947914198F607D90ECDE5200DE52978AD5DED63C000299EC5117C2D29EDEE8A2092587C3909BE694D5CFF0667D6C02EA4059F7CD9786" + } + ] +} diff --git a/tests/src/commonTest/resources/musig2/sig_agg_vectors.json b/tests/src/commonTest/resources/musig2/sig_agg_vectors.json new file mode 100644 index 0000000..04a7bc6 --- /dev/null +++ b/tests/src/commonTest/resources/musig2/sig_agg_vectors.json @@ -0,0 +1,151 @@ +{ + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02D2DC6F5DF7C56ACF38C7FA0AE7A759AE30E19B37359DFDE015872324C7EF6E05", + "03C7FB101D97FF930ACD0C6760852EF64E69083DE0B06AC6335724754BB4B0522C", + "02352433B21E7E05D3B452B81CAE566E06D2E003ECE16D1074AABA4289E0E3D581" + ], + "pnonces": [ + "036E5EE6E28824029FEA3E8A9DDD2C8483F5AF98F7177C3AF3CB6F47CAF8D94AE902DBA67E4A1F3680826172DA15AFB1A8CA85C7C5CC88900905C8DC8C328511B53E", + "03E4F798DA48A76EEC1C9CC5AB7A880FFBA201A5F064E627EC9CB0031D1D58FC5103E06180315C5A522B7EC7C08B69DCD721C313C940819296D0A7AB8E8795AC1F00", + "02C0068FD25523A31578B8077F24F78F5BD5F2422AFF47C1FADA0F36B3CEB6C7D202098A55D1736AA5FCC21CF0729CCE852575C06C081125144763C2C4C4A05C09B6", + "031F5C87DCFBFCF330DEE4311D85E8F1DEA01D87A6F1C14CDFC7E4F1D8C441CFA40277BF176E9F747C34F81B0D9F072B1B404A86F402C2D86CF9EA9E9C69876EA3B9", + "023F7042046E0397822C4144A17F8B63D78748696A46C3B9F0A901D296EC3406C302022B0B464292CF9751D699F10980AC764E6F671EFCA15069BBE62B0D1C62522A", + "02D97DDA5988461DF58C5897444F116A7C74E5711BF77A9446E27806563F3B6C47020CBAD9C363A7737F99FA06B6BE093CEAFF5397316C5AC46915C43767AE867C00" + ], + "tweaks": [ + "B511DA492182A91B0FFB9A98020D55F260AE86D7ECBD0399C7383D59A5F2AF7C", + "A815FE049EE3C5AAB66310477FBC8BCCCAC2F3395F59F921C364ACD78A2F48DC", + "75448A87274B056468B977BE06EB1E9F657577B7320B0A3376EA51FD420D18A8" + ], + "psigs": [ + "B15D2CD3C3D22B04DAE438CE653F6B4ECF042F42CFDED7C41B64AAF9B4AF53FB", + "6193D6AC61B354E9105BBDC8937A3454A6D705B6D57322A5A472A02CE99FCB64", + "9A87D3B79EC67228CB97878B76049B15DBD05B8158D17B5B9114D3C226887505", + "66F82EA90923689B855D36C6B7E032FB9970301481B99E01CDB4D6AC7C347A15", + "4F5AEE41510848A6447DCD1BBC78457EF69024944C87F40250D3EF2C25D33EFE", + "DDEF427BBB847CC027BEFF4EDB01038148917832253EBC355FC33F4A8E2FCCE4", + "97B890A26C981DA8102D3BC294159D171D72810FDF7C6A691DEF02F0F7AF3FDC", + "53FA9E08BA5243CBCB0D797C5EE83BC6728E539EB76C2D0BF0F971EE4E909971", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ], + "msg": "599C67EA410D005B9DA90817CF03ED3B1C868E4DA4EDF00A5880B0082C237869", + "valid_test_cases": [ + { + "aggnonce": "0341432722C5CD0268D829C702CF0D1CBCE57033EED201FD335191385227C3210C03D377F2D258B64AADC0E16F26462323D701D286046A2EA93365656AFD9875982B", + "nonce_indices": [ + 0, + 1 + ], + "key_indices": [ + 0, + 1 + ], + "tweak_indices": [], + "is_xonly": [], + "psig_indices": [ + 0, + 1 + ], + "expected": "041DA22223CE65C92C9A0D6C2CAC828AAF1EEE56304FEC371DDF91EBB2B9EF0912F1038025857FEDEB3FF696F8B99FA4BB2C5812F6095A2E0004EC99CE18DE1E" + }, + { + "aggnonce": "0224AFD36C902084058B51B5D36676BBA4DC97C775873768E58822F87FE437D792028CB15929099EEE2F5DAE404CD39357591BA32E9AF4E162B8D3E7CB5EFE31CB20", + "nonce_indices": [ + 0, + 2 + ], + "key_indices": [ + 0, + 2 + ], + "tweak_indices": [], + "is_xonly": [], + "psig_indices": [ + 2, + 3 + ], + "expected": "1069B67EC3D2F3C7C08291ACCB17A9C9B8F2819A52EB5DF8726E17E7D6B52E9F01800260A7E9DAC450F4BE522DE4CE12BA91AEAF2B4279219EF74BE1D286ADD9" + }, + { + "aggnonce": "0208C5C438C710F4F96A61E9FF3C37758814B8C3AE12BFEA0ED2C87FF6954FF186020B1816EA104B4FCA2D304D733E0E19CEAD51303FF6420BFD222335CAA402916D", + "nonce_indices": [ + 0, + 3 + ], + "key_indices": [ + 0, + 2 + ], + "tweak_indices": [ + 0 + ], + "is_xonly": [ + false + ], + "psig_indices": [ + 4, + 5 + ], + "expected": "5C558E1DCADE86DA0B2F02626A512E30A22CF5255CAEA7EE32C38E9A71A0E9148BA6C0E6EC7683B64220F0298696F1B878CD47B107B81F7188812D593971E0CC" + }, + { + "aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD", + "nonce_indices": [ + 0, + 4 + ], + "key_indices": [ + 0, + 3 + ], + "tweak_indices": [ + 0, + 1, + 2 + ], + "is_xonly": [ + true, + false, + true + ], + "psig_indices": [ + 6, + 7 + ], + "expected": "839B08820B681DBA8DAF4CC7B104E8F2638F9388F8D7A555DC17B6E6971D7426CE07BF6AB01F1DB50E4E33719295F4094572B79868E440FB3DEFD3FAC1DB589E" + } + ], + "error_test_cases": [ + { + "aggnonce": "02B5AD07AFCD99B6D92CB433FBD2A28FDEB98EAE2EB09B6014EF0F8197CD58403302E8616910F9293CF692C49F351DB86B25E352901F0E237BAFDA11F1C1CEF29FFD", + "nonce_indices": [ + 0, + 4 + ], + "key_indices": [ + 0, + 3 + ], + "tweak_indices": [ + 0, + 1, + 2 + ], + "is_xonly": [ + true, + false, + true + ], + "psig_indices": [ + 7, + 8 + ], + "error": { + "type": "invalid_contribution", + "signer": 1 + }, + "comment": "Partial signature is invalid because it exceeds group size" + } + ] +} diff --git a/tests/src/commonTest/resources/musig2/sign_verify_vectors.json b/tests/src/commonTest/resources/musig2/sign_verify_vectors.json new file mode 100644 index 0000000..b467640 --- /dev/null +++ b/tests/src/commonTest/resources/musig2/sign_verify_vectors.json @@ -0,0 +1,212 @@ +{ + "sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671", + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA661", + "020000000000000000000000000000000000000000000000000000000000000007" + ], + "secnonces": [ + "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9" + ], + "pnonces": [ + "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046", + "0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "0200000000000000000000000000000000000000000000000000000000000000090287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480" + ], + "aggnonces": [ + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "048465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61020000000000000000000000000000000000000000000000000000000000000009", + "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD6102FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30" + ], + "msgs": [ + "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "", + "2626262626262626262626262626262626262626262626262626262626262626262626262626" + ], + "valid_test_cases": [ + { + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "expected": "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB" + }, + { + "key_indices": [1, 0, 2], + "nonce_indices": [1, 0, 2], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 1, + "expected": "9FF2F7AAA856150CC8819254218D3ADEEB0535269051897724F9DB3789513A52" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 2, + "expected": "FA23C359F6FAC4E7796BB93BC9F0532A95468C539BA20FF86D7C76ED92227900" + }, + { + "key_indices": [0, 1], + "nonce_indices": [0, 3], + "aggnonce_index": 1, + "msg_index": 0, + "signer_index": 0, + "expected": "AE386064B26105404798F75DE2EB9AF5EDA5387B064B83D049CB7C5E08879531", + "comment": "Both halves of aggregate nonce correspond to point at infinity" + }, + { + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "aggnonce_index": 0, + "msg_index": 1, + "signer_index": 0, + "expected": "D7D63FFD644CCDA4E62BC2BC0B1D02DD32A1DC3030E155195810231D1037D82D", + "comment": "Empty message" + }, + { + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "aggnonce_index": 0, + "msg_index": 2, + "signer_index": 0, + "expected": "E184351828DA5094A97C79CABDAAA0BFB87608C32E8829A4DF5340A6F243B78C", + "comment": "38-byte message" + } + ], + "sign_error_test_cases": [ + { + "key_indices": [1, 2], + "aggnonce_index": 0, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "value", + "message": "The signer's pubkey must be included in the list of pubkeys." + }, + "comment": "The signers pubkey is not in the list of pubkeys. This test case is optional: it can be skipped by implementations that do not check that the signer's pubkey is included in the list of pubkeys." + }, + { + "key_indices": [1, 0, 3], + "aggnonce_index": 0, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "invalid_contribution", + "signer": 2, + "contrib": "pubkey" + }, + "comment": "Signer 2 provided an invalid public key" + }, + { + "key_indices": [1, 2, 0], + "aggnonce_index": 2, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggnonce" + }, + "comment": "Aggregate nonce is invalid due wrong tag, 0x04, in the first half" + }, + { + "key_indices": [1, 2, 0], + "aggnonce_index": 3, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggnonce" + }, + "comment": "Aggregate nonce is invalid because the second half does not correspond to an X coordinate" + }, + { + "key_indices": [1, 2, 0], + "aggnonce_index": 4, + "msg_index": 0, + "secnonce_index": 0, + "error": { + "type": "invalid_contribution", + "signer": null, + "contrib": "aggnonce" + }, + "comment": "Aggregate nonce is invalid because second half exceeds field size" + }, + { + "key_indices": [0, 1, 2], + "aggnonce_index": 0, + "msg_index": 0, + "signer_index": 0, + "secnonce_index": 1, + "error": { + "type": "value", + "message": "first secnonce value is out of range." + }, + "comment": "Secnonce is invalid which may indicate nonce reuse" + } + ], + "verify_fail_test_cases": [ + { + "sig": "97AC833ADCB1AFA42EBF9E0725616F3C9A0D5B614F6FE283CEAAA37A8FFAF406", + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "msg_index": 0, + "signer_index": 0, + "comment": "Wrong signature (which is equal to the negation of valid signature)" + }, + { + "sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B", + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "msg_index": 0, + "signer_index": 1, + "comment": "Wrong signer" + }, + { + "sig": "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", + "key_indices": [0, 1, 2], + "nonce_indices": [0, 1, 2], + "msg_index": 0, + "signer_index": 0, + "comment": "Signature exceeds group size" + } + ], + "verify_error_test_cases": [ + { + "sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B", + "key_indices": [0, 1, 2], + "nonce_indices": [4, 1, 2], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubnonce" + }, + "comment": "Invalid pubnonce" + }, + { + "sig": "68537CC5234E505BD14061F8DA9E90C220A181855FD8BDB7F127BB12403B4D3B", + "key_indices": [3, 1, 2], + "nonce_indices": [0, 1, 2], + "msg_index": 0, + "signer_index": 0, + "error": { + "type": "invalid_contribution", + "signer": 0, + "contrib": "pubkey" + }, + "comment": "Invalid pubkey" + } + ] +} diff --git a/tests/src/commonTest/resources/musig2/tweak_vectors.json b/tests/src/commonTest/resources/musig2/tweak_vectors.json new file mode 100644 index 0000000..d0a7cfe --- /dev/null +++ b/tests/src/commonTest/resources/musig2/tweak_vectors.json @@ -0,0 +1,84 @@ +{ + "sk": "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671", + "pubkeys": [ + "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" + ], + "secnonce": "508B81A611F100A6B2B6B29656590898AF488BCF2E1F55CF22E5CFB84421FE61FA27FD49B1D50085B481285E1CA205D55C82CC1B31FF5CD54A489829355901F703935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9", + "pnonces": [ + "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480", + "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046" + ], + "aggnonce": "028465FCF0BBDBCF443AABCCE533D42B4B5A10966AC09A49655E8C42DAAB8FCD61037496A3CC86926D452CAFCFD55D25972CA1675D549310DE296BFF42F72EEEA8C9", + "tweaks": [ + "E8F791FF9225A2AF0102AFFF4A9A723D9612A682A25EBE79802B263CDFCD83BB", + "AE2EA797CC0FE72AC5B97B97F3C6957D7E4199A167A58EB08BCAFFDA70AC0455", + "F52ECBC565B3D8BEA2DFD5B75A4F457E54369809322E4120831626F290FA87E0", + "1969AD73CC177FA0B4FCED6DF1F7BF9907E665FDE9BA196A74FED0A3CF5AEF9D", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141" + ], + "msg": "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF", + "valid_test_cases": [ + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0], + "is_xonly": [true], + "signer_index": 2, + "expected": "E28A5C66E61E178C2BA19DB77B6CF9F7E2F0F56C17918CD13135E60CC848FE91", + "comment": "A single x-only tweak" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0], + "is_xonly": [false], + "signer_index": 2, + "expected": "38B0767798252F21BF5702C48028B095428320F73A4B14DB1E25DE58543D2D2D", + "comment": "A single plain tweak" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0, 1], + "is_xonly": [false, true], + "signer_index": 2, + "expected": "408A0A21C4A0F5DACAF9646AD6EB6FECD7F7A11F03ED1F48DFFF2185BC2C2408", + "comment": "A plain tweak followed by an x-only tweak" + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0, 1, 2, 3], + "is_xonly": [false, false, true, true], + "signer_index": 2, + "expected": "45ABD206E61E3DF2EC9E264A6FEC8292141A633C28586388235541F9ADE75435", + "comment": "Four tweaks: plain, plain, x-only, x-only." + }, + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [0, 1, 2, 3], + "is_xonly": [true, false, true, false], + "signer_index": 2, + "expected": "B255FDCAC27B40C7CE7848E2D3B7BF5EA0ED756DA81565AC804CCCA3E1D5D239", + "comment": "Four tweaks: x-only, plain, x-only, plain. If an implementation prohibits applying plain tweaks after x-only tweaks, it can skip this test vector or return an error." + } + ], + "error_test_cases": [ + { + "key_indices": [1, 2, 0], + "nonce_indices": [1, 2, 0], + "tweak_indices": [4], + "is_xonly": [false], + "signer_index": 2, + "error": { + "type": "value", + "message": "The tweak must be less than n." + }, + "comment": "Tweak is invalid because it exceeds group size" + } + ] +}