Skip to content

Commit

Permalink
tests: add BIP-352 test vectors
Browse files Browse the repository at this point in the history
The vectors are generated with a Python script that converts the .json
file from the BIP to C code:

$ ./tools/tests_silentpayments_generate.py test_vectors.json > ./src/modules/silentpayments/vectors.h
  • Loading branch information
theStack authored and josibake committed Apr 24, 2024
1 parent 6704738 commit 9119c61
Show file tree
Hide file tree
Showing 7 changed files with 4,474 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/modules/silentpayments/Makefile.am.include
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
include_HEADERS += include/secp256k1_silentpayments.h
noinst_HEADERS += src/modules/silentpayments/main_impl.h
noinst_HEADERS += src/modules/silentpayments/tests_impl.h
noinst_HEADERS += src/modules/silentpayments/vectors.h
258 changes: 258 additions & 0 deletions src/modules/silentpayments/tests_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
/***********************************************************************
* 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 "../../../src/modules/silentpayments/vectors.h"

struct label_cache_entry {
secp256k1_pubkey label;
unsigned char label_tweak[32];
};
struct labels_cache {
const secp256k1_context *ctx;
size_t entries_used;
struct label_cache_entry entries[10];
};
struct labels_cache labels_cache;
const unsigned char* label_lookup(const secp256k1_pubkey* key, const void* cache_ptr) {
const struct labels_cache* cache = (const struct labels_cache*)cache_ptr;
size_t i;
for (i = 0; i < cache->entries_used; i++) {
if (secp256k1_ec_pubkey_cmp(cache->ctx, &cache->entries[i].label, key) == 0) {
return cache->entries[i].label_tweak;
}
}
return NULL;
}

void run_silentpayments_test_vector_send(const struct bip352_test_vector *test) {
secp256k1_silentpayments_recipient recipients[MAX_OUTPUTS_PER_TEST_CASE];
const secp256k1_silentpayments_recipient *recipient_ptrs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey generated_outputs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey *generated_output_ptrs[MAX_OUTPUTS_PER_TEST_CASE];
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];
unsigned char created_output[32];
size_t i, j;
int match;

/* Check that sender creates expected outputs */
for (i = 0; i < test->num_outputs; i++) {
CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].scan_pubkey, test->recipient_pubkeys[i].scan_pubkey, 33));
CHECK(secp256k1_ec_pubkey_parse(CTX, &recipients[i].spend_pubkey, test->recipient_pubkeys[i].spend_pubkey, 33));
recipients[i].index = i;
recipient_ptrs[i] = &recipients[i];
generated_output_ptrs[i] = &generated_outputs[i];
}
for (i = 0; i < test->num_plain_inputs; i++) {
plain_seckeys[i] = test->plain_seckeys[i];
}
for (i = 0; i < test->num_taproot_inputs; i++) {
int ret = secp256k1_keypair_create(CTX, &taproot_keypairs[i], test->taproot_seckeys[i]);
CHECK(ret);
taproot_keypair_ptrs[i] = &taproot_keypairs[i];
}
CHECK(secp256k1_silentpayments_sender_create_outputs(CTX,
generated_output_ptrs,
recipient_ptrs,
test->num_outputs,
test->outpoint_smallest,
test->num_taproot_inputs > 0 ? taproot_keypair_ptrs : NULL, test->num_taproot_inputs,
test->num_plain_inputs > 0 ? plain_seckeys : NULL, test->num_plain_inputs
));
for (i = 0; i < test->num_outputs; i++) {
CHECK(secp256k1_xonly_pubkey_serialize(CTX, created_output, &generated_outputs[i]));
match = 0;
/* Loop over both lists to ensure tests don't fail due to different orderings of outputs */
for (j = 0; j < test->num_recipient_outputs; j++) {
if (secp256k1_memcmp_var(created_output, test->recipient_outputs[j], 32) == 0) {
match = 1;
break;
}
}
CHECK(match);
}
}

