diff --git a/src/secp256k1/CMakeLists.txt b/src/secp256k1/CMakeLists.txt index cdac47ba9d1cf..c7d961e22acdd 100644 --- a/src/secp256k1/CMakeLists.txt +++ b/src/secp256k1/CMakeLists.txt @@ -76,6 +76,11 @@ if(SECP256K1_ENABLE_MODULE_ELLSWIFT) add_compile_definitions(ENABLE_MODULE_ELLSWIFT=1) endif() +option(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS "Enable Silent Payments module." OFF) +if(SECP256K1_ENABLE_MODULE_SILENTPAYMENTS) + add_compile_definitions(ENABLE_MODULE_SILENTPAYMENTS=1) +endif() + option(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS "Enable external default callback functions." OFF) if(SECP256K1_USE_EXTERNAL_DEFAULT_CALLBACKS) add_compile_definitions(USE_EXTERNAL_DEFAULT_CALLBACKS=1) @@ -276,6 +281,7 @@ message(" ECDSA pubkey recovery ............... ${SECP256K1_ENABLE_MODULE_RECOV message(" extrakeys ........................... ${SECP256K1_ENABLE_MODULE_EXTRAKEYS}") message(" schnorrsig .......................... ${SECP256K1_ENABLE_MODULE_SCHNORRSIG}") message(" ElligatorSwift ...................... ${SECP256K1_ENABLE_MODULE_ELLSWIFT}") +message(" Silent Payments ..................... ${SECP256K1_ENABLE_MODULE_SILENTPAYMENTS}") message("Parameters:") message(" ecmult window size .................. ${SECP256K1_ECMULT_WINDOW_SIZE}") message(" ecmult gen precision bits ........... ${SECP256K1_ECMULT_GEN_PREC_BITS}") diff --git a/src/secp256k1/Makefile.am b/src/secp256k1/Makefile.am index 32bc729a41a29..811d4ad5dcad5 100644 --- a/src/secp256k1/Makefile.am +++ b/src/secp256k1/Makefile.am @@ -271,3 +271,7 @@ endif if ENABLE_MODULE_ELLSWIFT include src/modules/ellswift/Makefile.am.include endif + +if ENABLE_MODULE_SILENTPAYMENTS +include src/modules/silentpayments/Makefile.am.include +endif diff --git a/src/secp256k1/configure.ac b/src/secp256k1/configure.ac index e3877850d3cfb..13b45545d05e3 100644 --- a/src/secp256k1/configure.ac +++ b/src/secp256k1/configure.ac @@ -188,6 +188,10 @@ AC_ARG_ENABLE(module_ellswift, AS_HELP_STRING([--enable-module-ellswift],[enable ElligatorSwift module [default=yes]]), [], [SECP_SET_DEFAULT([enable_module_ellswift], [yes], [yes])]) +AC_ARG_ENABLE(module_silentpayments, + AS_HELP_STRING([--enable-module-silentpayments],[enable Silent Payments module [default=no]]), [], + [SECP_SET_DEFAULT([enable_module_silentpayments], [no], [yes])]) + AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [], [SECP_SET_DEFAULT([enable_external_default_callbacks], [no], [no])]) @@ -404,6 +408,10 @@ if test x"$enable_module_ellswift" = x"yes"; then SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_ELLSWIFT=1" fi +if test x"$enable_module_silentpayments" = x"yes"; then + SECP_CONFIG_DEFINES="$SECP_CONFIG_DEFINES -DENABLE_MODULE_SILENTPAYMENTS=1" +fi + # Test if extrakeys is set after the schnorrsig module to allow the schnorrsig # module to set enable_module_extrakeys=yes if test x"$enable_module_extrakeys" = x"yes"; then @@ -447,6 +455,7 @@ AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"ye AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ELLSWIFT], [test x"$enable_module_ellswift" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_SILENTPAYMENTS], [test x"$enable_module_silentpayments" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$enable_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm32"]) AM_CONDITIONAL([BUILD_WINDOWS], [test "$build_windows" = "yes"]) @@ -469,6 +478,7 @@ echo " module recovery = $enable_module_recovery" echo " module extrakeys = $enable_module_extrakeys" echo " module schnorrsig = $enable_module_schnorrsig" echo " module ellswift = $enable_module_ellswift" +echo " module silentpayments = $enable_module_silentpayments" echo echo " asm = $set_asm" echo " ecmult window size = $set_ecmult_window" diff --git a/src/secp256k1/include/secp256k1_silentpayments.h b/src/secp256k1/include/secp256k1_silentpayments.h new file mode 100644 index 0000000000000..e72bcddab3eb0 --- /dev/null +++ b/src/secp256k1/include/secp256k1_silentpayments.h @@ -0,0 +1,204 @@ +#ifndef SECP256K1_SILENTPAYMENTS_H +#define SECP256K1_SILENTPAYMENTS_H + +#include "secp256k1.h" +#include "secp256k1_extrakeys.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* This module provides an implementation for the ECC related parts of + * Silent Payments, as specified in BIP352. This particularly involves + * the creation of input tweak data by summing up private or public keys + * and the derivation of a shared secret using Elliptic Curve Diffie-Hellman. + * Combined are either: + * - spender's private keys and receiver's public key (a * B, sender side) + * - spender's public keys and receiver's private key (A * b, receiver side) + * With this result, the necessary key material for ultimately creating/scanning + * or spending Silent Payment outputs can be determined. + * + * Note that this module is _not_ a full implementation of BIP352, as it + * inherently doesn't deal with higher-level concepts like addresses, output + * script types or transactions. The intent is to provide cryptographical + * helpers for low-level calculations that are most error-prone to custom + * implementations (e.g. enforcing the right y-parity for key material, ECDH + * calculation etc.). For any wallet software already using libsecp256k1, this + * API should provide all the functions needed for a Silent Payments + * implementation without the need for any further manual elliptic-curve + * operations. + */ + +/** Create Silent Payment tweak data from input private keys. + * + * Given a list of n private keys a_0...a_(n-1) (one for each input to spend) + * and an outpoints_hash, compute the corresponding input private keys tweak data: + * + * a_tweaked = (a_0 + a_1 + ... a_(n-1)) * outpoints_hash + * + * If necessary, the private keys are negated to enforce the right y-parity. + * For that reason, the private keys have to be passed in via two different parameter + * pairs, depending on whether they were used for creating taproot outputs or not. + * The resulting data is needed to create a shared secret for the sender side. + * + * Returns: 1 if shared secret creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: tweak_data32: pointer to the resulting 32-byte tweak data + * In: plain_seckeys: pointer to an array of 32-byte private keys of non-taproot inputs + * (can be NULL if no private keys of non-taproot inputs are used) + * n_plain_seckeys: the number of sender's non-taproot input private keys + * taproot_seckeys: pointer to an array of 32-byte private keys of taproot inputs + * (can be NULL if no private keys of taproot inputs are used) + * n_taproot_seckeys: the number of sender's taproot input private keys + * outpoints_hash32: hash of the sorted serialized outpoints + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_private_tweak_data( + const secp256k1_context *ctx, + unsigned char *tweak_data32, + const unsigned char *plain_seckeys, + size_t n_plain_seckeys, + const unsigned char *taproot_seckeys, + size_t n_taproot_seckeys, + const unsigned char *outpoints_hash32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(7); + +/** Create Silent Payment shared secret for the sender side. + * + * Given private input tweak data a_tweaked a recipient's scan public key B_scan, + * compute the corresponding shared secret using ECDH: + * + * shared_secret = a_tweaked * B_scan + * (where a_tweaked = (a_0 + a_1 + ... a_(n-1)) * outpoints_hash) + * + * The resulting data is needed as input for creating silent payments outputs + * belonging to the same receiver scan public key. + * + * Returns: 1 if shared secret creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: shared_secret33: pointer to the resulting 33-byte shared secret + * In: tweak_data32: pointer to 32-byte private input tweak data + * receiver_scan_pubkey: pointer to the receiver's scan pubkey + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_send_create_shared_secret( + const secp256k1_context *ctx, + unsigned char *shared_secret33, + const unsigned char *tweak_data32, + const secp256k1_pubkey *receiver_scan_pubkey +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment tweak data from input public keys. + * + * Given a list of n public keys A_0...A_(n-1) (one for each input to spend) + * and an outpoints_hash, compute the corresponding input public keys tweak data: + * + * A_tweaked = (A_0 + A_1 + ... A_(n-1)) * outpoints_hash + * + * If necessary, the public keys are negated to enforce the right y-parity. + * For that reason, the public keys have to be passed in via two different parameter + * pairs, depending on whether they were used for creating taproot outputs or not. + * The resulting data is needed to create a shared secret for the receiver's side. + * + * Returns: 1 if tweak data creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: tweak_data33: pointer to the resulting 33-byte tweak data + * In: plain_pubkeys: pointer to an array of non-taproot public keys + * (can be NULL if no non-taproot inputs are used) + * n_plain_pubkeys: the number of non-taproot input public keys + * xonly_pubkeys: pointer to an array of taproot x-only public keys + * (can be NULL if no taproot input public keys are used) + * n_xonly_pubkeys: the number of taproot input public keys + * outpoints_hash32: hash of the sorted serialized outpoints + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_public_tweak_data( + const secp256k1_context *ctx, + unsigned char *tweak_data33, + const secp256k1_pubkey *plain_pubkeys, + size_t n_plain_pubkeys, + const secp256k1_xonly_pubkey *xonly_pubkeys, + size_t n_xonly_pubkeys, + const unsigned char *outpoints_hash32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(7); + +/** Create Silent Payment shared secret for the receiver side. + * + * Given public input tweak data A_tweaked and a recipient's scan private key + * b_scan, compute the corresponding shared secret using ECDH: + * + * shared_secret = A_tweaked * b_scan + * (where A_tweaked = (A_0 + A_1 + ... A_(n-1)) * outpoints_hash) + * + * The resulting data is needed as input for creating silent payments outputs + * belonging to the same receiver scan public key. + * + * Returns: 1 if shared secret creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: shared_secret33: pointer to the resulting 33-byte shared secret + * In: tweak_data33: pointer to 33-byte public input tweak data + * receiver_scan_seckey: pointer to the receiver's scan private key + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_receive_create_shared_secret( + const secp256k1_context *ctx, + unsigned char *shared_secret33, + const unsigned char *tweak_data33, + const unsigned char *receiver_scan_seckey +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment output public key (both for sender and receiver). + * + * Given a shared_secret, a recipient's spend public key B_spend, and an + * output counter k, calculate the corresponding output public key: + * + * P_output = B_spend + sha256(shared_secret || ser_32(k)) * G + * + * Returns: 1 if outputs creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: output_xonly_pubkey: pointer to the resulting output x-only pubkey + * In: shared_secret33: shared secret, derived from either sender's + * or receiver's perspective with routines from above + * receiver_spend_pubkey: pointer to the receiver's spend pubkey + * k: output counter (usually set to 0, should be increased for + * every additional output to the same recipient) + * label_tweak32: an optional 32-byte label tweak + * (not supported yet, must be set to NULL right now) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_output_pubkey( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey *output_xonly_pubkey, + const unsigned char *shared_secret33, + const secp256k1_pubkey *receiver_spend_pubkey, + unsigned int k, + const unsigned char *label_tweak32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create Silent Payment output private key (for spending receiver's funds). + * + * Given a shared_secret, a recipient's spend private key b_spend, and an + * output counter k, calculate the corresponding output private key d: + * + * d = (b_spend + sha256(shared_secret || ser_32(k))) mod n + * + * Returns: 1 if private key creation was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: output_seckey: pointer to the resulting spending private key + * In: shared_secret33: shared secret, derived from either sender's + * or receiver's perspective with routines from above + * receiver_spend_seckey: pointer to the receiver's spend private key + * k: output counter (usually set to 0, should be increased for + * every additional output to the same recipient) + * label_tweak32: an optional 32-byte label tweak + * (not supported yet, must be set to NULL right now) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_create_output_seckey( + const secp256k1_context *ctx, + unsigned char *output_seckey, + const unsigned char *shared_secret33, + const unsigned char *receiver_spend_seckey, + unsigned int k, + const unsigned char *label_tweak32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_SILENTPAYMENTS_H */ diff --git a/src/secp256k1/src/modules/silentpayments/Makefile.am.include b/src/secp256k1/src/modules/silentpayments/Makefile.am.include new file mode 100644 index 0000000000000..842a33e2d9b41 --- /dev/null +++ b/src/secp256k1/src/modules/silentpayments/Makefile.am.include @@ -0,0 +1,2 @@ +include_HEADERS += include/secp256k1_silentpayments.h +noinst_HEADERS += src/modules/silentpayments/main_impl.h diff --git a/src/secp256k1/src/modules/silentpayments/main_impl.h b/src/secp256k1/src/modules/silentpayments/main_impl.h new file mode 100644 index 0000000000000..519b33ddc8f6a --- /dev/null +++ b/src/secp256k1/src/modules/silentpayments/main_impl.h @@ -0,0 +1,263 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H +#define SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H + +#include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_ecdh.h" +#include "../../../include/secp256k1_extrakeys.h" +#include "../../../include/secp256k1_silentpayments.h" +#include "../../hash.h" + +/* secp256k1_ecdh expects a hash function to be passed in or uses its default + * hashing function. We don't want to hash the ECDH result, so we define a + * custom function which simply returns the pubkey without hashing. + */ +static int ecdh_return_pubkey(unsigned char *output, const unsigned char *x32, const unsigned char *y32, void *data) { + secp256k1_pubkey pubkey; + unsigned char uncompressed_pubkey[65]; + size_t outputlen = 33; + (void)data; + + uncompressed_pubkey[0] = 0x04; + memcpy(uncompressed_pubkey + 1, x32, 32); + memcpy(uncompressed_pubkey + 33, y32, 32); + + if (!secp256k1_ec_pubkey_parse(secp256k1_context_static, &pubkey, uncompressed_pubkey, 65)) { + return 0; + } + + if (!secp256k1_ec_pubkey_serialize(secp256k1_context_static, output, &outputlen, &pubkey, SECP256K1_EC_COMPRESSED)) { + return 0; + } + + return 1; +} + +int secp256k1_silentpayments_create_private_tweak_data(const secp256k1_context *ctx, unsigned char *tweak_data32, const unsigned char *plain_seckeys, size_t n_plain_seckeys, const unsigned char *taproot_seckeys, size_t n_taproot_seckeys, const unsigned char *outpoints_hash32) { + size_t i; + unsigned char a_tweaked[32]; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(tweak_data32 != NULL); + memset(tweak_data32, 0, 32); + ARG_CHECK(plain_seckeys == NULL || n_plain_seckeys >= 1); + ARG_CHECK(taproot_seckeys == NULL || n_taproot_seckeys >= 1); + ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL)); + ARG_CHECK((n_plain_seckeys + n_taproot_seckeys) >= 1); + ARG_CHECK(outpoints_hash32 != NULL); + + /* Compute input private keys tweak: a_tweaked = (a_0 + a_1 + ... + a_(n-1)) * outpoints_hash */ + for (i = 0; i < n_plain_seckeys; i++) { + const unsigned char *seckey_to_add = &plain_seckeys[i*32]; + if (i == 0) { + memcpy(a_tweaked, seckey_to_add, 32); + continue; + } + if (!secp256k1_ec_seckey_tweak_add(ctx, a_tweaked, seckey_to_add)) { + return 0; + } + } + /* private keys used for taproot outputs have to be negated if they resulted in an odd point */ + for (i = 0; i < n_taproot_seckeys; i++) { + unsigned char seckey_to_add[32]; + secp256k1_pubkey pubkey; + secp256k1_ge ge; + + memcpy(seckey_to_add, &taproot_seckeys[32*i], 32); + if (!secp256k1_ec_pubkey_create(ctx, &pubkey, seckey_to_add)) { + return 0; + } + if (!secp256k1_pubkey_load(ctx, &ge, &pubkey)) { + return 0; + } + if (secp256k1_fe_is_odd(&ge.y)) { + if (!secp256k1_ec_seckey_negate(ctx, seckey_to_add)) { + return 0; + } + } + + if (i == 0 && n_plain_seckeys == 0) { + memcpy(a_tweaked, seckey_to_add, 32); + continue; + } + if (!secp256k1_ec_seckey_tweak_add(ctx, a_tweaked, seckey_to_add)) { + return 0; + } + } + + if (!secp256k1_ec_seckey_tweak_mul(ctx, a_tweaked, outpoints_hash32)) { + return 0; + } + + memcpy(tweak_data32, a_tweaked, 32); + return 1; +} + +int secp256k1_silentpayments_send_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const unsigned char *tweak_data32, const secp256k1_pubkey *receiver_scan_pubkey) { + /* Sanity check inputs */ + ARG_CHECK(shared_secret33 != NULL); + memset(shared_secret33, 0, 33); + ARG_CHECK(receiver_scan_pubkey != NULL); + + /* Compute shared_secret = a_tweaked * B_scan */ + if (!secp256k1_ecdh(ctx, shared_secret33, receiver_scan_pubkey, tweak_data32, ecdh_return_pubkey, NULL)) { + return 0; + } + + return 1; +} + +int secp256k1_silentpayments_create_public_tweak_data(const secp256k1_context *ctx, unsigned char *tweak_data33, const secp256k1_pubkey *plain_pubkeys, size_t n_plain_pubkeys, const secp256k1_xonly_pubkey *xonly_pubkeys, size_t n_xonly_pubkeys, const unsigned char *outpoints_hash32) { + size_t i; + secp256k1_pubkey A_tweaked; + size_t outputlen = 33; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(tweak_data33 != NULL); + memset(tweak_data33, 0, 33); + ARG_CHECK(plain_pubkeys == NULL || n_plain_pubkeys >= 1); + ARG_CHECK(xonly_pubkeys == NULL || n_xonly_pubkeys >= 1); + ARG_CHECK((plain_pubkeys != NULL) || (xonly_pubkeys != NULL)); + ARG_CHECK((n_plain_pubkeys + n_xonly_pubkeys) >= 1); + ARG_CHECK(outpoints_hash32 != NULL); + + /* Compute input public keys tweak: A_tweaked = (A_0 + A_1 + ... + A_n) * outpoints_hash */ + for (i = 0; i < n_plain_pubkeys; i++) { + secp256k1_pubkey combined; + const secp256k1_pubkey *addends[2]; + if (i == 0) { + A_tweaked = plain_pubkeys[0]; + continue; + } + addends[0] = &A_tweaked; + addends[1] = &plain_pubkeys[i]; + if (!secp256k1_ec_pubkey_combine(ctx, &combined, addends, 2)) { + return 0; + } + A_tweaked = combined; + } + /* X-only public keys have to be converted to regular public keys (assuming even parity) */ + for (i = 0; i < n_xonly_pubkeys; i++) { + unsigned char pubkey_to_add_ser[33]; + secp256k1_pubkey combined, pubkey_to_add; + const secp256k1_pubkey *addends[2]; + + pubkey_to_add_ser[0] = 0x02; + if (!secp256k1_xonly_pubkey_serialize(ctx, &pubkey_to_add_ser[1], &xonly_pubkeys[i])) { + return 0; + } + if (!secp256k1_ec_pubkey_parse(ctx, &pubkey_to_add, pubkey_to_add_ser, 33)) { + return 0; + } + + if (i == 0 && n_plain_pubkeys == 0) { + A_tweaked = pubkey_to_add; + continue; + } + addends[0] = &A_tweaked; + addends[1] = &pubkey_to_add; + if (!secp256k1_ec_pubkey_combine(ctx, &combined, addends, 2)) { + return 0; + } + A_tweaked = combined; + } + + if (!secp256k1_ec_pubkey_tweak_mul(ctx, &A_tweaked, outpoints_hash32)) { + return 0; + } + + /* Serialize tweak_data */ + if (!secp256k1_ec_pubkey_serialize(ctx, tweak_data33, &outputlen, &A_tweaked, SECP256K1_EC_COMPRESSED)) { + return 0; + } + + return 1; +} + +int secp256k1_silentpayments_receive_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const unsigned char *tweak_data33, const unsigned char *receiver_scan_seckey) { + secp256k1_pubkey A_tweaked; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(shared_secret33 != NULL); + memset(shared_secret33, 0, 33); + ARG_CHECK(tweak_data33 != NULL); + ARG_CHECK(receiver_scan_seckey != NULL); + + /* Parse tweak data into pubkey object */ + if (!secp256k1_ec_pubkey_parse(ctx, &A_tweaked, tweak_data33, 33)) { + return 0; + } + + /* Compute shared_secret = A_tweaked * b_scan */ + if (!secp256k1_ecdh(ctx, shared_secret33, &A_tweaked, receiver_scan_seckey, ecdh_return_pubkey, NULL)) { + return 0; + } + + return 1; +} + +static void secp256k1_silentpayments_create_t_k(unsigned char *t_k, const unsigned char *shared_secret33, unsigned int k) { + secp256k1_sha256 sha; + unsigned char shared_secret_and_k[33+4]; + + /* Compute t_k = sha256(shared_secret || ser_32(k)) */ + memcpy(shared_secret_and_k, shared_secret33, 33); + secp256k1_write_be32(shared_secret_and_k+33, k); + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, shared_secret_and_k, sizeof(shared_secret_and_k)); + secp256k1_sha256_finalize(&sha, t_k); +} + +int secp256k1_silentpayments_create_output_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *output_xonly_pubkey, const unsigned char *shared_secret33, const secp256k1_pubkey *receiver_spend_pubkey, unsigned int k, const unsigned char *label_tweak32) { + secp256k1_pubkey P_output; + unsigned char t_k[32]; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output_xonly_pubkey != NULL); + ARG_CHECK(shared_secret33 != NULL); + ARG_CHECK(receiver_spend_pubkey != NULL); + ARG_CHECK(label_tweak32 == NULL); /* label tweaks are not supported yet */ + + /* Compute and return P_output = B_spend + t_k * G */ + secp256k1_silentpayments_create_t_k(t_k, shared_secret33, k); + P_output = *receiver_spend_pubkey; + if (!secp256k1_ec_pubkey_tweak_add(ctx, &P_output, t_k)) { + return 0; + } + if (!secp256k1_xonly_pubkey_from_pubkey(ctx, output_xonly_pubkey, NULL, &P_output)) { + return 0; + } + + return 1; +} + +int secp256k1_silentpayments_create_output_seckey(const secp256k1_context *ctx, unsigned char *output_seckey, const unsigned char *shared_secret33, const unsigned char *receiver_spend_seckey, unsigned int k, const unsigned char *label_tweak32) { + unsigned char t_k[32]; + + /* Sanity check inputs */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output_seckey != NULL); + memset(output_seckey, 0, 32); + ARG_CHECK(shared_secret33 != NULL); + ARG_CHECK(receiver_spend_seckey != NULL); + ARG_CHECK(label_tweak32 == NULL); /* label tweaks are not supported yet */ + + /* Compute and return d = (b_spend + t_k) mod n */ + memcpy(output_seckey, receiver_spend_seckey, 32); + secp256k1_silentpayments_create_t_k(t_k, shared_secret33, k); + if (!secp256k1_ec_seckey_tweak_add(ctx, output_seckey, t_k)) { + return 0; + } + + return 1; +} + +#endif diff --git a/src/secp256k1/src/modules/silentpayments/tests_impl.h b/src/secp256k1/src/modules/silentpayments/tests_impl.h new file mode 100644 index 0000000000000..73544b430aada --- /dev/null +++ b/src/secp256k1/src/modules/silentpayments/tests_impl.h @@ -0,0 +1,105 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H +#define SECP256K1_MODULE_SILENTPAYMENTS_TESTS_H + +#include "../../../include/secp256k1_silentpayments.h" + +void run_silentpayments_tests(void) { + /* BIP-352 test vector + * "Single recipient: taproot input with odd y-value and non-taproot input" */ + unsigned char outpoints_hash[32] = { + 0x21,0x0f,0xef,0x5d,0x62,0x4d,0xb1,0x7c,0x96,0x5c,0x75,0x97,0xe2,0xc6,0xc9,0xf6, + 0x0e,0xf4,0x40,0xc8,0x31,0xd1,0x49,0xc4,0x35,0x67,0xc5,0x01,0x58,0x55,0x7f,0x12 + }; + unsigned char input_privkeys_plain[32] = { + 0x8d,0x47,0x51,0xf6,0xe8,0xa3,0x58,0x68,0x80,0xfb,0x66,0xc1,0x9a,0xe2,0x77,0x96, + 0x9b,0xd5,0xaa,0x06,0xf6,0x1c,0x4e,0xe2,0xf1,0xe2,0x48,0x6e,0xfd,0xf6,0x66,0xd3 + }; + unsigned char input_privkeys_taproot[32] = { + 0x03,0x78,0xe9,0x56,0x85,0xb7,0x45,0x65,0xfa,0x56,0x75,0x1b,0x84,0xa3,0x2d,0xfd, + 0x18,0x54,0x5d,0x10,0xd6,0x91,0x64,0x1b,0x83,0x72,0xe3,0x21,0x64,0xfa,0xd6,0x6a + }; + unsigned char input_pubkeys_plain[33] = {0x03, + 0xe0,0xec,0x4f,0x64,0xb3,0xfa,0x2e,0x46,0x3c,0xcf,0xcf,0x4e,0x85,0x6e,0x37,0xd5, + 0xe1,0xe2,0x02,0x75,0xbc,0x89,0xec,0x1d,0xef,0x9e,0xb0,0x98,0xef,0xf1,0xf8,0x5d + }; + unsigned char input_pubkeys_xonly[32] = { + 0x78,0x2e,0xeb,0x91,0x34,0x31,0xca,0x6e,0x9b,0x8c,0x2f,0xd8,0x0a,0x5f,0x72,0xed, + 0x20,0x24,0xef,0x72,0xa3,0xc6,0xfb,0x10,0x26,0x3c,0x37,0x99,0x37,0x32,0x33,0x38 + }; + unsigned char receiver_scan_privkey[32] = { + 0x0f,0x69,0x4e,0x06,0x80,0x28,0xa7,0x17,0xf8,0xaf,0x6b,0x94,0x11,0xf9,0xa1,0x33, + 0xdd,0x35,0x65,0x25,0x87,0x14,0xcc,0x22,0x65,0x94,0xb3,0x4d,0xb9,0x0c,0x1f,0x2c + }; + unsigned char receiver_scan_pubkey[33] = {0x02, + 0x20,0xbc,0xfa,0xc5,0xb9,0x9e,0x04,0xad,0x1a,0x06,0xdd,0xfb,0x01,0x6e,0xe1,0x35, + 0x82,0x60,0x9d,0x60,0xb6,0x29,0x1e,0x98,0xd0,0x1a,0x9b,0xc9,0xa1,0x6c,0x96,0xd4 + }; + unsigned char receiver_spend_privkey[32] = { + 0x9d,0x6a,0xd8,0x55,0xce,0x34,0x17,0xef,0x84,0xe8,0x36,0x89,0x2e,0x5a,0x56,0x39, + 0x2b,0xfb,0xa0,0x5f,0xa5,0xd9,0x7c,0xce,0xa3,0x0e,0x26,0x6f,0x54,0x0e,0x08,0xb3 + }; + unsigned char receiver_spend_pubkey[33] = {0x02, + 0x5c,0xc9,0x85,0x6d,0x6f,0x83,0x75,0x35,0x0e,0x12,0x39,0x78,0xda,0xac,0x20,0x0c, + 0x26,0x0c,0xb5,0xb5,0xae,0x83,0x10,0x6c,0xab,0x90,0x48,0x4d,0xcd,0x8f,0xcf,0x36 + }; + unsigned char output_expected[32] = { + 0x75,0xf5,0x01,0xf3,0x19,0xdb,0x54,0x9a,0xaa,0x61,0x37,0x17,0xbd,0x7a,0xf4,0x4d, + 0xa5,0x66,0xd4,0xd8,0x59,0xb6,0x7f,0xe4,0x36,0x94,0x65,0x64,0xfa,0xfc,0x47,0xa3 + }; + unsigned char privkey_tweak_expected[32] = { + 0x61,0x9a,0x5a,0x59,0xa1,0x6d,0x4a,0x8e,0x85,0x7e,0xf4,0x8e,0x63,0xef,0x7c,0x81, + 0x95,0xc8,0x58,0x19,0x1d,0x4e,0x82,0x62,0x05,0xe8,0x43,0x8a,0xb7,0x0d,0x05,0x9e + }; + + unsigned char shared_secret_sender[33]; + unsigned char shared_secret_receiver[33]; + unsigned char public_tweak_data[33]; + unsigned char private_tweak_data[32]; + secp256k1_xonly_pubkey output_expected_xonly_obj; + secp256k1_xonly_pubkey output_calculated_xonly_obj; + unsigned char output_calculated[32]; + unsigned char privkey_calculated[32]; + unsigned char privkey_expected[32]; + + /* convert raw key material into secp256k1 objects where necessary */ + secp256k1_pubkey input_pubkey_plain_obj, receiver_scan_pubkey_obj, receiver_spend_pubkey_obj; + secp256k1_xonly_pubkey input_pubkey_xonly_obj; + CHECK(secp256k1_ec_pubkey_parse(CTX, &input_pubkey_plain_obj, input_pubkeys_plain, 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &receiver_scan_pubkey_obj, receiver_scan_pubkey, 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &receiver_spend_pubkey_obj, receiver_spend_pubkey, 33)); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &input_pubkey_xonly_obj, input_pubkeys_xonly)); + CHECK(secp256k1_xonly_pubkey_parse(CTX, &output_expected_xonly_obj, output_expected)); + + /* create shared secret from sender and receiver perspective, and check that they match */ + CHECK(secp256k1_silentpayments_create_private_tweak_data(CTX, private_tweak_data, + input_privkeys_plain, 1, input_privkeys_taproot, 1, outpoints_hash)); + CHECK(secp256k1_silentpayments_send_create_shared_secret(CTX, shared_secret_sender, + private_tweak_data, &receiver_scan_pubkey_obj)); + + CHECK(secp256k1_silentpayments_create_public_tweak_data(CTX, public_tweak_data, + &input_pubkey_plain_obj, 1, &input_pubkey_xonly_obj, 1, outpoints_hash)); + CHECK(secp256k1_silentpayments_receive_create_shared_secret(CTX, shared_secret_receiver, + public_tweak_data, receiver_scan_privkey)); + + CHECK(secp256k1_memcmp_var(shared_secret_sender, shared_secret_receiver, 33) == 0); + + /* check that calculated silent payments output matches */ + CHECK(secp256k1_silentpayments_create_output_pubkey(CTX, &output_calculated_xonly_obj, + shared_secret_sender, &receiver_spend_pubkey_obj, 0, NULL)); + CHECK(secp256k1_xonly_pubkey_serialize(CTX, output_calculated, &output_calculated_xonly_obj)); + CHECK(secp256k1_memcmp_var(output_calculated, output_expected, 32) == 0); + + /* check that calculated silent payment output spending private key matches */ + memcpy(privkey_expected, receiver_spend_privkey, 32); + CHECK(secp256k1_ec_seckey_tweak_add(CTX, privkey_expected, privkey_tweak_expected)); + CHECK(secp256k1_silentpayments_create_output_seckey(CTX, privkey_calculated, + shared_secret_receiver, receiver_spend_privkey, 0, NULL)); + CHECK(secp256k1_memcmp_var(privkey_calculated, privkey_expected, 32) == 0); +} + +#endif diff --git a/src/secp256k1/src/secp256k1.c b/src/secp256k1/src/secp256k1.c index 4c11e7f0b8b58..75dd835370b3a 100644 --- a/src/secp256k1/src/secp256k1.c +++ b/src/secp256k1/src/secp256k1.c @@ -815,3 +815,7 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32, #ifdef ENABLE_MODULE_ELLSWIFT # include "modules/ellswift/main_impl.h" #endif + +#ifdef ENABLE_MODULE_SILENTPAYMENTS +# include "modules/silentpayments/main_impl.h" +#endif diff --git a/src/secp256k1/src/tests.c b/src/secp256k1/src/tests.c index d3959406c7f29..b940aad129835 100644 --- a/src/secp256k1/src/tests.c +++ b/src/secp256k1/src/tests.c @@ -7487,6 +7487,10 @@ static void run_ecdsa_wycheproof(void) { # include "modules/ellswift/tests_impl.h" #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS +# include "modules/silentpayments/tests_impl.h" +#endif + static void run_secp256k1_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -7835,6 +7839,10 @@ int main(int argc, char **argv) { run_ellswift_tests(); #endif +#ifdef ENABLE_MODULE_SILENTPAYMENTS + run_silentpayments_tests(); +#endif + /* util tests */ run_secp256k1_memczero_test(); run_secp256k1_byteorder_tests();