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; - - 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; + 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; + + 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; - - if (jctx == 0) return 0; - if (jpubkey == NULL) return 0; - - 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 = 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; + 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; + + 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 = 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) -{ - 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; - - 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; +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; + + 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; } /* @@ -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) -{ - 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; - - 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"); - - 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; +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; + + 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); + + 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; } 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) -{ - 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; - - 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"); - - 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; - } - - 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; +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; + + 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"); + + 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"); + + 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"); + + 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) -{ - 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; - - 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"); - - return return_value; +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; + + 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"); + + 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; } /* @@ -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; - - 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; + 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; } /* @@ -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; - - 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"); - - 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; + 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; + + 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"); + + 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; - - 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; + 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; + + 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; - - 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"); - - 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; + 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; + + 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_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; } /* @@ -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; - - 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; + 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; + + 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; - - 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"); - - 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; + 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; + + 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"); + + 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; - - if (jctx == 0) return NULL; - if (jpubkeys == NULL) return NULL; + 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; count = (*penv)->GetArrayLength(penv, jpubkeys); CHECKRESULT(count < 1, "pubkey array cannot be empty") - pubkeys = calloc(count, sizeof(secp256k1_pubkey*)); - - for(i = 0; i < count; i++) { - 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); + 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_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; - - 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"); - - 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; + 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; + + 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"); + + 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) -{ - 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"); - - 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; +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; + + 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"); + + 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) -{ - 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"); - - 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; +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; + + 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 = 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) -{ - 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; - - 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); - } - - 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; +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; + + 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"); + + 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"); + + 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; + + 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"); + + 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; +} - if (jctx == 0) return 0; - if (jsig == NULL) return 0; - if (jmsg == NULL) return 0; - if (jpubkey == NULL) return 0; +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); +} - 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"); +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_xonly_pubkey_parse(ctx, &pubkey, (unsigned char*)pub); + result = secp256k1_ec_pubkey_parse(ctx, pubkeys[i], (unsigned char *)pub, size); (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); - CHECKRESULT(!result, "secp256k1_ec_pubkey_parse failed"); + 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; +} - 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; +/* + * 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) } } - - public override fun cleanup() { - secp256k1_context_destroy(ctx) + + 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" + } + ] +}