void run_silentpayments_test_vector_receive(const struct bip352_test_vector *test) {
secp256k1_pubkey plain_pubkeys_objs[MAX_INPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey xonly_pubkeys_objs[MAX_INPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey tx_output_objs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_silentpayments_found_output found_output_objs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_pubkey const *plain_pubkeys[MAX_INPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey const *xonly_pubkeys[MAX_INPUTS_PER_TEST_CASE];
secp256k1_xonly_pubkey const *tx_outputs[MAX_OUTPUTS_PER_TEST_CASE];
secp256k1_silentpayments_found_output *found_outputs[MAX_OUTPUTS_PER_TEST_CASE];
unsigned char found_outputs_light_client[MAX_OUTPUTS_PER_TEST_CASE][32];
secp256k1_pubkey receiver_scan_pubkey;
secp256k1_pubkey receiver_spend_pubkey;
size_t i,j;
int match;
size_t n_found = 0;
unsigned char found_output[32];
unsigned char found_signatures[10][64];
secp256k1_silentpayments_public_data public_data, public_data_index;
unsigned char shared_secret_lightclient[33];
unsigned char light_client_data[33];


/* prepare the inputs */
{
for (i = 0; i < test->num_plain_inputs; i++) {
CHECK(secp256k1_ec_pubkey_parse(CTX, &plain_pubkeys_objs[i], test->plain_pubkeys[i], 33));
plain_pubkeys[i] = &plain_pubkeys_objs[i];
}
for (i = 0; i < test->num_taproot_inputs; i++) {
CHECK(secp256k1_xonly_pubkey_parse(CTX, &xonly_pubkeys_objs[i], test->xonly_pubkeys[i]));
xonly_pubkeys[i] = &xonly_pubkeys_objs[i];
}
CHECK(secp256k1_silentpayments_recipient_public_data_create(CTX, &public_data,
test->outpoint_smallest,
test->num_taproot_inputs > 0 ? xonly_pubkeys : NULL, test->num_taproot_inputs,
test->num_plain_inputs > 0 ? plain_pubkeys : NULL, test->num_plain_inputs
));
}
/* prepare the outputs */
{
for (i = 0; i < test->num_to_scan_outputs; i++) {
CHECK(secp256k1_xonly_pubkey_parse(CTX, &tx_output_objs[i], test->to_scan_outputs[i]));
tx_outputs[i] = &tx_output_objs[i];
}
for (i = 0; i < test->num_found_output_pubkeys; i++) {
found_outputs[i] = &found_output_objs[i];
}
}

/* scan / spend pubkeys are not in the given data of the receiver part, so let's compute them */
CHECK(secp256k1_ec_pubkey_create(CTX, &receiver_scan_pubkey, test->scan_seckey));
CHECK(secp256k1_ec_pubkey_create(CTX, &receiver_spend_pubkey, test->spend_seckey));

/* create labels cache */
labels_cache.ctx = CTX;
labels_cache.entries_used = 0;
for (i = 0; i < test->num_labels; i++) {
unsigned int m = test->label_integers[i];
struct label_cache_entry *cache_entry = &labels_cache.entries[labels_cache.entries_used];
CHECK(secp256k1_silentpayments_recipient_create_label_tweak(CTX, &cache_entry->label, cache_entry->label_tweak, test->scan_seckey, m));
labels_cache.entries_used++;
}
CHECK(secp256k1_silentpayments_recipient_scan_outputs(CTX,
found_outputs, &n_found,
tx_outputs, test->num_to_scan_outputs,
test->scan_seckey,
&public_data,
&receiver_spend_pubkey,
label_lookup, &labels_cache)
);
for (i = 0; i < n_found; i++) {
unsigned char full_seckey[32];
secp256k1_keypair keypair;
unsigned char signature[64];
const unsigned char msg32[32] = /* sha256("message") */
{0xab,0x53,0x0a,0x13,0xe4,0x59,0x14,0x98,0x2b,0x79,0xf9,0xb7,0xe3,0xfb,0xa9,0x94,
0xcf,0xd1,0xf3,0xfb,0x22,0xf7,0x1c,0xea,0x1a,0xfb,0xf0,0x2b,0x46,0x0c,0x6d,0x1d};
const unsigned char aux32[32] = /* sha256("random auxiliary data") */
{0x0b,0x3f,0xdd,0xfd,0x67,0xbf,0x76,0xae,0x76,0x39,0xee,0x73,0x5b,0x70,0xff,0x15,
0x83,0xfd,0x92,0x48,0xc0,0x57,0xd2,0x86,0x07,0xa2,0x15,0xf4,0x0b,0x0a,0x3e,0xcc};
memcpy(&full_seckey, test->spend_seckey, 32);
CHECK(secp256k1_ec_seckey_tweak_add(CTX, full_seckey, found_outputs[i]->tweak));
CHECK(secp256k1_keypair_create(CTX, &keypair, full_seckey));
CHECK(secp256k1_schnorrsig_sign32(CTX, signature, msg32, &keypair, aux32));
memcpy(found_signatures[i], signature, 64);
}

/* compare expected and scanned outputs (including calculated seckey tweaks and signatures) */
for (i = 0; i < n_found; i++) {
CHECK(secp256k1_xonly_pubkey_serialize(CTX, found_output, &found_outputs[i]->output));
match = 0;
for (j = 0; j < test->num_found_output_pubkeys; j++) {
if (secp256k1_memcmp_var(&found_output, test->found_output_pubkeys[j], 32) == 0) {
match = 1;
CHECK(secp256k1_memcmp_var(found_outputs[i]->tweak, test->found_seckey_tweaks[j], 32) == 0);
CHECK(secp256k1_memcmp_var(found_signatures[i], test->found_signatures[j], 64) == 0);
break;
}
}
CHECK(match);
}
CHECK(n_found == test->num_found_output_pubkeys);
/* Scan as a light client
* it is not recommended to use labels as a light client so here we are only
* running this on tests that do not involve labels. Primarily, this test is to
* ensure that _recipient_created_shared_secret and _create_shared_secret are the same
*/
if (test->num_labels == 0) {
CHECK(secp256k1_silentpayments_recipient_public_data_serialize(CTX, light_client_data, &public_data));
CHECK(secp256k1_silentpayments_recipient_public_data_parse(CTX, &public_data_index, light_client_data));
CHECK(secp256k1_silentpayments_recipient_create_shared_secret(CTX, shared_secret_lightclient, test->scan_seckey, &public_data_index));
n_found = 0;
{
int found = 0;
size_t k = 0;
secp256k1_xonly_pubkey potential_output;
unsigned char xonly_print[32];

while(1) {

CHECK(secp256k1_silentpayments_recipient_create_output_pubkey(CTX,
&potential_output,
shared_secret_lightclient,
&receiver_spend_pubkey,
k
));
/* At this point, we check that the utxo exists with a light client protocol.
* For this example, we'll just iterate through the list of pubkeys */
found = 0;
secp256k1_xonly_pubkey_serialize(CTX, xonly_print, &potential_output);
for (i = 0; i < test->num_to_scan_outputs; i++) {
secp256k1_xonly_pubkey_serialize(CTX, xonly_print, tx_outputs[i]);
if (secp256k1_xonly_pubkey_cmp(CTX, &potential_output, tx_outputs[i]) == 0) {
secp256k1_xonly_pubkey_serialize(CTX, found_outputs_light_client[n_found], &potential_output);
found = 1;
n_found++;
k++;
break;
}
}
if (!found) {
break;
}
}
}
CHECK(n_found == test->num_found_output_pubkeys);
for (i = 0; i < n_found; i++) {
match = 0;
for (j = 0; j < test->num_found_output_pubkeys; j++) {
if (secp256k1_memcmp_var(&found_outputs_light_client[i], test->found_output_pubkeys[j], 32) == 0) {
match = 1;
break;
}
}
CHECK(match);
}
}
}

void run_silentpayments_test_vectors(void) {
size_t i;


for (i = 0; i < sizeof(bip352_test_vectors) / sizeof(bip352_test_vectors[0]); i++) {
const struct bip352_test_vector *test = &bip352_test_vectors[i];
run_silentpayments_test_vector_send(test);
run_silentpayments_test_vector_receive(test);
}
}

void run_silentpayments_tests(void) {
run_silentpayments_test_vectors();
/* TODO: add a few manual tests here, that target the ECC-related parts of silent payments */
}

#endif
Loading

0 comments on commit 9119c61

Please sign in to comment.