Skip to content

Commit

Permalink
add tests for creating and verifying proofs
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
stratospher committed Feb 11, 2025
1 parent 2dab4e9 commit 44bf41d
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 13 deletions.
99 changes: 90 additions & 9 deletions examples/silentpayments.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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(" ");
Expand Down Expand Up @@ -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) ***
Expand Down Expand Up @@ -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);
}
}
}

Expand Down
52 changes: 48 additions & 4 deletions src/modules/silentpayments/tests_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "../../../src/modules/silentpayments/dleq_impl.h"
#include "../../../src/modules/silentpayments/vectors.h"
#include "include/secp256k1.h"
#include <assert.h>

/** Constants
*
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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++) {
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 44bf41d

Please sign in to comment.