Skip to content

Commit

Permalink
WIP: ct tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasnick committed Oct 17, 2024
1 parent f42e0dd commit 5816dc7
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 127 deletions.
80 changes: 80 additions & 0 deletions src/ctime_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
#include "../include/secp256k1_ellswift.h"
#endif

#ifdef ENABLE_MODULE_SILENTPAYMENTS
#include "../include/secp256k1_silentpayments.h"
#endif

static void run_tests(secp256k1_context *ctx, unsigned char *key);

int main(void) {
Expand Down Expand Up @@ -88,6 +92,26 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
unsigned char ellswift[64];
static const unsigned char prefix[64] = {'t', 'e', 's', 't'};
#endif
#ifdef ENABLE_MODULE_SILENTPAYMENTS
secp256k1_xonly_pubkey generated_output;
secp256k1_xonly_pubkey *generated_outputs[1];
secp256k1_silentpayments_recipient recipient;
const secp256k1_silentpayments_recipient *recipients[1];
unsigned char outpoint_smallest[36] = { 0 };
secp256k1_keypair taproot_seckey;
const secp256k1_keypair *taproot_seckeys[1];
const unsigned char *plain_seckeys[1];
secp256k1_silentpayments_found_output *found_outputs[1];
size_t n_found_outputs;
const secp256k1_xonly_pubkey *tx_outputs[1];
secp256k1_silentpayments_public_data public_data;
unsigned char label_tweak[32] = { 0 };
secp256k1_xonly_pubkey xonly_pubkey;
const secp256k1_xonly_pubkey *xonly_pubkeys[1];
secp256k1_pubkey plain_pubkey;
const secp256k1_pubkey *plain_pubkeys[1];
unsigned char shared_secret[33];
#endif

for (i = 0; i < 32; i++) {
msg[i] = i + 1;
Expand Down Expand Up @@ -205,5 +229,61 @@ static void run_tests(secp256k1_context *ctx, unsigned char *key) {
CHECK(ret == 1);
}

#endif

#ifdef ENABLE_MODULE_SILENTPAYMENTS
SECP256K1_CHECKMEM_DEFINE(key, 32);

generated_outputs[0] = &generated_output;

/* Initialize recipient */
CHECK(secp256k1_ec_pubkey_create(ctx, &recipient.scan_pubkey, key));
key[31] ^= 1;
CHECK(secp256k1_ec_pubkey_create(ctx, &recipient.spend_pubkey, key));
key[31] ^= (1 << 1);
recipient.index = 0;
recipients[0] = &recipient;

/* Set up secret keys */
SECP256K1_CHECKMEM_UNDEFINE(key, 32);
ret = secp256k1_keypair_create(ctx, &taproot_seckey, key);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret);
key[31] ^= (1 << 2);
taproot_seckeys[0] = &taproot_seckey;
plain_seckeys[0] = key;

ret = secp256k1_silentpayments_sender_create_outputs(ctx, generated_outputs, recipients, 1, outpoint_smallest, taproot_seckeys, 1, plain_seckeys, 1);
CHECK(ret == 1);

/* TODO: use non-confusing public key */
ret = secp256k1_silentpayments_recipient_create_label_tweak(ctx, &recipient.spend_pubkey, label_tweak, key, 0);
key[31] ^= (1 << 3);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);

CHECK(secp256k1_keypair_xonly_pub(ctx, &xonly_pubkey, NULL, &taproot_seckey));
SECP256K1_CHECKMEM_DEFINE(&xonly_pubkey, sizeof(xonly_pubkey));
xonly_pubkeys[0] = &xonly_pubkey;
ret = secp256k1_ec_pubkey_create(ctx, &plain_pubkey, plain_seckeys[0]);
SECP256K1_CHECKMEM_DEFINE(&ret, sizeof(ret));
CHECK(ret == 1);
SECP256K1_CHECKMEM_DEFINE(&plain_pubkey, sizeof(plain_pubkey));
plain_pubkeys[0] = &plain_pubkey;

ret = secp256k1_silentpayments_recipient_public_data_create(ctx, &public_data, outpoint_smallest, xonly_pubkeys, 1, plain_pubkeys, 1);
CHECK(ret == 1);

tx_outputs[0] = generated_outputs[0];
n_found_outputs = 1;
SECP256K1_CHECKMEM_DEFINE(&recipient.spend_pubkey, sizeof(recipient.spend_pubkey));
/* TODO: make sure we're actually go through all relevant code paths */
ret = secp256k1_silentpayments_recipient_scan_outputs(ctx, found_outputs, &n_found_outputs, tx_outputs, 1, key, &public_data, &recipient.spend_pubkey, NULL, NULL);
CHECK(ret == 1);

