From ea9236a6b7c9bfec60fa3fb2dda94d8b164edc55 Mon Sep 17 00:00:00 2001 From: stratospher <44024636+stratospher@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:14:06 +0530 Subject: [PATCH] add tests for creating and verifying proofs - in examples/silentpayments.c - sender now generates outputs with proof and verifies the proof - proof can be serialised to bytes and sent to recipient - bytes can be parsed back to proof by recipient as well - recipient can verify proof - in tests_recipients_helper, run_silentpayments_test_vector_send - along with output checks, generate DLEQ proofs and verify them add detailed examples showing parsing/serialisation --- examples/silentpayments.c | 99 ++++++++++++++++++++++--- src/modules/silentpayments/tests_impl.h | 52 ++++++++++++- 2 files changed, 138 insertions(+), 13 deletions(-) diff --git a/examples/silentpayments.c b/examples/silentpayments.c index 4b5bc03b96..5b777f7a9b 100644 --- a/examples/silentpayments.c +++ b/examples/silentpayments.c @@ -111,13 +111,14 @@ const unsigned char* label_lookup( } int main(void) { - enum { N_INPUTS = 2, N_OUTPUTS = 3 }; + enum { N_INPUTS = 2, N_OUTPUTS = 3, N_RECIPIENTS = 2 }; unsigned char randomize[32]; unsigned char xonly_print[32]; secp256k1_xonly_pubkey tx_inputs[N_INPUTS]; secp256k1_xonly_pubkey tx_outputs[N_OUTPUTS]; int ret; size_t i; + unsigned char dleq_proof[N_RECIPIENTS][64]; /* Before we can call actual API functions, we need to create a "context" */ secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); @@ -218,14 +219,63 @@ int main(void) { for (i = 0; i < N_OUTPUTS; i++) { generated_output_ptrs[i] = &generated_outputs[i]; } - ret = secp256k1_silentpayments_sender_create_outputs(ctx, - generated_output_ptrs, - recipient_ptrs, N_OUTPUTS, - smallest_outpoint, - sender_seckey_ptrs, N_INPUTS, - NULL, 0 - ); - assert(ret); + + /* Sender can perform 1 of the following options: + * Option 1: generate outputs without DLEQ proofs + ret = secp256k1_silentpayments_sender_create_outputs(ctx, + generated_output_ptrs, + recipient_ptrs, N_OUTPUTS, + smallest_outpoint, + sender_seckey_ptrs, N_INPUTS, + NULL, 0 + ); + assert(ret); + */ + { + /* Option 2: generate outputs with DLEQ proofs*/ + secp256k1_silentpayments_public_data public_data; + const secp256k1_xonly_pubkey *tx_input_ptrs[N_INPUTS]; + size_t n_dleq_size; + secp256k1_silentpayments_dleq_data dleq_data[N_RECIPIENTS]; + secp256k1_silentpayments_dleq_data *dleq_data_ptrs[N_RECIPIENTS]; + for (i = 0; i < N_RECIPIENTS; i++) { + dleq_data_ptrs[i] = &dleq_data[i]; + } + for (i = 0; i < N_INPUTS; i++) { + tx_input_ptrs[i] = &tx_inputs[i]; + } + ret = secp256k1_silentpayments_recipient_public_data_create(ctx, &public_data, smallest_outpoint, + tx_input_ptrs, N_INPUTS, NULL, 0); + assert(ret); + + ret = secp256k1_silentpayments_sender_create_outputs_with_proof(ctx, + generated_output_ptrs, dleq_data_ptrs, &n_dleq_size, + recipient_ptrs, N_OUTPUTS, + smallest_outpoint, + sender_seckey_ptrs, N_INPUTS, + NULL, 0 + ); + assert(n_dleq_size == N_RECIPIENTS); + assert(ret); + /* Ensure that outputs are generated correctly at the sender side by verifying the DLEQ proof */ + for (i = 0; i < N_RECIPIENTS; i++) { + /* Serialized form of proof can be sent from 1 sender side device to another sender side device. + * ex: hardware wallet (which can do ECDH + proof calculation) to wallet application. */ + unsigned char ss_proof_index_bytes[33 + 64 + 4]; + secp256k1_silentpayments_dleq_data data; + secp256k1_silentpayments_dleq_data_serialize(ss_proof_index_bytes, &dleq_data[i]); + /* Parse the serialized proof on the second device. (ex: wallet application) */ + secp256k1_silentpayments_dleq_data_parse(&data, ss_proof_index_bytes); + /* Proof verification can be done on the second device. */ + ret = secp256k1_silentpayments_verify_proof(ctx, data.shared_secret, data.proof, + &recipients[data.index].scan_pubkey, + &public_data); + assert(ret); + /* Store proof to send to different receivers (Bob, Carol) later. */ + memcpy(dleq_proof[i], ss_proof_index_bytes + 33, 64); + } + } + printf("Alice created the following outputs for Bob and Carol: \n"); for (i = 0; i < N_OUTPUTS; i++) { printf(" "); @@ -400,6 +450,25 @@ int main(void) { ); print_hex(xonly_print, sizeof(xonly_print)); } + { + /* Optionally, Bob can use DLEQ proof to prove ownership of his address without revealing private keys + * DLEQ proof verification needs proof, input pubkey sum, Bob's scan pubkey and shared secret as inputs. */ + unsigned char shared_secret[33]; + secp256k1_pubkey scan_pubkey; + /* 1. Get Bob's scan pubkey */ + ret = secp256k1_ec_pubkey_parse(ctx, &scan_pubkey, bob_address[0], 33); + assert(ret); + /* 2. Compute input pubkey sum */ + ret = secp256k1_silentpayments_recipient_public_data_parse(ctx, &public_data, light_client_data33); + assert(ret); + /* 3. Bob computes shared secret */ + ret = secp256k1_silentpayments_recipient_create_shared_secret(ctx, shared_secret, bob_scan_key, + &public_data); + assert(ret); + /* 4. Use proof we obtained from Alice for verification */ + ret &= secp256k1_silentpayments_verify_proof(ctx, shared_secret, dleq_proof[0], &scan_pubkey, &public_data); + assert(ret); + } } { /*** Scanning as a light client (Carol) *** @@ -494,6 +563,18 @@ int main(void) { printf(" "); print_hex(ser_found_outputs[i], 32); } + { + /* Optionally, Carol can use DLEQ proof to prove ownership of her address without revealing private keys + * DLEQ proof verification needs proof, input pubkey sum, Carol's scan pubkey and shared secret as inputs. */ + /* 1. Get Carol's scan pubkey */ + secp256k1_pubkey scan_pubkey; + ret = secp256k1_ec_pubkey_parse(ctx, &scan_pubkey, carol_address[0], 33); + assert(ret); + /* 2. Input pubkey sum and shared secret already computed at this point, so verify_proof directly */ + /* 3. Use proof we obtained from Alice for verification */ + ret &= secp256k1_silentpayments_verify_proof(ctx, shared_secret, dleq_proof[1], &scan_pubkey, &public_data); + assert(ret); + } } } diff --git a/src/modules/silentpayments/tests_impl.h b/src/modules/silentpayments/tests_impl.h index d87874930e..cad55e2a77 100644 --- a/src/modules/silentpayments/tests_impl.h +++ b/src/modules/silentpayments/tests_impl.h @@ -10,6 +10,7 @@ #include "../../../src/modules/silentpayments/dleq_impl.h" #include "../../../src/modules/silentpayments/vectors.h" #include "include/secp256k1.h" +#include /** Constants * @@ -129,6 +130,9 @@ static void test_recipient_sort_helper(unsigned char (*sp_addresses[3])[2][33], const secp256k1_silentpayments_recipient *recipient_ptrs[3]; secp256k1_xonly_pubkey generated_outputs[3]; secp256k1_xonly_pubkey *generated_output_ptrs[3]; + secp256k1_silentpayments_dleq_data dleq_data[2]; + secp256k1_silentpayments_dleq_data *dleq_data_ptrs[2]; + size_t n_dleq_size; unsigned char xonly_ser[32]; size_t i; int ret; @@ -140,15 +144,32 @@ static void test_recipient_sort_helper(unsigned char (*sp_addresses[3])[2][33], recipients[i].index = i; recipient_ptrs[i] = &recipients[i]; generated_output_ptrs[i] = &generated_outputs[i]; + if (i != 2){ + dleq_data_ptrs[i] = &dleq_data[i]; + } } - ret = secp256k1_silentpayments_sender_create_outputs(CTX, - generated_output_ptrs, + ret = secp256k1_silentpayments_sender_create_outputs_with_proof(CTX, + generated_output_ptrs, dleq_data_ptrs, &n_dleq_size, recipient_ptrs, 3, SMALLEST_OUTPOINT, NULL, 0, seckey_ptrs, 1 ); CHECK(ret); + { + secp256k1_pubkey pk; + secp256k1_xonly_pubkey xonly_pk; + secp256k1_silentpayments_public_data public_data; + const secp256k1_xonly_pubkey *tx_input_ptr = &xonly_pk; + CHECK(secp256k1_ec_pubkey_create(CTX, &pk, seckey_ptrs[0]) == 1); + CHECK(secp256k1_xonly_pubkey_from_pubkey(CTX, &xonly_pk, NULL, &pk) == 1); + CHECK(secp256k1_silentpayments_recipient_public_data_create(CTX, &public_data, SMALLEST_OUTPOINT, &tx_input_ptr, 1, NULL, 0) == 1); + for (i = 0; i < 2; i++) { + CHECK(secp256k1_silentpayments_verify_proof(CTX, dleq_data_ptrs[i]->shared_secret, dleq_data_ptrs[i]->proof, + &recipients[dleq_data_ptrs[i]->index].scan_pubkey, + &public_data) == 1); + } + } 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); @@ -373,9 +394,17 @@ void run_silentpayments_test_vector_send(const struct bip352_test_vector *test) secp256k1_keypair taproot_keypairs[MAX_INPUTS_PER_TEST_CASE]; secp256k1_keypair const *taproot_keypair_ptrs[MAX_INPUTS_PER_TEST_CASE]; unsigned char const *plain_seckeys[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_pubkey plain_pubkeys[MAX_INPUTS_PER_TEST_CASE]; + const secp256k1_pubkey *plain_pubkey_ptrs[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_xonly_pubkey xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE]; + const secp256k1_xonly_pubkey *xonly_pubkey_ptrs[MAX_INPUTS_PER_TEST_CASE]; + secp256k1_silentpayments_dleq_data dleq_data[MAX_OUTPUTS_PER_TEST_CASE]; + secp256k1_silentpayments_dleq_data *dleq_data_ptrs[MAX_OUTPUTS_PER_TEST_CASE]; unsigned char created_output[32]; size_t i, j, k; int match, ret; + size_t n_dleq_size; + secp256k1_silentpayments_public_data public_data; /* Check that sender creates expected outputs */ for (i = 0; i < test->num_outputs; i++) { @@ -384,16 +413,21 @@ void run_silentpayments_test_vector_send(const struct bip352_test_vector *test) recipients[i].index = i; recipient_ptrs[i] = &recipients[i]; generated_output_ptrs[i] = &generated_outputs[i]; + dleq_data_ptrs[i] = &dleq_data[i]; } for (i = 0; i < test->num_plain_inputs; i++) { plain_seckeys[i] = test->plain_seckeys[i]; + CHECK(secp256k1_ec_pubkey_create(CTX, &plain_pubkeys[i], plain_seckeys[i]) == 1); + plain_pubkey_ptrs[i] = &plain_pubkeys[i]; } for (i = 0; i < test->num_taproot_inputs; i++) { CHECK(secp256k1_keypair_create(CTX, &taproot_keypairs[i], test->taproot_seckeys[i])); taproot_keypair_ptrs[i] = &taproot_keypairs[i]; + CHECK(secp256k1_keypair_xonly_pub(CTX, &xonly_pubkeys[i], NULL, &taproot_keypairs[i]) == 1); + xonly_pubkey_ptrs[i] = &xonly_pubkeys[i]; } - ret = secp256k1_silentpayments_sender_create_outputs(CTX, - generated_output_ptrs, + ret = secp256k1_silentpayments_sender_create_outputs_with_proof(CTX, + generated_output_ptrs, dleq_data_ptrs, &n_dleq_size, recipient_ptrs, test->num_outputs, test->outpoint_smallest, @@ -427,6 +461,16 @@ void run_silentpayments_test_vector_send(const struct bip352_test_vector *test) } } CHECK(match); + + /* Verify generated DLEQ proofs*/ + CHECK(secp256k1_silentpayments_recipient_public_data_create(CTX, &public_data, test->outpoint_smallest, + test->num_taproot_inputs > 0 ? xonly_pubkey_ptrs : NULL, test->num_taproot_inputs, + test->num_plain_inputs > 0 ? plain_pubkey_ptrs : NULL, test->num_plain_inputs) == 1); + for (i = 0; i < n_dleq_size; i++) { + CHECK(secp256k1_silentpayments_verify_proof(CTX, dleq_data_ptrs[i]->shared_secret, dleq_data_ptrs[i]->proof, + &recipients[dleq_data_ptrs[i]->index].scan_pubkey, + &public_data) == 1); + } } void run_silentpayments_test_vector_receive(const struct bip352_test_vector *test) {