diff --git a/build.gradle.kts b/build.gradle.kts index 8056f90..fade156 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ buildscript { allprojects { group = "fr.acinq.secp256k1" - version = "0.13.0-SNAPSHOT" + version = "0.13.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..1235f79 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)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1process + (JNIEnv *, jclass, jlong, jbyteArray, 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 7bafa40..fe31999 100644 --- a/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c +++ b/jni/c/src/fr_acinq_secp256k1_Secp256k1CFunctions.c @@ -8,6 +8,7 @@ #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 @@ -803,3 +804,531 @@ JNIEXPORT jint JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1sc (*penv)->ReleaseByteArrayElements(penv, jmsg, msg, 0); return result; } + +static void copy_bytes_from_java(JNIEnv *penv, jbyteArray source, size_t size, unsigned char *dest) +{ + jbyte *ptr = NULL; + if (source == NULL) + return; // nothing to do + ptr = (*penv)->GetByteArrayElements(penv, source, 0); + memcpy(dest, ptr, size); + (*penv)->ReleaseByteArrayElements(penv, source, ptr, 0); +} + +static void copy_bytes_to_java(JNIEnv *penv, jbyteArray dest, size_t size, unsigned char *source) +{ + jbyte *ptr = (*penv)->GetByteArrayElements(penv, dest, 0); + memcpy(ptr, source, size); + (*penv)->ReleaseByteArrayElements(penv, dest, ptr, 0); +} + +// session_id32: ByteArray, seckey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyagg_cache: ByteArray?, extra_input32: ByteArray? +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_nonce_gen + * Signature: (J[B[B[B[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1gen(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jsession_id32, jbyteArray jseckey, jbyteArray jpubkey, jbyteArray jmsg32, jbyteArray jkeyaggcache, jbyteArray jextra_input32) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + int result = 0; + size_t size; + secp256k1_musig_pubnonce pubnonce; + secp256k1_musig_secnonce secnonce; + unsigned char session_id32[32]; + jbyte *pubkey_ptr; + secp256k1_pubkey pubkey; + unsigned char seckey[32]; + unsigned char msg32[32]; + secp256k1_musig_keyagg_cache keyaggcache; + unsigned char extra_input32[32]; + jbyteArray jnonce; + jbyte *nonce_ptr = NULL; + unsigned char nonce[fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_SECRET_NONCE_SIZE + fr_acinq_secp256k1_Secp256k1CFunctions_SECP256K1_MUSIG_PUBLIC_NONCE_SIZE]; + + if (jctx == 0) + return NULL; + + if (jsession_id32 == 0) + return NULL; + size = (*penv)->GetArrayLength(penv, jsession_id32); + CHECKRESULT(size != 32, "invalid session_id size"); + copy_bytes_from_java(penv, jsession_id32, size, session_id32); + + if (jseckey != NULL) + { + size = (*penv)->GetArrayLength(penv, jseckey); + CHECKRESULT(size != 32, "invalid session_id 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); + 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; + if (jkeyaggcache != NULL) + { + size = (*penv)->GetArrayLength(penv, jkeyaggcache); + CHECKRESULT(size != sizeof(secp256k1_musig_keyagg_cache), "invalid keyagg cache size"); + copy_bytes_from_java(penv, jkeyaggcache, size, keyaggcache.data); + } + + count = (*penv)->GetArrayLength(penv, jpubkeys); + pubkeys = calloc(count, sizeof(secp256k1_pubkey *)); + + for (i = 0; i < count; i++) + { + pubkeys[i] = calloc(1, sizeof(secp256k1_pubkey)); + jpubkey = (jbyteArray)(*penv)->GetObjectArrayElement(penv, jpubkeys, i); + size = (*penv)->GetArrayLength(penv, jpubkey); + CHECKRESULT1((size != 33) && (size != 65), "invalid public key size", free_pubkeys(pubkeys, count)); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_ec_pubkey_parse(ctx, pubkeys[i], (unsigned char *)pub, size); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT1(!result, "secp256k1_ec_pubkey_parse failed", free_pubkeys(pubkeys, count)); + } + result = secp256k1_musig_pubkey_agg(ctx, NULL, &combined, jkeyaggcache == NULL ? NULL : &keyaggcache, (const secp256k1_pubkey *const *)pubkeys, count); + free_pubkeys(pubkeys, count); + CHECKRESULT(!result, "secp256k1_musig_pubkey_agg failed"); + + size = 32; + jpubkey = (*penv)->NewByteArray(penv, 32); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + result = secp256k1_xonly_pubkey_serialize(ctx, (unsigned char *)pub, &combined); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_xonly_pubkey_serialize failed"); + + if (jkeyaggcache != NULL) + { + pub = (*penv)->GetByteArrayElements(penv, jkeyaggcache, 0); + memcpy(pub, keyaggcache.data, sizeof(secp256k1_musig_keyagg_cache)); + (*penv)->ReleaseByteArrayElements(penv, jkeyaggcache, pub, 0); + } + return jpubkey; +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_pubkey_ec_tweak_add + * Signature: (J[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1ec_1tweak_1add(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jkeyaggcache, jbyteArray jtweak32) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *tweak32, *pub; + secp256k1_pubkey pubkey; + secp256k1_musig_keyagg_cache keyaggcache; + jbyteArray jpubkey; + size_t size; + int result = 0; + + if (jctx == 0) + return NULL; + if (jkeyaggcache == NULL) + return NULL; + size = (*penv)->GetArrayLength(penv, jkeyaggcache); + CHECKRESULT(size != sizeof(secp256k1_musig_keyagg_cache), "invalid keyagg cache size"); + copy_bytes_from_java(penv, jkeyaggcache, size, keyaggcache.data); + if (jtweak32 == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jtweak32) != 32, "tweak must be 32 bytes"); + tweak32 = (*penv)->GetByteArrayElements(penv, jtweak32, 0); + + result = secp256k1_musig_pubkey_ec_tweak_add(ctx, &pubkey, &keyaggcache, tweak32); + (*penv)->ReleaseByteArrayElements(penv, jtweak32, tweak32, 0); + CHECKRESULT(!result, "secp256k1_musig_pubkey_ec_tweak_add failed"); + + jpubkey = (*penv)->NewByteArray(penv, 65); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + size = 65; + result = secp256k1_ec_pubkey_serialize(ctx, pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + + pub = (*penv)->GetByteArrayElements(penv, jkeyaggcache, 0); + memcpy(pub, keyaggcache.data, sizeof(secp256k1_musig_keyagg_cache)); + (*penv)->ReleaseByteArrayElements(penv, jkeyaggcache, pub, 0); + + return jpubkey; +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_pubkey_xonly_tweak_add + * Signature: (J[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1pubkey_1xonly_1tweak_1add(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jkeyaggcache, jbyteArray jtweak32) +{ + secp256k1_context *ctx = (secp256k1_context *)jctx; + jbyte *tweak32, *pub; + secp256k1_pubkey pubkey; + secp256k1_musig_keyagg_cache keyaggcache; + jbyteArray jpubkey; + size_t size; + int result = 0; + + if (jctx == 0) + return NULL; + if (jkeyaggcache == NULL) + return NULL; + size = (*penv)->GetArrayLength(penv, jkeyaggcache); + CHECKRESULT(size != sizeof(secp256k1_musig_keyagg_cache), "invalid keyagg cache size"); + copy_bytes_from_java(penv, jkeyaggcache, size, keyaggcache.data); + if (jtweak32 == NULL) + return NULL; + CHECKRESULT((*penv)->GetArrayLength(penv, jtweak32) != 32, "tweak must be 32 bytes"); + tweak32 = (*penv)->GetByteArrayElements(penv, jtweak32, 0); + + result = secp256k1_musig_pubkey_xonly_tweak_add(ctx, &pubkey, &keyaggcache, tweak32); + (*penv)->ReleaseByteArrayElements(penv, jtweak32, tweak32, 0); + CHECKRESULT(!result, "secp256k1_musig_pubkey_xonly_tweak_add failed"); + + jpubkey = (*penv)->NewByteArray(penv, 65); + pub = (*penv)->GetByteArrayElements(penv, jpubkey, 0); + size = 65; + result = secp256k1_ec_pubkey_serialize(ctx, pub, &size, &pubkey, SECP256K1_EC_UNCOMPRESSED); + (*penv)->ReleaseByteArrayElements(penv, jpubkey, pub, 0); + CHECKRESULT(!result, "secp256k1_ec_pubkey_serialize failed"); + + pub = (*penv)->GetByteArrayElements(penv, jkeyaggcache, 0); + memcpy(pub, keyaggcache.data, sizeof(secp256k1_musig_keyagg_cache)); + (*penv)->ReleaseByteArrayElements(penv, jkeyaggcache, pub, 0); + + return jpubkey; +} + +/* + * Class: fr_acinq_secp256k1_Secp256k1CFunctions + * Method: secp256k1_musig_nonce_process + * Signature: (J[B[B[B[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_fr_acinq_secp256k1_Secp256k1CFunctions_secp256k1_1musig_1nonce_1process(JNIEnv *penv, jclass clazz, jlong jctx, jbyteArray jaggnonce, jbyteArray jmsg32, jbyteArray jkeyaggcache, jbyteArray jadaptor) +{ + 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 nonce 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, NULL); + 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); + 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..df726c7 100644 --- a/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java +++ b/jni/src/main/java/fr/acinq/secp256k1/Secp256k1CFunctions.java @@ -29,6 +29,14 @@ 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); + public static final int SECP256K1_MUSIG_PUBLIC_NONCE_SIZE = 66; + + public static final int SECP256K1_MUSIG_SECRET_NONCE_SIZE = 132; + + public static final int SECP256K1_MUSIG_KEYAGG_CACHE_SIZE = 197; + + 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); @@ -68,4 +76,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, byte[] adaptor); + + 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..25e8dd5 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(session_id32: ByteArray, seckey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyagg_cache: ByteArray?, extra_input32: ByteArray?): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_nonce_gen(Secp256k1Context.getContext(), session_id32, seckey, pubkey, msg32, keyagg_cache, extra_input32) + } + + override fun musigNonceAgg(pubnonces: Array): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_nonce_agg(Secp256k1Context.getContext(), pubnonces) + } + + override fun musigPubkeyAdd(pubkeys: Array, keyagg_cache: ByteArray?): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_pubkey_agg(Secp256k1Context.getContext(), pubkeys, keyagg_cache) + } + + override fun musigPubkeyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_pubkey_ec_tweak_add(Secp256k1Context.getContext(), keyagg_cache, tweak32) + } + + override fun musigPubkeyXonlyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_pubkey_xonly_tweak_add(Secp256k1Context.getContext(), keyagg_cache, tweak32) + } + + override fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyagg_cache: ByteArray, adaptor: ByteArray?): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_nonce_process(Secp256k1Context.getContext(), aggnonce, msg32, keyagg_cache, adaptor) + } + + override fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyagg_cache: ByteArray, session: ByteArray): ByteArray { + return Secp256k1CFunctions.secp256k1_musig_partial_sign(Secp256k1Context.getContext(), secnonce, privkey, keyagg_cache, session) + } + + override fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyagg_cache: ByteArray, session: ByteArray): Int { + return Secp256k1CFunctions.secp256k1_musig_partial_sig_verify(Secp256k1Context.getContext(), psig, pubnonce, pubkey, keyagg_cache, 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/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt index 8911051..a4e936e 100644 --- a/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt +++ b/src/commonMain/kotlin/fr/acinq/secp256k1/Secp256k1.kt @@ -153,6 +153,25 @@ public interface Secp256k1 { } } + public fun musigNonceGen(session_id32: ByteArray, seckey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyagg_cache: ByteArray?, extra_input32: ByteArray?): ByteArray + + public fun musigNonceAgg(pubnonces: Array): ByteArray + + public fun musigPubkeyAdd(pubkeys: Array, keyagg_cache: ByteArray?): ByteArray + + public fun musigPubkeyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray + + public fun musigPubkeyXonlyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray + + public fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyagg_cache: ByteArray, adaptor: ByteArray?): ByteArray + + public fun musigPartialSign(secnonce: ByteArray, privkey: ByteArray, keyagg_cache: ByteArray, session: ByteArray): ByteArray + + public fun musigPartialSigVerify(psig: ByteArray, pubnonce: ByteArray, pubkey: ByteArray, keyagg_cache: ByteArray, session: ByteArray): Int + + public fun musigPartialSigAgg(session: ByteArray, psigs: Array): ByteArray + + /** * Delete the secp256k1 context from dynamic memory. */ @@ -161,6 +180,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..0386d91 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,160 @@ public object Secp256k1Native : Secp256k1 { return nSig.readBytes(64) } } - - public override fun cleanup() { - secp256k1_context_destroy(ctx) + + override fun musigNonceGen(session_id32: ByteArray, seckey: ByteArray?, pubkey: ByteArray, msg32: ByteArray?, keyagg_cache: ByteArray?, extra_input32: ByteArray?): ByteArray { + require(session_id32.size == 32) + seckey?.let { require(it.size == 32) } + msg32?.let { require(it.size == 32) } + keyagg_cache?.let { require(it.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) } + extra_input32?.let { require(it.size == 32) } + + val nonce = memScoped { + val secret_nonce = alloc() + val public_nonce = alloc() + val nPubkey = allocPublicKey(pubkey) + val nKeyAggCache = keyagg_cache?.let { + val n = alloc() + memcpy(n.ptr, toNat(it), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + n + } + secp256k1_musig_nonce_gen(ctx, secret_nonce.ptr, public_nonce.ptr, toNat(session_id32), seckey?.let { toNat(it) }, nPubkey.ptr, msg32?.let { toNat(it) },nKeyAggCache?.ptr, extra_input32?.let { toNat(it) }).requireSuccess("secp256k1_musig_nonce_gen() failed") + val nPubnonce = allocArray(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + secp256k1_musig_pubnonce_serialize(ctx, nPubnonce, public_nonce.ptr).requireSuccess("secp256k1_musig_pubnonce_serialize failed") + secret_nonce.ptr.readBytes(Secp256k1.MUSIG2_SECRET_NONCE_SIZE) + nPubnonce.readBytes(Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + } + return nonce + } + + override fun musigNonceAgg(pubnonces: Array): ByteArray { + 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 musigPubkeyAdd(pubkeys: Array, keyagg_cache: ByteArray?): ByteArray { + pubkeys.forEach { require(it.size == 33 || it.size == 65) } + keyagg_cache?.let { require(it.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) } + memScoped { + val nPubkeys = pubkeys.map { allocPublicKey(it).ptr } + val combined = alloc() + val nKeyAggCache = keyagg_cache?.let { + val n = alloc() + memcpy(n.ptr, toNat(it), Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + n + } + secp256k1_musig_pubkey_agg(ctx, null, combined.ptr, nKeyAggCache?.ptr, nPubkeys.toCValues(), pubkeys.size.convert()).requireSuccess("secp256k1_musig_nonce_agg() failed") + val agg = serializeXonlyPubkey(combined) + keyagg_cache?.let { blob -> nKeyAggCache?.let { memcpy(toNat(blob), it.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) } } + return agg + } + } + + override fun musigPubkeyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray { + require(keyagg_cache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + require(tweak32.size == 32) + memScoped { + val nKeyAggCache = alloc() + memcpy(nKeyAggCache.ptr, toNat(keyagg_cache), 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(keyagg_cache), nKeyAggCache.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + return serializePubkey(nPubkey) + } + } + + override fun musigPubkeyXonlyTweakAdd(keyagg_cache: ByteArray, tweak32: ByteArray): ByteArray { + require(keyagg_cache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + require(tweak32.size == 32) + memScoped { + val nKeyAggCache = alloc() + memcpy(nKeyAggCache.ptr, toNat(keyagg_cache), 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(keyagg_cache), nKeyAggCache.ptr, Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE.toULong()) + return serializePubkey(nPubkey) + } + } + + override fun musigNonceProcess(aggnonce: ByteArray, msg32: ByteArray, keyagg_cache: ByteArray, adaptor: ByteArray?): ByteArray { + require(aggnonce.size == Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + require(keyagg_cache.size == Secp256k1.MUSIG2_PUBLIC_KEYAGG_CACHE_SIZE) + require(msg32.size == 32) + memScoped { + val nKeyAggCache = alloc() + memcpy(nKeyAggCache.ptr, toNat(keyagg_cache), 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, null ).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, keyagg_cache: ByteArray, session: ByteArray): ByteArray { + require(secnonce.size == Secp256k1.MUSIG2_SECRET_NONCE_SIZE) + require(privkey.size == 32) + require(keyagg_cache.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(keyagg_cache), 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, keyagg_cache: ByteArray, session: ByteArray): Int { + require(psig.size == 32) + require(pubnonce.size == Secp256k1.MUSIG2_PUBLIC_NONCE_SIZE) + require(pubkey.size == 33 || pubkey.size == 65) + require(keyagg_cache.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(keyagg_cache), 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) + 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/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt index cf07ae1..d5bd100 100644 --- a/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt +++ b/tests/src/commonTest/kotlin/fr/acinq/secp256k1/Secp256k1Test.kt @@ -352,6 +352,119 @@ class Secp256k1Test { } } + @Test + fun testMusig2GenerateNonce() { + 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) + } + + @Test + fun testMusig2AggregateNonce() { + val nonces = listOf( + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E66603BA47FBC1834437B3212E89A84D8425E7BF12E0245D98262268EBDCB385D50641", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60248C264CDD57D3C24D79990B0F865674EB62A0F9018277A95011B41BFC193B833", + "020151C80F435648DF67A22B749CD798CE54E0321D034B92B709B567D60A42E6660279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "03FF406FFD8ADB9CD29877E4985014F66A59F6CD01C0E88CAA8E5F3166B1F676A60379BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", + "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.musigPubkeyAdd(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), null) + assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg1).uppercase()) + val cache = ByteArray(197) + val agg2 = Secp256k1.musigPubkeyAdd(arrayOf(pubkeys[0], pubkeys[1], pubkeys[2]), cache) + assertEquals("90539EEDE565F5D054F32CC0C220126889ED1E5D193BAF15AEF344FE59D4610C", Hex.encode(agg2).uppercase()) + + val agg3 = Secp256k1.musigPubkeyAdd(arrayOf(pubkeys[2], pubkeys[1], pubkeys[0]), null) + assertEquals("6204DE8B083426DC6EAF9502D27024D53FC826BF7D2012148A0575435DF54B2B", Hex.encode(agg3).uppercase()) + + val agg4 = Secp256k1.musigPubkeyAdd(arrayOf(pubkeys[0], pubkeys[0], pubkeys[0]), null) + assertEquals("B436E3BAD62B8CD409969A224731C193D051162D8C5AE8B109306127DA3AA935", Hex.encode(agg4).uppercase()) + + val agg5 = Secp256k1.musigPubkeyAdd(arrayOf(pubkeys[0], pubkeys[0], pubkeys[1], pubkeys[1]), null) + assertEquals("69BC22BFA5D106306E48A20679DE1D7389386124D07571D0D872686028C26A3E", Hex.encode(agg5).uppercase()) + } + + @Test + fun testMusig2TweakPubkeys() { + val pubkeys = listOf( + "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", + "02531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe337" + ).map { Hex.decode(it) }.toTypedArray() + val cache = ByteArray(197) + val agg1 = Secp256k1.musigPubkeyAdd(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 caches = (0 until 2).map { ByteArray(197) } + val aggpubkey = Secp256k1.musigPubkeyAdd(pubkeys.toTypedArray(), caches[0]) + Secp256k1.musigPubkeyAdd(pubkeys.toTypedArray(), caches[1]) + + val msg32 = Hex.decode("0303030303030303030303030303030303030303030303030303030303030303") + val sessions = (0 until 2).map { Secp256k1.musigNonceProcess(aggnonce, msg32, caches[it], null) } + val psigs = (0 until 2).map { + val psig = Secp256k1.musigPartialSign(secnonces[it], privkeys[it], caches[it], sessions[it]) + val check = Secp256k1.musigPartialSigVerify(psig, pubnonces[it], pubkeys[it], caches[it], sessions[it]) + assertEquals(1, check) + psig + } + val sig = Secp256k1.musigPartialSigAgg(sessions[0], psigs.toTypedArray()) + val check = Secp256k1.verifySchnorr(sig, msg32, aggpubkey) + assertTrue(check) + } + @Test fun testInvalidArguments() { assertFails {