/* TODO: this fails */
/* CHECK(secp256k1_silentpayments_recipient_create_shared_secret(ctx, shared_secret, key, &public_data)); */
/* TODO: test secp256k1_silentpayments_recipient_create_output_pubkey */

#endif
}
105 changes: 77 additions & 28 deletions src/modules/silentpayments/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ static void secp256k1_silentpayments_calculate_input_hash(unsigned char *input_h
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) {
static void secp256k1_silentpayments_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const secp256k1_scalar *secret_component, const secp256k1_ge *public_component) {
secp256k1_gej ss_j;
secp256k1_ge ss;
size_t len;
Expand All @@ -73,6 +73,7 @@ static void secp256k1_silentpayments_create_shared_secret(unsigned char *shared_
/* Compute shared_secret = tweaked_secret_component * Public_component */
secp256k1_ecmult_const(&ss_j, public_component, secret_component);
secp256k1_ge_set_gej(&ss, &ss_j);
secp256k1_declassify(ctx, &ss, sizeof(ss));
/* 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
Expand Down Expand Up @@ -132,16 +133,26 @@ static int secp256k1_silentpayments_create_output_pubkey(const secp256k1_context
* 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);
if (!secp256k1_pubkey_load(ctx, &P_output_ge, recipient_spend_pubkey)) {
secp256k1_scalar_clear(&t_k_scalar);
return 0;
}
ret = secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar);
/* tweak add only fails if t_k_scalar is equal to the dlog of P_output_ge, but t_k_scalar is the output of a collision resistant hash function. */
/* TODO: consider declassify ret */
/* TODO: but we don't want to imply this can never happen */
VERIFY_CHECK(ret);
#ifndef VERIFY
(void) ret;
#endif
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;
return 1;
}

int secp256k1_silentpayments_sender_create_outputs(
Expand All @@ -163,7 +174,7 @@ int secp256k1_silentpayments_sender_create_outputs(
unsigned char shared_secret[33];
secp256k1_silentpayments_recipient last_recipient;
int overflow = 0;
int ret = 1;
int ret;

/* Sanity check inputs. */
VERIFY_CHECK(ctx != NULL);
Expand Down Expand Up @@ -191,34 +202,55 @@ int secp256k1_silentpayments_sender_create_outputs(
/* 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]);
ret = secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]);
/* TODO: We can declassify return value, because scalar set only fails if the seckey is invalid */
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (!ret) {
/* TODO: clear a_sum_scalar */
printf("b\n");
return 0;
}
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]);
ret = secp256k1_keypair_load(ctx, &addend, &addend_point, taproot_seckeys[i]);
/* TODO: we can declassify return value */
if (!ret) {
/* TODO: clear a_sum_scalar */
printf("a\n");
return 0;
}
secp256k1_declassify(ctx, &ret, sizeof(ret));
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)) {
/* TODO: can we declassify this? */
/* Yes: We assume the adversary has access to a_sum_scalar*G */
ret = secp256k1_scalar_is_zero(&a_sum_scalar);
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (ret) {
printf("z\n");
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);
/* TODO: comment */
secp256k1_declassify(ctx, &A_sum_ge, sizeof(A_sum_ge));

/* 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;
/* TODO: consider VERIFY_CHECK ??? */
if (overflow) {
printf("y\n");
return 0;
}
secp256k1_scalar_mul(&a_sum_scalar, &a_sum_scalar, &input_hash_scalar);
secp256k1_silentpayments_recipient_sort(ctx, recipients, n_recipients);
last_recipient = *recipients[0];
Expand All @@ -231,12 +263,15 @@ int secp256k1_silentpayments_sender_create_outputs(
* 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);
if (!secp256k1_pubkey_load(ctx, &pk, &recipients[i]->scan_pubkey)) break;
secp256k1_silentpayments_create_shared_secret(ctx, 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);
if (!secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret, &recipients[i]->spend_pubkey, k)) {
/* TODO: clean up */
printf("x\n");
return 0;
}
k++;
last_recipient = *recipients[i];
}
Expand All @@ -249,7 +284,7 @@ int secp256k1_silentpayments_sender_create_outputs(
* and potentially link the transaction back to a silent payment address
*/
memset(&shared_secret, 0, sizeof(shared_secret));
return ret;
return 1;
}

