diff --git a/include/secp256k1_silentpayments.h b/include/secp256k1_silentpayments.h index bc8628c6b7..ca3284a00c 100644 --- a/include/secp256k1_silentpayments.h +++ b/include/secp256k1_silentpayments.h @@ -2,6 +2,7 @@ #define SECP256K1_SILENTPAYMENTS_H #include "secp256k1.h" +#include "secp256k1_extrakeys.h" #ifdef __cplusplus extern "C" { @@ -25,6 +26,87 @@ extern "C" { * any further elliptic-curve operations from the wallet. */ +/* This struct serves as an In param for passing the silent payment address + * data. The index field is for when more than one address is being sent to in + * a transaction. Index is set based on the original ordering of the addresses + * and used to return the generated outputs matching the original ordering. + * When more than one recipient is used the recipient array will be sorted in + * place as part of generating the outputs, but the generated outputs will be + * returned in the original ordering specified by the index to ensure the + * caller is able to match up the generated outputs to the correct silent + * payment address (e.g. to be able to assign the correct amounts to the + * correct generated outputs in the final transaction). + */ +typedef struct { + secp256k1_pubkey scan_pubkey; + secp256k1_pubkey spend_pubkey; + size_t index; +} secp256k1_silentpayments_recipient; + +/** Create Silent Payment outputs for recipient(s). + * + * Given a list of n private keys a_1...a_n (one for each silent payment + * eligible input to spend), a serialized outpoint, and a list of recipients, + * create the taproot outputs. + * + * `outpoint_smallest36` refers to the smallest outpoint lexicographically + * from the transaction inputs (both silent payments eligible and non-eligible + * inputs). This value MUST be the smallest outpoint out of all of the + * transaction inputs, otherwise the recipient will be unable to find the + * payment. + * + * 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 the seckeys correspond to x-only + * outputs or not. + * + * Returns: 1 if creation of outputs was successful. 0 if an error occured. + * Args: ctx: pointer to a context object + * Out: generated_outputs: pointer to an array of pointers to xonly pubkeys, + * one per recipient. + * The order of outputs here matches the original + * ordering of the recipients array. + * In: recipients: pointer to an array of pointers to silent payment + * recipients, where each recipient is a scan public + * key, a spend public key, and an index indicating + * its position in the original ordering. The + * recipient array will be sorted in place, but + * generated outputs are saved in the + * `generated_outputs` array to match the ordering + * from the index field. This ensures the caller is + * able to match the generated outputs to the + * correct silent payment addresses. The same + * recipient can be passed multiple times to create + * multiple outputs for the same recipient. + * n_recipients: the number of recipients. This is equal to the + * total number of outputs to be generated as each + * recipient may passed multiple times to generate + * multiple outputs for the same recipient + * outpoint_smallest36: serialized smallest outpoint (lexicographically) + * from the transaction inputs + * taproot_seckeys: pointer to an array of pointers to 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 + * plain_seckeys: pointer to an array of pointers to 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 + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_create_outputs( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey **generated_outputs, + const secp256k1_silentpayments_recipient **recipients, + size_t n_recipients, + const unsigned char *outpoint_smallest36, + const secp256k1_keypair * const *taproot_seckeys, + size_t n_taproot_seckeys, + const unsigned char * const *plain_seckeys, + size_t n_plain_seckeys +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); + #ifdef __cplusplus } #endif diff --git a/src/modules/silentpayments/main_impl.h b/src/modules/silentpayments/main_impl.h index f8ccdd7baa..c82f785060 100644 --- a/src/modules/silentpayments/main_impl.h +++ b/src/modules/silentpayments/main_impl.h @@ -7,10 +7,249 @@ #define SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H #include "../../../include/secp256k1.h" +#include "../../../include/secp256k1_extrakeys.h" #include "../../../include/secp256k1_silentpayments.h" -/* TODO: implement functions for sender side. */ +/** Sort an array of silent payment recipients. This is used to group recipients by scan pubkey to + * ensure the correct values of k are used when creating multiple outputs for a recipient. */ +static int secp256k1_silentpayments_recipient_sort_cmp(const void* pk1, const void* pk2, void *ctx) { + return secp256k1_ec_pubkey_cmp((secp256k1_context *)ctx, + &(*(const secp256k1_silentpayments_recipient **)pk1)->scan_pubkey, + &(*(const secp256k1_silentpayments_recipient **)pk2)->scan_pubkey + ); +} -/* TODO: implement functions for receiver side. */ +static void secp256k1_silentpayments_recipient_sort(const secp256k1_context* ctx, const secp256k1_silentpayments_recipient **recipients, size_t n_recipients) { + + /* Suppress wrong warning (fixed in MSVC 19.33) */ + #if defined(_MSC_VER) && (_MSC_VER < 1933) + #pragma warning(push) + #pragma warning(disable: 4090) + #endif + + secp256k1_hsort(recipients, n_recipients, sizeof(*recipients), secp256k1_silentpayments_recipient_sort_cmp, (void *)ctx); + + #if defined(_MSC_VER) && (_MSC_VER < 1933) + #pragma warning(pop) + #endif +} + +/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Inputs". */ +static void secp256k1_silentpayments_sha256_init_inputs(secp256k1_sha256* hash) { + secp256k1_sha256_initialize(hash); + hash->s[0] = 0xd4143ffcul; + hash->s[1] = 0x012ea4b5ul; + hash->s[2] = 0x36e21c8ful; + hash->s[3] = 0xf7ec7b54ul; + hash->s[4] = 0x4dd4e2acul; + hash->s[5] = 0x9bcaa0a4ul; + hash->s[6] = 0xe244899bul; + hash->s[7] = 0xcd06903eul; + + hash->bytes = 64; +} + +static void secp256k1_silentpayments_calculate_input_hash(unsigned char *input_hash, const unsigned char *outpoint_smallest36, secp256k1_ge *pubkey_sum) { + secp256k1_sha256 hash; + unsigned char pubkey_sum_ser[33]; + size_t len; + int ret; + + secp256k1_silentpayments_sha256_init_inputs(&hash); + secp256k1_sha256_write(&hash, outpoint_smallest36, 36); + ret = secp256k1_eckey_pubkey_serialize(pubkey_sum, pubkey_sum_ser, &len, 1); + VERIFY_CHECK(ret && len == sizeof(pubkey_sum_ser)); + (void)ret; + secp256k1_sha256_write(&hash, pubkey_sum_ser, sizeof(pubkey_sum_ser)); + secp256k1_sha256_finalize(&hash, input_hash); +} + +static void secp256k1_silentpayments_create_shared_secret(unsigned char *shared_secret33, const secp256k1_scalar *secret_component, const secp256k1_ge *public_component) { + secp256k1_gej ss_j; + secp256k1_ge ss; + size_t len; + int ret; + + /* Compute shared_secret = tweaked_secret_component * Public_component */ + secp256k1_ecmult_const(&ss_j, public_component, secret_component); + secp256k1_ge_set_gej(&ss, &ss_j); + /* This can only fail if the shared secret is the point at infinity, which should be + * impossible at this point, considering we have already validated the public key and + * the secret key being used + */ + ret = secp256k1_eckey_pubkey_serialize(&ss, shared_secret33, &len, 1); + VERIFY_CHECK(ret && len == 33); + (void)ret; + /* While not technically "secret" data, explicitly clear the shared secret since leaking this would allow an attacker + * to identify the resulting transaction as a silent payments transaction and potentially link the transaction + * back to the silent payment address + */ + secp256k1_ge_clear(&ss); + secp256k1_gej_clear(&ss_j); +} + +/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/SharedSecret". */ +static void secp256k1_silentpayments_sha256_init_sharedsecret(secp256k1_sha256* hash) { + secp256k1_sha256_initialize(hash); + hash->s[0] = 0x88831537ul; + hash->s[1] = 0x5127079bul; + hash->s[2] = 0x69c2137bul; + hash->s[3] = 0xab0303e6ul; + hash->s[4] = 0x98fa21faul; + hash->s[5] = 0x4a888523ul; + hash->s[6] = 0xbd99daabul; + hash->s[7] = 0xf25e5e0aul; + + hash->bytes = 64; +} + +static void secp256k1_silentpayments_create_t_k(secp256k1_scalar *t_k_scalar, const unsigned char *shared_secret33, unsigned int k) { + secp256k1_sha256 hash; + unsigned char hash_ser[32]; + unsigned char k_serialized[4]; + + /* Compute t_k = hash(shared_secret || ser_32(k)) [sha256 with tag "BIP0352/SharedSecret"] */ + secp256k1_silentpayments_sha256_init_sharedsecret(&hash); + secp256k1_sha256_write(&hash, shared_secret33, 33); + secp256k1_write_be32(k_serialized, k); + secp256k1_sha256_write(&hash, k_serialized, sizeof(k_serialized)); + secp256k1_sha256_finalize(&hash, hash_ser); + secp256k1_scalar_set_b32(t_k_scalar, hash_ser, NULL); + /* While not technically "secret" data, explicitly clear hash_ser since leaking this would allow an attacker + * to identify the resulting transaction as a silent payments transaction and potentially link the transaction + * back to the silent payment address + */ + memset(hash_ser, 0, sizeof(hash_ser)); +} + +static int secp256k1_silentpayments_create_output_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *P_output_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey *recipient_spend_pubkey, unsigned int k) { + secp256k1_ge P_output_ge; + secp256k1_scalar t_k_scalar; + int ret; + + /* Calculate and return P_output_xonly = B_spend + t_k * G + * This will fail if B_spend is the point at infinity or if + * B_spend + t_k*G is the point at infinity. + */ + secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret33, k); + ret = secp256k1_pubkey_load(ctx, &P_output_ge, recipient_spend_pubkey); + ret &= secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar); + secp256k1_xonly_pubkey_save(P_output_xonly, &P_output_ge); + + /* While not technically "secret" data, explicitly clear t_k since leaking this would allow an attacker + * to identify the resulting transaction as a silent payments transaction and potentially link the transaction + * back to the silent payment address + */ + secp256k1_scalar_clear(&t_k_scalar); + return ret; +} + +int secp256k1_silentpayments_sender_create_outputs( + const secp256k1_context *ctx, + secp256k1_xonly_pubkey **generated_outputs, + const secp256k1_silentpayments_recipient **recipients, + size_t n_recipients, + const unsigned char *outpoint_smallest36, + const secp256k1_keypair * const *taproot_seckeys, + size_t n_taproot_seckeys, + const unsigned char * const *plain_seckeys, + size_t n_plain_seckeys +) { + size_t i, k; + secp256k1_scalar a_sum_scalar, addend, input_hash_scalar; + secp256k1_ge A_sum_ge; + secp256k1_gej A_sum_gej; + unsigned char input_hash[32]; + unsigned char shared_secret[33]; + secp256k1_silentpayments_recipient last_recipient; + int overflow = 0; + int ret = 1; + + /* Sanity check inputs. */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(generated_outputs != NULL); + ARG_CHECK(recipients != NULL); + ARG_CHECK(n_recipients > 0); + ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL)); + if (taproot_seckeys != NULL) { + ARG_CHECK(n_taproot_seckeys > 0); + } else { + ARG_CHECK(n_taproot_seckeys == 0); + } + if (plain_seckeys != NULL) { + ARG_CHECK(n_plain_seckeys > 0); + } else { + ARG_CHECK(n_plain_seckeys == 0); + } + ARG_CHECK(outpoint_smallest36 != NULL); + /* ensure the index field is set correctly */ + for (i = 0; i < n_recipients; i++) { + ARG_CHECK(recipients[i]->index == i); + } + + /* Compute input private keys sum: a_sum = a_1 + a_2 + ... + a_n */ + a_sum_scalar = secp256k1_scalar_zero; + for (i = 0; i < n_plain_seckeys; i++) { + /* TODO: in other places where _set_b32_seckey is called, its normally followed by a _cmov call + * Do we need that here and if so, is it better to call it after the loop is finished? + */ + ret &= secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]); + secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend); + } + /* 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++) { + secp256k1_ge addend_point; + /* TODO: why don't we need _cmov here after calling keypair_load? Because the ret is declassified? */ + ret &= secp256k1_keypair_load(ctx, &addend, &addend_point, taproot_seckeys[i]); + if (secp256k1_fe_is_odd(&addend_point.y)) { + secp256k1_scalar_negate(&addend, &addend); + } + secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend); + } + /* If there are any failures in loading/summing up the secret keys, fail early */ + if (!ret || secp256k1_scalar_is_zero(&a_sum_scalar)) { + return 0; + } + /* Compute input_hash = hash(outpoint_L || (a_sum * G)) */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &A_sum_gej, &a_sum_scalar); + secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej); + + /* Calculate the input hash and tweak a_sum, i.e., a_sum_tweaked = a_sum * input_hash */ + secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge); + secp256k1_scalar_set_b32(&input_hash_scalar, input_hash, &overflow); + ret &= !overflow; + secp256k1_scalar_mul(&a_sum_scalar, &a_sum_scalar, &input_hash_scalar); + secp256k1_silentpayments_recipient_sort(ctx, recipients, n_recipients); + last_recipient = *recipients[0]; + k = 0; + for (i = 0; i < n_recipients; i++) { + if ((i == 0) || (secp256k1_ec_pubkey_cmp(ctx, &last_recipient.scan_pubkey, &recipients[i]->scan_pubkey) != 0)) { + /* If we are on a different scan pubkey, its time to recreate the the shared secret and reset k to 0. + * It's very unlikely tha the scan public key is invalid by this point, since this means the caller would + * have created the _silentpayments_recipient object incorrectly, but just to be sure we still check that + * the public key is valid. + */ + secp256k1_ge pk; + ret &= secp256k1_pubkey_load(ctx, &pk, &recipients[i]->scan_pubkey); + if (!ret) break; + secp256k1_silentpayments_create_shared_secret(shared_secret, &a_sum_scalar, &pk); + k = 0; + } + ret &= secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret, &recipients[i]->spend_pubkey, k); + k++; + last_recipient = *recipients[i]; + } + /* Explicitly clear variables containing secret data */ + secp256k1_scalar_clear(&addend); + secp256k1_scalar_clear(&a_sum_scalar); + + /* While technically not "secret data," explicitly clear the shared secret since leaking this + * could result in a third party being able to identify the transaction as a silent payments transaction + * and potentially link the transaction back to a silent payment address + */ + memset(&shared_secret, 0, sizeof(shared_secret)); + return ret; +} #endif diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h new file mode 100644 index 0000000000..29d5c99a39 --- /dev/null +++ b/src/modules/silentpayments/tests_impl.h @@ -0,0 +1,233 @@ +/*********************************************************************** + * 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" +#include "include/secp256k1.h" + +/** Constants + * + * Addresses: scan and spend public keys for Bob and Carol + * Seckey: secret key for Alice + * Outputs: generated outputs from Alice's secret key and Bob/Carol's + * scan public keys + * Smallest Outpoint: smallest outpoint lexicographically from the transaction + * orderc: a scalar which overflows the secp256k1 group order + * Malformed Seckey: a seckey that is all zeros + * + * The values themselves are not important. + */ +static unsigned char ORDERC[32] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, + 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41 +}; +static unsigned char MALFORMED_SECKEY[32] = { 0x00 }; +static unsigned char BOB_ADDRESS[2][33] = { + { + 0x02,0x15,0x40,0xae,0xa8,0x97,0x54,0x7a, + 0xd4,0x39,0xb4,0xe0,0xf6,0x09,0xe5,0xf0, + 0xfa,0x63,0xde,0x89,0xab,0x11,0xed,0xe3, + 0x1e,0x8c,0xde,0x4b,0xe2,0x19,0x42,0x5f,0x23 + }, + { + 0x02,0x3e,0xff,0xf8,0x18,0x51,0x65,0xea, + 0x63,0xa9,0x92,0xb3,0x9f,0x31,0xd8,0xfd, + 0x8e,0x0e,0x64,0xae,0xf9,0xd3,0x88,0x07, + 0x34,0x97,0x37,0x14,0xa5,0x3d,0x83,0x11,0x8d + } +}; +static unsigned char CAROL_ADDRESS[2][33] = { + { + 0x03,0xbb,0xc6,0x3f,0x12,0x74,0x5d,0x3b, + 0x9e,0x9d,0x24,0xc6,0xcd,0x7a,0x1e,0xfe, + 0xba,0xd0,0xa7,0xf4,0x69,0x23,0x2f,0xbe, + 0xcf,0x31,0xfb,0xa7,0xb4,0xf7,0xdd,0xed,0xa8 + }, + { + 0x03,0x81,0xeb,0x9a,0x9a,0x9e,0xc7,0x39, + 0xd5,0x27,0xc1,0x63,0x1b,0x31,0xb4,0x21, + 0x56,0x6f,0x5c,0x2a,0x47,0xb4,0xab,0x5b, + 0x1f,0x6a,0x68,0x6d,0xfb,0x68,0xea,0xb7,0x16 + } +}; +static unsigned char BOB_OUTPUT[32] = { + 0x46,0x0d,0x68,0x08,0x65,0x64,0x45,0xee, + 0x4d,0x4e,0xc0,0x8e,0xba,0x8a,0x66,0xea, + 0x66,0x8e,0x4e,0x12,0x98,0x9a,0x0e,0x60, + 0x4b,0x5c,0x36,0x0e,0x43,0xf5,0x5a,0xfa +}; +static unsigned char CAROL_OUTPUT_ONE[32] = { + 0xb7,0xf3,0xc6,0x79,0x30,0x4a,0xef,0x8c, + 0xc0,0xc7,0x61,0xf1,0x00,0x99,0xdd,0x7b, + 0x20,0x65,0x20,0xd7,0x11,0x6f,0xb7,0x91, + 0xee,0x74,0x54,0xa2,0xfc,0x22,0x79,0xf4 +}; +static unsigned char CAROL_OUTPUT_TWO[32] = { + 0x4b,0x81,0x34,0x5d,0x53,0x89,0xba,0xa3, + 0xd8,0x93,0xe2,0xfb,0xe7,0x08,0xdd,0x6d, + 0x82,0xdc,0xd8,0x49,0xab,0x03,0xc1,0xdb, + 0x68,0xbe,0xc7,0xe9,0x2a,0x45,0xfa,0xc5 +}; +static unsigned char SMALLEST_OUTPOINT[36] = { + 0x16,0x9e,0x1e,0x83,0xe9,0x30,0x85,0x33,0x91, + 0xbc,0x6f,0x35,0xf6,0x05,0xc6,0x75,0x4c,0xfe, + 0xad,0x57,0xcf,0x83,0x87,0x63,0x9d,0x3b,0x40, + 0x96,0xc5,0x4f,0x18,0xf4,0x00,0x00,0x00,0x00 +}; +static unsigned char ALICE_SECKEY[32] = { + 0xea,0xdc,0x78,0x16,0x5f,0xf1,0xf8,0xea, + 0x94,0xad,0x7c,0xfd,0xc5,0x49,0x90,0x73, + 0x8a,0x4c,0x53,0xf6,0xe0,0x50,0x7b,0x42, + 0x15,0x42,0x01,0xb8,0xe5,0xdf,0xf3,0xb1 +}; + +static void test_recipient_sort_helper(unsigned char (*sp_addresses[3])[2][33], unsigned char (*sp_outputs[3])[32]) { + unsigned char const *seckey_ptrs[1]; + secp256k1_silentpayments_recipient recipients[3]; + const secp256k1_silentpayments_recipient *recipient_ptrs[3]; + secp256k1_xonly_pubkey generated_outputs[3]; + secp256k1_xonly_pubkey *generated_output_ptrs[3]; + unsigned char xonly_ser[32]; + size_t i; + int ret; + + seckey_ptrs[0] = ALICE_SECKEY; + for (i = 0; i < 3; i++) { + CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].scan_pubkey, (*sp_addresses[i])[0], 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].spend_pubkey,(*sp_addresses[i])[1], 33)); + recipients[i].index = i; + recipient_ptrs[i] = &recipients[i]; + generated_output_ptrs[i] = &generated_outputs[i]; + } + ret = secp256k1_silentpayments_sender_create_outputs(CTX, + generated_output_ptrs, + recipient_ptrs, 3, + SMALLEST_OUTPOINT, + NULL, 0, + seckey_ptrs, 1 + ); + CHECK(ret); + for (i = 0; i < 3; i++) { + secp256k1_xonly_pubkey_serialize(CTX, xonly_ser, &generated_outputs[i]); + CHECK(secp256k1_memcmp_var(xonly_ser, (*sp_outputs[i]), 32) == 0); + } +} + +static void test_recipient_sort(void) { + unsigned char (*sp_addresses[3])[2][33]; + unsigned char (*sp_outputs[3])[32]; + + /* With a fixed set of addresses and a fixed set of inputs, + * test that we always get the same outputs, regardless of the ordering + * of the recipients + */ + sp_addresses[0] = &CAROL_ADDRESS; + sp_addresses[1] = &BOB_ADDRESS; + sp_addresses[2] = &CAROL_ADDRESS; + + sp_outputs[0] = &CAROL_OUTPUT_ONE; + sp_outputs[1] = &BOB_OUTPUT; + sp_outputs[2] = &CAROL_OUTPUT_TWO; + test_recipient_sort_helper(sp_addresses, sp_outputs); + + sp_addresses[0] = &CAROL_ADDRESS; + sp_addresses[1] = &CAROL_ADDRESS; + sp_addresses[2] = &BOB_ADDRESS; + + sp_outputs[0] = &CAROL_OUTPUT_ONE; + sp_outputs[1] = &CAROL_OUTPUT_TWO; + sp_outputs[2] = &BOB_OUTPUT; + test_recipient_sort_helper(sp_addresses, sp_outputs); + + sp_addresses[0] = &BOB_ADDRESS; + sp_addresses[1] = &CAROL_ADDRESS; + sp_addresses[2] = &CAROL_ADDRESS; + + /* Note: in this case, the second output for Carol comes before the first. + * This is because heapsort is an unstable sorting algorithm, i.e., the ordering + * of identical elements is not guaranteed to be preserved + */ + sp_outputs[0] = &BOB_OUTPUT; + sp_outputs[1] = &CAROL_OUTPUT_TWO; + sp_outputs[2] = &CAROL_OUTPUT_ONE; + test_recipient_sort_helper(sp_addresses, sp_outputs); +} + +static void test_send_api(void) { + unsigned char (*sp_addresses[2])[2][33]; + unsigned char const *p[1]; + secp256k1_keypair const *t[1]; + secp256k1_silentpayments_recipient r[2]; + const secp256k1_silentpayments_recipient *rp[2]; + secp256k1_xonly_pubkey o[2]; + secp256k1_xonly_pubkey *op[2]; + secp256k1_keypair taproot; + size_t i; + + /* Set up Bob and Carol as the recipients */ + sp_addresses[0] = &BOB_ADDRESS; + sp_addresses[1] = &CAROL_ADDRESS; + for (i = 0; i < 2; i++) { + CHECK(secp256k1_ec_pubkey_parse(CTX, &r[i].scan_pubkey, (*sp_addresses[i])[0], 33)); + CHECK(secp256k1_ec_pubkey_parse(CTX, &r[i].spend_pubkey,(*sp_addresses[i])[1], 33)); + /* Set the index value incorrectly */ + r[i].index = 0; + rp[i] = &r[i]; + op[i] = &o[i]; + } + /* Set up a taproot key and a plain key for Alice */ + CHECK(secp256k1_keypair_create(CTX, &taproot, ALICE_SECKEY)); + t[0] = &taproot; + p[0] = ALICE_SECKEY; + + /* Fails if the index is set incorrectly */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1)); + + /* Set the index correctly for the next tests */ + for (i = 0; i < 2; i++) { + r[i].index = i; + } + CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1)); + + /* Check that null arguments are handled */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, NULL, rp, 2, SMALLEST_OUTPOINT, t, 1, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, NULL, 2, SMALLEST_OUTPOINT, t, 1, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, NULL, t, 1, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 1, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 1, NULL, 1)); + + /* Check that array arguments are verified */ + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, NULL, 0)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 0, SMALLEST_OUTPOINT, NULL, 0, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 0, p, 1)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, t, 1, p, 0)); + + /* Create malformed keys for Alice by using a key that will overflow */ + p[0] = ORDERC; + CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1) == 0); + /* Create malformed keys for Alice by using a zero'd seckey */ + p[0] = MALFORMED_SECKEY; + CHECK(secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1) == 0); + /* Create malformed recipients by setting all of the public key bytes to zero. + * Realistically, this would never happen since a bad public key would get caught when + * trying to parse the public key with _ec_pubkey_parse + */ + p[0] = ALICE_SECKEY; + memset(&r[1].spend_pubkey.data, 0, sizeof(secp256k1_pubkey)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 2, SMALLEST_OUTPOINT, NULL, 0, p, 1)); + memset(&r[0].scan_pubkey.data, 0, sizeof(secp256k1_pubkey)); + CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 1, SMALLEST_OUTPOINT, NULL, 0, p, 1)); +} + +void run_silentpayments_tests(void) { + test_recipient_sort(); + test_send_api(); +} + +#endif diff --git a/src/tests.c b/src/tests.c index 70c15f870b..3f4012e274 100644 --- a/src/tests.c +++ b/src/tests.c @@ -7423,6 +7423,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)]; @@ -7775,6 +7779,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();