From 8d443b8030836fbf1561284866b41c42ff099ad7 Mon Sep 17 00:00:00 2001 From: mllwchrry Date: Fri, 13 Feb 2026 14:03:05 +0200 Subject: [PATCH] musig: Re-add adaptor signatures support --- doc/musig.md | 12 ++ examples/musig.c | 2 +- include/secp256k1_musig.h | 85 +++++++++++- src/ctime_tests.c | 24 +++- src/modules/musig/Makefile.am.include | 1 + src/modules/musig/adaptor_impl.h | 101 ++++++++++++++ src/modules/musig/main_impl.h | 1 + src/modules/musig/session_impl.h | 14 +- src/modules/musig/tests_impl.h | 182 +++++++++++++++++++++++--- 9 files changed, 401 insertions(+), 21 deletions(-) create mode 100644 src/modules/musig/adaptor_impl.h diff --git a/doc/musig.md b/doc/musig.md index ae21f9b1..ad09d983 100644 --- a/doc/musig.md +++ b/doc/musig.md @@ -18,6 +18,7 @@ Therefore, users of the musig module must take great care to make sure of the fo See also the comment on `secp256k1_musig_secnonce` in `include/secp256k1_musig.h`. 3. Opaque data structures are never written to or read from directly. Instead, only the provided accessor functions are used. +4. If adaptor signatures are used, all partial signatures are verified. ## Key Aggregation and (Taproot) Tweaking @@ -52,3 +53,14 @@ Similarly, the API supports an alternative protocol flow where generating the ag ## Verification A participant who wants to verify the partial signatures, but does not sign itself may do so using the above instructions except that the verifier skips steps 1, 4 and 7. + +# Atomic Swaps + +The signing API supports the production of "adaptor signatures", modified partial signatures +which are offset by an auxiliary secret known to one party. That is, +1. One party generates a (secret) adaptor `t` with corresponding (public) adaptor `T = t*G`. +2. When calling `secp256k1_musig_nonce_process`, the public adaptor `T` is provided as the `adaptor` argument. +3. The party who is going to extract the secret adaptor `t` later must verify all partial signatures. +4. Due to step 2, the signature output of `secp256k1_musig_partial_sig_agg` is a pre-signature and not a valid Schnorr signature. All parties involved extract this session's `nonce_parity` with `secp256k1_musig_nonce_parity`. +5. The party who knows `t` must "adapt" the pre-signature with `t` (and the `nonce_parity` using `secp256k1_musig_adapt` to complete the signature. +6. Any party who sees both the final signature and the pre-signature (and has the `nonce_parity`) can extract `t` with `secp256k1_musig_extract_adaptor`. diff --git a/examples/musig.c b/examples/musig.c index 396dbb9f..3450cf82 100644 --- a/examples/musig.c +++ b/examples/musig.c @@ -139,7 +139,7 @@ static int sign(const secp256k1_context* ctx, struct signer_secrets *signer_secr /* Every signer creates a partial signature */ for (i = 0; i < N_SIGNERS; i++) { /* Initialize the signing session by processing the aggregate nonce */ - if (!secp256k1_musig_nonce_process(ctx, &session, &agg_pubnonce, msg32, cache)) { + if (!secp256k1_musig_nonce_process(ctx, &session, &agg_pubnonce, msg32, cache, NULL)) { return 0; } /* partial_sign will clear the secnonce by setting it to 0. That's because diff --git a/include/secp256k1_musig.h b/include/secp256k1_musig.h index 53501814..97243033 100644 --- a/include/secp256k1_musig.h +++ b/include/secp256k1_musig.h @@ -16,7 +16,7 @@ extern "C" { * v1.0.0. You can find an example demonstrating the musig module in * examples/musig.c. * - * The module also supports BIP 341 ("Taproot") public key tweaking. + * The module also supports BIP 341 ("Taproot") public key tweaking and adaptor signatures. * * It is recommended to read the documentation in this include file carefully. * Further notes on API usage can be found in doc/musig.md @@ -462,6 +462,11 @@ SECP256K1_API int secp256k1_musig_nonce_agg( /** Takes the aggregate nonce and creates a session that is required for signing * and verification of partial signatures. * + * If the adaptor argument is non-NULL, then the output of + * musig_partial_sig_agg will be a pre-signature which is not a valid Schnorr + * signature. In order to create a valid signature, the pre-signature and the + * secret adaptor must be provided to `musig_adapt`. + * * Returns: 0 if the arguments are invalid, 1 otherwise * Args: ctx: pointer to a context object * Out: session: pointer to a struct to store the session @@ -470,13 +475,17 @@ SECP256K1_API int secp256k1_musig_nonce_agg( * msg32: the 32-byte message to sign * keyagg_cache: pointer to the keyagg_cache that was used to create the * aggregate (and potentially tweaked) pubkey + * adaptor: optional pointer to an adaptor point encoded as a public + * key if this signing session is part of an adaptor + * signature protocol (can be NULL) */ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_nonce_process( const secp256k1_context *ctx, secp256k1_musig_session *session, const secp256k1_musig_aggnonce *aggnonce, const unsigned char *msg32, - const secp256k1_musig_keyagg_cache *keyagg_cache + const secp256k1_musig_keyagg_cache *keyagg_cache, + const secp256k1_pubkey *adaptor ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); /** Produces a partial signature @@ -534,6 +543,7 @@ SECP256K1_API int secp256k1_musig_partial_sign( * before aggregating it with `musig_nonce_agg` and using the result to * create the `session` with `musig_nonce_process`. * + * This function is essential when using protocols with adaptor signatures. * It is not required to call this function in regular MuSig sessions, because * if any partial signature does not verify, the final signature will not * verify either, so the problem will be caught. However, this function @@ -581,6 +591,77 @@ SECP256K1_API int secp256k1_musig_partial_sig_agg( size_t n_sigs ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); +/** Extracts the nonce_parity bit from a session + * + * This is used for adaptor signatures. + * + * Returns: 0 if the arguments are invalid, 1 otherwise + * Args: ctx: pointer to a context object + * Out: nonce_parity: pointer to an integer that indicates the parity + * of the aggregate public nonce. Used for adaptor + * signatures. + * In: session: pointer to the session that was created with + * musig_nonce_process + */ +SECP256K1_API int secp256k1_musig_nonce_parity( + const secp256k1_context *ctx, + int *nonce_parity, + const secp256k1_musig_session *session +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Creates a signature from a pre-signature and an adaptor. + * + * If the sec_adaptor32 argument is incorrect, the output signature will be + * invalid. This function does not verify the signature. + * + * Returns: 0 if the arguments are invalid, or pre_sig64 or sec_adaptor32 contain + * invalid (overflowing) values. 1 otherwise (which does NOT mean the + * signature or the adaptor are valid!) + * Args: ctx: pointer to a context object + * Out: sig64: 64-byte signature. This pointer may point to the same + * memory area as `pre_sig`. + * In: pre_sig64: 64-byte pre-signature + * sec_adaptor32: 32-byte secret adaptor to add to the pre-signature + * nonce_parity: the output of `musig_nonce_parity` called with the + * session used for producing the pre-signature + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_adapt( + const secp256k1_context *ctx, + unsigned char *sig64, + const unsigned char *pre_sig64, + const unsigned char *sec_adaptor32, + int nonce_parity +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Extracts a secret adaptor from a MuSig pre-signature and corresponding + * signature + * + * This function will not fail unless given grossly invalid data; if it is + * merely given signatures that do not verify, the returned value will be + * nonsense. It is therefore important that all data be verified at earlier + * steps of any protocol that uses this function. In particular, this includes + * verifying all partial signatures that were aggregated into pre_sig64. + * + * Returns: 0 if the arguments are NULL, or sig64 or pre_sig64 contain + * grossly invalid (overflowing) values. 1 otherwise (which does NOT + * mean the signatures or the adaptor are valid!) + * Args: ctx: pointer to a context object + * Out:sec_adaptor32: 32-byte secret adaptor + * In: sig64: complete, valid 64-byte signature + * pre_sig64: the pre-signature corresponding to sig64, i.e., the + * aggregate of partial signatures without the secret + * adaptor + * nonce_parity: the output of `musig_nonce_parity` called with the + * session used for producing sig64 + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_musig_extract_adaptor( + const secp256k1_context *ctx, + unsigned char *sec_adaptor32, + const unsigned char *sig64, + const unsigned char *pre_sig64, + int nonce_parity +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/ctime_tests.c b/src/ctime_tests.c index 14d18e47..a4aef214 100644 --- a/src/ctime_tests.c +++ b/src/ctime_tests.c @@ -207,7 +207,12 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { secp256k1_musig_keyagg_cache cache; secp256k1_musig_session session; secp256k1_musig_partial_sig partial_sig; + const secp256k1_musig_partial_sig *partial_sig_ptr[1]; unsigned char extra_input[32]; + unsigned char sec_adaptor[32]; + secp256k1_pubkey adaptor; + unsigned char pre_sig[64]; + int nonce_parity; pk_ptr[0] = &pk; pubnonce_ptr[0] = &pubnonce; @@ -216,14 +221,19 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { session_secrand[0] = session_secrand[0] + 1; memcpy(extra_input, key, sizeof(extra_input)); extra_input[0] = extra_input[0] + 2; + memcpy(sec_adaptor, key, sizeof(sec_adaptor)); + sec_adaptor[0] = extra_input[0] + 3; + partial_sig_ptr[0] = &partial_sig; CHECK(secp256k1_keypair_create(ctx, &keypair, key)); CHECK(secp256k1_keypair_pub(ctx, &pk, &keypair)); CHECK(secp256k1_musig_pubkey_agg(ctx, &agg_pk, &cache, pk_ptr, 1)); + CHECK(secp256k1_ec_pubkey_create(ctx, &adaptor, sec_adaptor)); SECP256K1_CHECKMEM_UNDEFINE(key, 32); SECP256K1_CHECKMEM_UNDEFINE(session_secrand, sizeof(session_secrand)); SECP256K1_CHECKMEM_UNDEFINE(extra_input, sizeof(extra_input)); + SECP256K1_CHECKMEM_UNDEFINE(sec_adaptor, sizeof(sec_adaptor)); ret = secp256k1_musig_nonce_gen(ctx, &secnonce, &pubnonce, session_secrand, key, &pk, msg, &cache, extra_input); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); @@ -234,7 +244,7 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { CHECK(secp256k1_musig_nonce_agg(ctx, &aggnonce, pubnonce_ptr, 1)); /* Make sure that previous tests don't undefine msg. It's not used as a secret here. */ SECP256K1_CHECKMEM_DEFINE(msg, sizeof(msg)); - CHECK(secp256k1_musig_nonce_process(ctx, &session, &aggnonce, msg, &cache) == 1); + CHECK(secp256k1_musig_nonce_process(ctx, &session, &aggnonce, msg, &cache, &adaptor) == 1); ret = secp256k1_keypair_create(ctx, &keypair, key); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); @@ -242,6 +252,18 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) { ret = secp256k1_musig_partial_sign(ctx, &partial_sig, &secnonce, &keypair, &cache, &session); SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); CHECK(ret == 1); + + SECP256K1_CHECKMEM_DEFINE(&partial_sig, sizeof(partial_sig)); + CHECK(secp256k1_musig_partial_sig_agg(ctx, pre_sig, &session, partial_sig_ptr, 1)); + SECP256K1_CHECKMEM_DEFINE(pre_sig, sizeof(pre_sig)); + + CHECK(secp256k1_musig_nonce_parity(ctx, &nonce_parity, &session)); + ret = secp256k1_musig_adapt(ctx, sig, pre_sig, sec_adaptor, nonce_parity); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); + ret = secp256k1_musig_extract_adaptor(ctx, sec_adaptor, sig, pre_sig, nonce_parity); + SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret)); + CHECK(ret == 1); } #endif diff --git a/src/modules/musig/Makefile.am.include b/src/modules/musig/Makefile.am.include index 796443c9..cd60e67f 100644 --- a/src/modules/musig/Makefile.am.include +++ b/src/modules/musig/Makefile.am.include @@ -4,5 +4,6 @@ noinst_HEADERS += src/modules/musig/keyagg.h noinst_HEADERS += src/modules/musig/keyagg_impl.h noinst_HEADERS += src/modules/musig/session.h noinst_HEADERS += src/modules/musig/session_impl.h +noinst_HEADERS += src/modules/musig/adaptor_impl.h noinst_HEADERS += src/modules/musig/tests_impl.h noinst_HEADERS += src/modules/musig/vectors.h diff --git a/src/modules/musig/adaptor_impl.h b/src/modules/musig/adaptor_impl.h new file mode 100644 index 00000000..3830e8a2 --- /dev/null +++ b/src/modules/musig/adaptor_impl.h @@ -0,0 +1,101 @@ +/*********************************************************************** + * Copyright (c) 2021 Jonas Nick * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_MUSIG_ADAPTOR_IMPL_H +#define SECP256K1_MODULE_MUSIG_ADAPTOR_IMPL_H + +#include + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_musig.h" + +#include "session.h" +#include "../../scalar.h" + +int secp256k1_musig_nonce_parity(const secp256k1_context* ctx, int *nonce_parity, const secp256k1_musig_session *session) { + secp256k1_musig_session_internal session_i; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(nonce_parity != NULL); + ARG_CHECK(session != NULL); + + if (!secp256k1_musig_session_load(ctx, &session_i, session)) { + return 0; + } + *nonce_parity = session_i.fin_nonce_parity; + return 1; +} + +int secp256k1_musig_adapt(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *pre_sig64, const unsigned char *sec_adaptor32, int nonce_parity) { + secp256k1_scalar s; + secp256k1_scalar t; + int overflow; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig64 != NULL); + ARG_CHECK(pre_sig64 != NULL); + ARG_CHECK(sec_adaptor32 != NULL); + ARG_CHECK(nonce_parity == 0 || nonce_parity == 1); + + secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow); + if (overflow) { + return 0; + } + secp256k1_scalar_set_b32(&t, sec_adaptor32, &overflow); + ret &= !overflow; + + /* Determine if the secret adaptor should be negated. + * + * The musig_session stores the X-coordinate and the parity of the "final nonce" + * (r + t)*G, where r*G is the aggregate public nonce and t is the secret adaptor. + * + * Since a BIP340 signature requires an x-only public nonce, in the case where + * (r + t)*G has odd Y-coordinate (i.e. nonce_parity == 1), the x-only public nonce + * corresponding to the signature is actually (-r - t)*G. Thus adapting a + * pre-signature requires negating t in this case. + */ + if (nonce_parity) { + secp256k1_scalar_negate(&t, &t); + } + + secp256k1_scalar_add(&s, &s, &t); + secp256k1_scalar_get_b32(&sig64[32], &s); + memmove(sig64, pre_sig64, 32); + secp256k1_scalar_clear(&t); + return ret; +} + +int secp256k1_musig_extract_adaptor(const secp256k1_context* ctx, unsigned char *sec_adaptor32, const unsigned char *sig64, const unsigned char *pre_sig64, int nonce_parity) { + secp256k1_scalar t; + secp256k1_scalar s; + int overflow; + int ret = 1; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sec_adaptor32 != NULL); + ARG_CHECK(sig64 != NULL); + ARG_CHECK(pre_sig64 != NULL); + ARG_CHECK(nonce_parity == 0 || nonce_parity == 1); + + secp256k1_scalar_set_b32(&t, &sig64[32], &overflow); + ret &= !overflow; + secp256k1_scalar_negate(&t, &t); + + secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow); + if (overflow) { + return 0; + } + secp256k1_scalar_add(&t, &t, &s); + + if (!nonce_parity) { + secp256k1_scalar_negate(&t, &t); + } + secp256k1_scalar_get_b32(sec_adaptor32, &t); + secp256k1_scalar_clear(&t); + return ret; +} + +#endif diff --git a/src/modules/musig/main_impl.h b/src/modules/musig/main_impl.h index a1311e41..044bd30e 100644 --- a/src/modules/musig/main_impl.h +++ b/src/modules/musig/main_impl.h @@ -8,5 +8,6 @@ #include "keyagg_impl.h" #include "session_impl.h" +#include "adaptor_impl.h" #endif diff --git a/src/modules/musig/session_impl.h b/src/modules/musig/session_impl.h index 2715b09d..76f04c53 100644 --- a/src/modules/musig/session_impl.h +++ b/src/modules/musig/session_impl.h @@ -608,7 +608,7 @@ static void secp256k1_musig_nonce_process_internal(int *fin_nonce_parity, unsign *fin_nonce_parity = secp256k1_fe_is_odd(&fin_nonce_pt.y); } -int secp256k1_musig_nonce_process(const secp256k1_context* ctx, secp256k1_musig_session *session, const secp256k1_musig_aggnonce *aggnonce, const unsigned char *msg32, const secp256k1_musig_keyagg_cache *keyagg_cache) { +int secp256k1_musig_nonce_process(const secp256k1_context* ctx, secp256k1_musig_session *session, const secp256k1_musig_aggnonce *aggnonce, const unsigned char *msg32, const secp256k1_musig_keyagg_cache *keyagg_cache, const secp256k1_pubkey *adaptor) { secp256k1_keyagg_cache_internal cache_i; secp256k1_ge aggnonce_pts[2]; unsigned char fin_nonce[32]; @@ -630,6 +630,18 @@ int secp256k1_musig_nonce_process(const secp256k1_context* ctx, secp256k1_musig_ return 0; } + /* Add public adaptor to nonce */ + if (adaptor != NULL) { + secp256k1_ge adaptorp; + secp256k1_gej tmp; + if (!secp256k1_pubkey_load(ctx, &adaptorp, adaptor)) { + return 0; + } + secp256k1_gej_set_ge(&tmp, &aggnonce_pts[0]); + secp256k1_gej_add_ge_var(&tmp, &tmp, &adaptorp, NULL); + secp256k1_ge_set_gej(&aggnonce_pts[0], &tmp); + } + secp256k1_musig_nonce_process_internal(&session_i.fin_nonce_parity, fin_nonce, &session_i.noncecoef, aggnonce_pts, agg_pk32, msg32); secp256k1_schnorrsig_challenge(&session_i.challenge, fin_nonce, msg32, 32, agg_pk32); diff --git a/src/modules/musig/tests_impl.h b/src/modules/musig/tests_impl.h index ce6ae178..f4ae1585 100644 --- a/src/modules/musig/tests_impl.h +++ b/src/modules/musig/tests_impl.h @@ -34,7 +34,7 @@ static int create_keypair_and_pk(secp256k1_keypair *keypair, secp256k1_pubkey *p return ret; } -/* Just a simple (non-tweaked) 2-of-2 MuSig aggregate, sign, verify +/* Just a simple (non-adaptor, non-tweaked) 2-of-2 MuSig aggregate, sign, verify * test. */ static void musig_simple_test(void) { unsigned char sk[2][32]; @@ -74,7 +74,7 @@ static void musig_simple_test(void) { CHECK(secp256k1_musig_pubkey_agg(CTX, &agg_pk, &keyagg_cache, pk_ptr, 2) == 1); CHECK(secp256k1_musig_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2) == 1); - CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &keyagg_cache) == 1); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &keyagg_cache, NULL) == 1); for (i = 0; i < 2; i++) { CHECK(secp256k1_musig_partial_sign(CTX, &partial_sig[i], &secnonce[i], &keypair[i], &keyagg_cache, &session) == 1); @@ -123,6 +123,7 @@ static void musig_api_tests(void) { const secp256k1_musig_partial_sig *partial_sig_ptr[2]; secp256k1_musig_partial_sig invalid_partial_sig; const secp256k1_musig_partial_sig *invalid_partial_sig_ptr[2]; + unsigned char final_sig[64]; unsigned char pre_sig[64]; unsigned char buf[32]; unsigned char sk[2][32]; @@ -157,6 +158,10 @@ static void musig_api_tests(void) { const secp256k1_pubkey *invalid_pk_ptr2[2]; const secp256k1_pubkey *invalid_pk_ptr3[3]; unsigned char tweak[32]; + int nonce_parity; + unsigned char sec_adaptor[32]; + unsigned char sec_adaptor1[32]; + secp256k1_pubkey adaptor; int i; /** setup **/ @@ -174,8 +179,10 @@ static void musig_api_tests(void) { memset(&invalid_pubnonce, 0, sizeof(invalid_pubnonce)); memset(&invalid_session, 0, sizeof(invalid_session)); + testrand256(sec_adaptor); testrand256(msg); testrand256(tweak); + CHECK(secp256k1_ec_pubkey_create(CTX, &adaptor, sec_adaptor) == 1); for (i = 0; i < 2; i++) { pk_ptr[i] = &pk[i]; invalid_pk_ptr2[i] = &invalid_pk; @@ -388,15 +395,17 @@ static void musig_api_tests(void) { } /** Process nonces **/ - CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &keyagg_cache) == 1); - CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, NULL, &aggnonce, msg, &keyagg_cache)); - CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, NULL, msg, &keyagg_cache)); - CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, (secp256k1_musig_aggnonce*) &invalid_pubnonce, msg, &keyagg_cache)); - CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, &aggnonce, NULL, &keyagg_cache)); - CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, NULL)); - CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &invalid_keyagg_cache)); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &keyagg_cache, &adaptor) == 1); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, NULL, &aggnonce, msg, &keyagg_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, NULL, msg, &keyagg_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, (secp256k1_musig_aggnonce*) &invalid_pubnonce, msg, &keyagg_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, &aggnonce, NULL, &keyagg_cache, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, NULL, &adaptor)); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &invalid_keyagg_cache, &adaptor)); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &keyagg_cache, NULL) == 1); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &keyagg_cache, (secp256k1_pubkey *)&invalid_pk)); - CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &keyagg_cache) == 1); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, &keyagg_cache, &adaptor) == 1); memcpy(&secnonce_tmp, &secnonce[0], sizeof(secnonce_tmp)); CHECK(secp256k1_musig_partial_sign(CTX, &partial_sig[0], &secnonce_tmp, &keypair[0], &keyagg_cache, &session) == 1); @@ -481,6 +490,40 @@ static void musig_api_tests(void) { CHECK_ILLEGAL(CTX, secp256k1_musig_partial_sig_agg(CTX, pre_sig, &session, partial_sig_ptr, 0)); CHECK(secp256k1_musig_partial_sig_agg(CTX, pre_sig, &session, partial_sig_ptr, 1) == 1); CHECK(secp256k1_musig_partial_sig_agg(CTX, pre_sig, &session, partial_sig_ptr, 2) == 1); + + /** Adaptor signature verification */ + CHECK(secp256k1_musig_nonce_parity(CTX, &nonce_parity, &session) == 1); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_parity(CTX, NULL, &session)); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_parity(CTX, &nonce_parity, NULL)); + CHECK_ILLEGAL(CTX, secp256k1_musig_nonce_parity(CTX, &nonce_parity, &invalid_session)); + + CHECK(secp256k1_musig_adapt(CTX, final_sig, pre_sig, sec_adaptor, nonce_parity) == 1); + CHECK_ILLEGAL(CTX, secp256k1_musig_adapt(CTX, NULL, pre_sig, sec_adaptor, 0)); + CHECK_ILLEGAL(CTX, secp256k1_musig_adapt(CTX, final_sig, NULL, sec_adaptor, 0)); + CHECK(secp256k1_musig_adapt(CTX, final_sig, max64, sec_adaptor, 0) == 0); + CHECK_ILLEGAL(CTX, secp256k1_musig_adapt(CTX, final_sig, pre_sig, NULL, 0)); + CHECK(secp256k1_musig_adapt(CTX, final_sig, pre_sig, max64, 0) == 0); + CHECK_ILLEGAL(CTX, secp256k1_musig_adapt(CTX, final_sig, pre_sig, sec_adaptor, 2)); + /* sig and pre_sig argument point to the same location */ + memcpy(final_sig, pre_sig, sizeof(final_sig)); + CHECK(secp256k1_musig_adapt(CTX, final_sig, final_sig, sec_adaptor, nonce_parity) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &agg_pk) == 1); + + CHECK(secp256k1_musig_adapt(CTX, final_sig, pre_sig, sec_adaptor, nonce_parity) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, final_sig, msg, sizeof(msg), &agg_pk) == 1); + + /** Secret adaptor can be extracted from signature */ + CHECK(secp256k1_musig_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, nonce_parity) == 1); + CHECK(secp256k1_memcmp_var(sec_adaptor, sec_adaptor1, 32) == 0); + /* wrong nonce parity */ + CHECK(secp256k1_musig_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, !nonce_parity) == 1); + CHECK(secp256k1_memcmp_var(sec_adaptor, sec_adaptor1, 32) != 0); + CHECK_ILLEGAL(CTX, secp256k1_musig_extract_adaptor(CTX, NULL, final_sig, pre_sig, 0)); + CHECK_ILLEGAL(CTX, secp256k1_musig_extract_adaptor(CTX, sec_adaptor1, NULL, pre_sig, 0)); + CHECK(secp256k1_musig_extract_adaptor(CTX, sec_adaptor1, max64, pre_sig, 0) == 0); + CHECK_ILLEGAL(CTX, secp256k1_musig_extract_adaptor(CTX, sec_adaptor1, final_sig, NULL, 0)); + CHECK(secp256k1_musig_extract_adaptor(CTX, sec_adaptor1, final_sig, max64, 0) == 0); + CHECK_ILLEGAL(CTX, secp256k1_musig_extract_adaptor(CTX, sec_adaptor1, final_sig, pre_sig, 2)); } static void musig_nonce_bitflip(unsigned char **args, size_t n_flip, size_t n_bytes) { @@ -548,6 +591,111 @@ static void musig_nonce_test(void) { } } +static void scriptless_atomic_swap(void) { + /* Throughout this test "a" and "b" refer to two hypothetical blockchains, + * while the indices 0 and 1 refer to the two signers. Here signer 0 is + * sending a-coins to signer 1, while signer 1 is sending b-coins to signer + * 0. Signer 0 produces the adaptor signatures. */ + unsigned char pre_sig_a[64]; + unsigned char final_sig_a[64]; + unsigned char pre_sig_b[64]; + unsigned char final_sig_b[64]; + secp256k1_musig_partial_sig partial_sig_a[2]; + const secp256k1_musig_partial_sig *partial_sig_a_ptr[2]; + secp256k1_musig_partial_sig partial_sig_b[2]; + const secp256k1_musig_partial_sig *partial_sig_b_ptr[2]; + unsigned char sec_adaptor[32]; + unsigned char sec_adaptor_extracted[32]; + secp256k1_pubkey pub_adaptor; + unsigned char sk_a[2][32]; + unsigned char sk_b[2][32]; + secp256k1_keypair keypair_a[2]; + secp256k1_keypair keypair_b[2]; + secp256k1_pubkey pk_a[2]; + const secp256k1_pubkey *pk_a_ptr[2]; + secp256k1_pubkey pk_b[2]; + const secp256k1_pubkey *pk_b_ptr[2]; + secp256k1_musig_keyagg_cache keyagg_cache_a; + secp256k1_musig_keyagg_cache keyagg_cache_b; + secp256k1_xonly_pubkey agg_pk_a; + secp256k1_xonly_pubkey agg_pk_b; + secp256k1_musig_secnonce secnonce_a[2]; + secp256k1_musig_secnonce secnonce_b[2]; + secp256k1_musig_pubnonce pubnonce_a[2]; + secp256k1_musig_pubnonce pubnonce_b[2]; + const secp256k1_musig_pubnonce *pubnonce_ptr_a[2]; + const secp256k1_musig_pubnonce *pubnonce_ptr_b[2]; + secp256k1_musig_aggnonce aggnonce_a; + secp256k1_musig_aggnonce aggnonce_b; + secp256k1_musig_session session_a, session_b; + int nonce_parity_a; + int nonce_parity_b; + unsigned char seed_a[2][32] = { "a0", "a1" }; + unsigned char seed_b[2][32] = { "b0", "b1" }; + const unsigned char msg32_a[32] = {'t', 'h', 'i', 's', ' ', 'i', 's', ' ', 't', 'h', 'e', ' ', 'm', 'e', 's', 's', 'a', 'g', 'e', ' ', 'b', 'l', 'o', 'c', 'k', 'c', 'h', 'a', 'i', 'n', ' ', 'a'}; + const unsigned char msg32_b[32] = {'t', 'h', 'i', 's', ' ', 'i', 's', ' ', 't', 'h', 'e', ' ', 'm', 'e', 's', 's', 'a', 'g', 'e', ' ', 'b', 'l', 'o', 'c', 'k', 'c', 'h', 'a', 'i', 'n', ' ', 'b'}; + int i; + + /* Step 1: key setup */ + for (i = 0; i < 2; i++) { + pk_a_ptr[i] = &pk_a[i]; + pk_b_ptr[i] = &pk_b[i]; + pubnonce_ptr_a[i] = &pubnonce_a[i]; + pubnonce_ptr_b[i] = &pubnonce_b[i]; + partial_sig_a_ptr[i] = &partial_sig_a[i]; + partial_sig_b_ptr[i] = &partial_sig_b[i]; + + testrand256(sk_a[i]); + testrand256(sk_b[i]); + CHECK(create_keypair_and_pk(&keypair_a[i], &pk_a[i], sk_a[i]) == 1); + CHECK(create_keypair_and_pk(&keypair_b[i], &pk_b[i], sk_b[i]) == 1); + } + testrand256(sec_adaptor); + CHECK(secp256k1_ec_pubkey_create(CTX, &pub_adaptor, sec_adaptor) == 1); + + CHECK(secp256k1_musig_pubkey_agg(CTX, &agg_pk_a, &keyagg_cache_a, pk_a_ptr, 2) == 1); + CHECK(secp256k1_musig_pubkey_agg(CTX, &agg_pk_b, &keyagg_cache_b, pk_b_ptr, 2) == 1); + + CHECK(secp256k1_musig_nonce_gen(CTX, &secnonce_a[0], &pubnonce_a[0], seed_a[0], sk_a[0], &pk_a[0], NULL, NULL, NULL) == 1); + CHECK(secp256k1_musig_nonce_gen(CTX, &secnonce_a[1], &pubnonce_a[1], seed_a[1], sk_a[1], &pk_a[1], NULL, NULL, NULL) == 1); + CHECK(secp256k1_musig_nonce_gen(CTX, &secnonce_b[0], &pubnonce_b[0], seed_b[0], sk_b[0], &pk_b[0], NULL, NULL, NULL) == 1); + CHECK(secp256k1_musig_nonce_gen(CTX, &secnonce_b[1], &pubnonce_b[1], seed_b[1], sk_b[1], &pk_b[1], NULL, NULL, NULL) == 1); + + /* Step 2: Exchange nonces */ + CHECK(secp256k1_musig_nonce_agg(CTX, &aggnonce_a, pubnonce_ptr_a, 2) == 1); + CHECK(secp256k1_musig_nonce_process(CTX, &session_a, &aggnonce_a, msg32_a, &keyagg_cache_a, &pub_adaptor) == 1); + CHECK(secp256k1_musig_nonce_parity(CTX, &nonce_parity_a, &session_a) == 1); + CHECK(secp256k1_musig_nonce_agg(CTX, &aggnonce_b, pubnonce_ptr_b, 2) == 1); + CHECK(secp256k1_musig_nonce_process(CTX, &session_b, &aggnonce_b, msg32_b, &keyagg_cache_b, &pub_adaptor) == 1); + CHECK(secp256k1_musig_nonce_parity(CTX, &nonce_parity_b, &session_b) == 1); + + /* Step 3: Signer 0 produces partial signatures for both chains. */ + CHECK(secp256k1_musig_partial_sign(CTX, &partial_sig_a[0], &secnonce_a[0], &keypair_a[0], &keyagg_cache_a, &session_a) == 1); + CHECK(secp256k1_musig_partial_sign(CTX, &partial_sig_b[0], &secnonce_b[0], &keypair_b[0], &keyagg_cache_b, &session_b) == 1); + + /* Step 4: Signer 1 receives partial signatures, verifies them and creates a + * partial signature to send B-coins to signer 0. */ + CHECK(secp256k1_musig_partial_sig_verify(CTX, &partial_sig_a[0], &pubnonce_a[0], &pk_a[0], &keyagg_cache_a, &session_a) == 1); + CHECK(secp256k1_musig_partial_sig_verify(CTX, &partial_sig_b[0], &pubnonce_b[0], &pk_b[0], &keyagg_cache_b, &session_b) == 1); + CHECK(secp256k1_musig_partial_sign(CTX, &partial_sig_b[1], &secnonce_b[1], &keypair_b[1], &keyagg_cache_b, &session_b) == 1); + + /* Step 5: Signer 0 aggregates its own partial signature with the partial + * signature from signer 1 and adapts it. This results in a complete + * signature which is broadcasted by signer 0 to take B-coins. */ + CHECK(secp256k1_musig_partial_sig_agg(CTX, pre_sig_b, &session_b, partial_sig_b_ptr, 2) == 1); + CHECK(secp256k1_musig_adapt(CTX, final_sig_b, pre_sig_b, sec_adaptor, nonce_parity_b) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, final_sig_b, msg32_b, sizeof(msg32_b), &agg_pk_b) == 1); + + /* Step 6: Signer 1 signs, extracts adaptor from the published signature, + * and adapts the signature to take A-coins. */ + CHECK(secp256k1_musig_partial_sign(CTX, &partial_sig_a[1], &secnonce_a[1], &keypair_a[1], &keyagg_cache_a, &session_a) == 1); + CHECK(secp256k1_musig_partial_sig_agg(CTX, pre_sig_a, &session_a, partial_sig_a_ptr, 2) == 1); + CHECK(secp256k1_musig_extract_adaptor(CTX, sec_adaptor_extracted, final_sig_b, pre_sig_b, nonce_parity_b) == 1); + CHECK(secp256k1_memcmp_var(sec_adaptor_extracted, sec_adaptor, sizeof(sec_adaptor)) == 0); /* in real life we couldn't check this, of course */ + CHECK(secp256k1_musig_adapt(CTX, final_sig_a, pre_sig_a, sec_adaptor_extracted, nonce_parity_a) == 1); + CHECK(secp256k1_schnorrsig_verify(CTX, final_sig_a, msg32_a, sizeof(msg32_a), &agg_pk_a) == 1); +} + static void sha256_tag_test_internal(secp256k1_sha256 *sha_tagged, unsigned char *tag, size_t taglen) { secp256k1_sha256 sha; secp256k1_sha256_initialize_tagged(&sha, tag, taglen); @@ -616,7 +764,7 @@ static void musig_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const CHECK(secp256k1_musig_nonce_gen(CTX, &secnonce[1], &pubnonce[1], session_secrand[1], sk1, &pk[1], NULL, NULL, NULL) == 1); CHECK(secp256k1_musig_nonce_agg(CTX, &aggnonce, pubnonce_ptr, 2) == 1); - CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, keyagg_cache) == 1); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, msg, keyagg_cache, NULL) == 1); CHECK(secp256k1_musig_partial_sign(CTX, &partial_sig[0], &secnonce[0], &keypair[0], keyagg_cache, &session) == 1); CHECK(secp256k1_musig_partial_sign(CTX, &partial_sig[1], &secnonce[1], &keypair[1], keyagg_cache, &session) == 1); @@ -882,7 +1030,7 @@ static void musig_test_vectors_signverify(void) { CHECK(musig_vectors_keyagg_and_tweak(&error, &keyagg_cache, NULL, vector->pubkeys, NULL, c->key_indices_len, c->key_indices, 0, NULL, NULL)); CHECK(secp256k1_musig_aggnonce_parse(CTX, &aggnonce, vector->aggnonces[c->aggnonce_index])); - CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msgs[c->msg_index], &keyagg_cache)); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msgs[c->msg_index], &keyagg_cache, NULL)); CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, vector->pubkeys[0], sizeof(vector->pubkeys[0]))); musig_test_set_secnonce(&secnonce, vector->secnonces[0], &pubkey); @@ -922,8 +1070,9 @@ static void musig_test_vectors_signverify(void) { if (!expected) { continue; } - CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msgs[c->msg_index], &keyagg_cache)); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msgs[c->msg_index], &keyagg_cache, NULL)); + CHECK(secp256k1_keypair_create(CTX, &keypair, vector->sk)); CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, vector->pubkeys[0], sizeof(vector->pubkeys[0]))); musig_test_set_secnonce(&secnonce, vector->secnonces[c->secnonce_index], &pubkey); expected = c->error != MUSIG_SECNONCE; @@ -955,7 +1104,7 @@ static void musig_test_vectors_signverify(void) { CHECK(musig_vectors_keyagg_and_tweak(&error, &keyagg_cache, NULL, vector->pubkeys, NULL, c->key_indices_len, c->key_indices, 0, NULL, NULL)); CHECK(secp256k1_musig_nonce_agg(CTX, &aggnonce, pubnonce_ptr, c->nonce_indices_len) == 1); - CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msgs[c->msg_index], &keyagg_cache)); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msgs[c->msg_index], &keyagg_cache, NULL)); CHECK(secp256k1_ec_pubkey_parse(CTX, &pubkey, vector->pubkeys[c->signer_index], sizeof(vector->pubkeys[0]))); @@ -1010,7 +1159,7 @@ static void musig_test_vectors_tweak(void) { CHECK(secp256k1_keypair_create(CTX, &keypair, vector->sk)); CHECK(musig_vectors_keyagg_and_tweak(&error, &keyagg_cache, NULL, vector->pubkeys, vector->tweaks, c->key_indices_len, c->key_indices, c->tweak_indices_len, c->tweak_indices, c->is_xonly)); - CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msg, &keyagg_cache)); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msg, &keyagg_cache, NULL)); CHECK(secp256k1_musig_partial_sign(CTX, &partial_sig, &secnonce, &keypair, &keyagg_cache, &session)); CHECK(secp256k1_musig_partial_sig_serialize(CTX, partial_sig32, &partial_sig)); @@ -1046,7 +1195,7 @@ static void musig_test_vectors_sigagg(void) { CHECK(musig_vectors_keyagg_and_tweak(&error, &keyagg_cache, agg_pk32, vector->pubkeys, vector->tweaks, c->key_indices_len, c->key_indices, c->tweak_indices_len, c->tweak_indices, c->is_xonly)); CHECK(secp256k1_musig_aggnonce_parse(CTX, &aggnonce, c->aggnonce)); - CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msg, &keyagg_cache)); + CHECK(secp256k1_musig_nonce_process(CTX, &session, &aggnonce, vector->msg, &keyagg_cache, NULL)); for (j = 0; j < c->psig_indices_len; j++) { CHECK(secp256k1_musig_partial_sig_parse(CTX, &partial_sig[j], vector->psigs[c->psig_indices[j]])); partial_sig_ptr[j] = &partial_sig[j]; @@ -1127,6 +1276,7 @@ static void run_musig_tests(void) { for (i = 0; i < COUNT; i++) { /* Run multiple times to ensure that pk and nonce have different y * parities */ + scriptless_atomic_swap(); musig_tweak_test(); } sha256_tag_test();