/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Label". */
Expand Down Expand Up @@ -471,12 +506,21 @@ int secp256k1_silentpayments_recipient_scan_outputs(
* Recall: a scan key isnt really "secret" data in that leaking the scan key will only leak privacy
* In this respect, a scan key is functionally equivalent to an xpub
*/
ret &= secp256k1_scalar_set_b32_seckey(&rsk_scalar, recipient_scan_key);
ret &= secp256k1_silentpayments_recipient_public_data_load_pubkey(ctx, &A_sum, public_data);
ret &= secp256k1_pubkey_load(ctx, &A_sum_ge, &A_sum);
ret &= secp256k1_pubkey_load(ctx, &recipient_spend_pubkey_ge, recipient_spend_pubkey);
/* If there is something wrong with the recipient scan key, recipient spend pubkey, or the public data, return early */
/* If there is something wrong with the recipient scan key, recipient spend pubkey, or the public data, then return */
ret = secp256k1_scalar_set_b32_seckey(&rsk_scalar, recipient_scan_key);
/* TODO: only fails in case of invalid key */
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (!ret) {
/* consider clearing */
return 0;
}
if (!secp256k1_silentpayments_recipient_public_data_load_pubkey(ctx, &A_sum, public_data)) {
return 0;
}
if (!secp256k1_pubkey_load(ctx, &A_sum_ge, &A_sum)) {
return 0;
}
if (!secp256k1_pubkey_load(ctx, &recipient_spend_pubkey_ge, recipient_spend_pubkey)) {
return 0;
}
combined = (int)public_data->data[0];
Expand All @@ -487,10 +531,12 @@ int secp256k1_silentpayments_recipient_scan_outputs(

secp256k1_silentpayments_recipient_public_data_load_input_hash(input_hash, public_data);
secp256k1_scalar_set_b32(&input_hash_scalar, input_hash, &overflow);
if (overflow) {
return 0;
}
secp256k1_scalar_mul(&rsk_scalar, &rsk_scalar, &input_hash_scalar);
ret &= !overflow;
}
secp256k1_silentpayments_create_shared_secret(shared_secret, &rsk_scalar, &A_sum_ge);
secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, &rsk_scalar, &A_sum_ge);

found_idx = 0;
n_found = 0;
Expand All @@ -503,7 +549,8 @@ int secp256k1_silentpayments_recipient_scan_outputs(
/* Calculate P_output = B_spend + t_k * G
* This can fail if t_k overflows the curver order, but this is statistically improbable
*/
ret &= secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar);
ret = secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar);
VERIFY_CHECK(ret);
found = 0;
secp256k1_xonly_pubkey_save(&P_output_xonly, &P_output_ge);
for (i = 0; i < n_tx_outputs; i++) {
Expand Down Expand Up @@ -560,7 +607,9 @@ int secp256k1_silentpayments_recipient_scan_outputs(
* created by hashing data, practically speaking this would only happen if an attacker
* tricked us into using a particular label_tweak (deviating from the protocol).
*/
ret &= secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak);
ret = secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak);
/* TODO: do we really want to do that */
VERIFY_CHECK(ret);
secp256k1_pubkey_save(&found_outputs[n_found]->label, &label_ge);
} else {
found_outputs[n_found]->found_with_label = 0;
Expand Down Expand Up @@ -609,7 +658,7 @@ int secp256k1_silentpayments_recipient_create_shared_secret(const secp256k1_cont
if (!ret) {
return 0;
}
secp256k1_silentpayments_create_shared_secret(shared_secret33, &rsk, &A_tweaked_ge);
secp256k1_silentpayments_create_shared_secret(ctx, shared_secret33, &rsk, &A_tweaked_ge);

/* Explicitly clear secrets */
secp256k1_scalar_clear(&rsk);
Expand Down
3 changes: 2 additions & 1 deletion src/modules/silentpayments/tests_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ static void test_send_api(void) {
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));
/* TODO: this test "probably" only worked because the function did not fail due to an earlier error ("ret-style") */
/* CHECK_ILLEGAL(CTX, secp256k1_silentpayments_sender_create_outputs(CTX, op, rp, 1, SMALLEST_OUTPOINT, NULL, 0, p, 1)) */;
}

static void test_label_api(void) {
Expand Down
Loading

0 comments on commit 5816dc7

Please sign in to comment.