Skip to content

Commit 70c776e

Browse files
committed
silentpayments: add recipient scanning routine
Add routine for scanning a transaction and returning the necessary spending data for any found outputs. This function works with labels via a lookup callback and requires access to the transaction outputs. Requiring access to the transaction outputs is not suitable for light clients, but light client support is enabled in followup commits.
1 parent f2f15f2 commit 70c776e

File tree

2 files changed

+135
-4
lines changed

2 files changed

+135
-4
lines changed

include/secp256k1_silentpayments.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,10 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien
240240
* none are found)
241241
* In: tx_outputs: pointer to the tx's x-only public key outputs
242242
* n_tx_outputs: the number of tx_outputs being scanned
243-
* scan_key: pointer to the recipient's scan key
243+
* recipient_scan_key: pointer to the recipient's scan key
244244
* public_data: pointer to the input public key sum (optionaly, with the `input_hash`
245245
* multiplied in, see `_recipient_compute_public_data`).
246-
* recipient_spend_pubkey: pointer to the receiver's spend pubkey
246+
* recipient_spend_pubkey: pointer to the recipient's spend pubkey
247247
* label_lookup: pointer to a callback function for looking up a label value. This fucntion
248248
* takes a label pubkey as an argument and returns a pointer to the label tweak
249249
* if the label exists, otherwise returns a nullptr (NULL if labels are not used)
@@ -264,9 +264,9 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien
264264
size_t *n_found_outputs,
265265
const secp256k1_xonly_pubkey * const *tx_outputs,
266266
size_t n_tx_outputs,
267-
const unsigned char *scan_key,
267+
const unsigned char *recipient_scan_key,
268268
const secp256k1_silentpayments_public_data *public_data,
269-
const secp256k1_pubkey *receiver_spend_pubkey,
269+
const secp256k1_pubkey *recipient_spend_pubkey,
270270
const secp256k1_silentpayments_label_lookup label_lookup,
271271
const void *label_context
272272
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4)

src/modules/silentpayments/main_impl.h

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,4 +425,135 @@ int secp256k1_silentpayments_recipient_public_data_parse(const secp256k1_context
425425
return 1;
426426
}
427427

428+
int secp256k1_silentpayments_recipient_scan_outputs(
429+
const secp256k1_context *ctx,
430+
secp256k1_silentpayments_found_output **found_outputs, size_t *n_found_outputs,
431+
const secp256k1_xonly_pubkey * const *tx_outputs, size_t n_tx_outputs,
432+
const unsigned char *recipient_scan_key,
433+
const secp256k1_silentpayments_public_data *public_data,
434+
const secp256k1_pubkey *recipient_spend_pubkey,
435+
const secp256k1_silentpayments_label_lookup label_lookup,
436+
const void *label_context
437+
) {
438+
secp256k1_scalar t_k_scalar;
439+
secp256k1_ge label_ge, recipient_spend_pubkey_ge;
440+
secp256k1_pubkey A_sum;
441+
secp256k1_xonly_pubkey P_output_xonly;
442+
unsigned char shared_secret[33];
443+
unsigned char label_tweak32[32];
444+
const unsigned char *label_tweak = label_tweak32;
445+
size_t i, k, n_found, found_idx;
446+
int found, combined;
447+
448+
/* Sanity check inputs */
449+
VERIFY_CHECK(ctx != NULL);
450+
ARG_CHECK(found_outputs != NULL);
451+
ARG_CHECK(n_found_outputs != NULL);
452+
ARG_CHECK(tx_outputs != NULL);
453+
ARG_CHECK(n_tx_outputs >= 1);
454+
ARG_CHECK(recipient_scan_key != NULL);
455+
ARG_CHECK(public_data != NULL);
456+
combined = (int)public_data->data[0];
457+
{
458+
unsigned char input_hash[32];
459+
unsigned char *input_hash_ptr;
460+
if (combined) {
461+
input_hash_ptr = NULL;
462+
} else {
463+
memset(input_hash, 0, 32);
464+
input_hash_ptr = input_hash;
465+
}
466+
if (!secp256k1_silentpayments_recipient_public_data_load(ctx, &A_sum, input_hash_ptr, public_data)) {
467+
return 0;
468+
}
469+
secp256k1_pubkey_load(ctx, &recipient_spend_pubkey_ge, recipient_spend_pubkey);
470+
if (!secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, recipient_scan_key, &A_sum, input_hash_ptr)) {
471+
return 0;
472+
}
473+
}
474+
475+
found_idx = 0;
476+
n_found = 0;
477+
k = 0;
478+
while (1) {
479+
secp256k1_ge P_output_ge = recipient_spend_pubkey_ge;
480+
/* Calculate t_k = hash(shared_secret || ser_32(k)) */
481+
secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret, k);
482+
483+
/* Calculate P_output = B_spend + t_k * G */
484+
if (!secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar)) {
485+
return 0;
486+
}
487+
488+
found = 0;
489+
secp256k1_xonly_pubkey_save(&P_output_xonly, &P_output_ge);
490+
for (i = 0; i < n_tx_outputs; i++) {
491+
if (secp256k1_xonly_pubkey_cmp(ctx, &P_output_xonly, tx_outputs[i]) == 0) {
492+
label_tweak = NULL;
493+
found = 1;
494+
found_idx = i;
495+
break;
496+
}
497+
498+
/* If not found, proceed to check for labels (if the labels cache is present) */
499+
if (label_lookup != NULL) {
500+
secp256k1_pubkey label_pubkey;
501+
secp256k1_ge P_output_negated_ge, tx_output_ge;
502+
secp256k1_gej tx_output_gej, label_gej;
503+
504+
secp256k1_xonly_pubkey_load(ctx, &tx_output_ge, tx_outputs[i]);
505+
secp256k1_gej_set_ge(&tx_output_gej, &tx_output_ge);
506+
secp256k1_ge_neg(&P_output_negated_ge, &P_output_ge);
507+
/* Negate the generated output and calculate first scan label candidate:
508+
* label1 = tx_output - P_output */
509+
secp256k1_gej_add_ge_var(&label_gej, &tx_output_gej, &P_output_negated_ge, NULL);
510+
secp256k1_ge_set_gej(&label_ge, &label_gej);
511+
secp256k1_pubkey_save(&label_pubkey, &label_ge);
512+
label_tweak = label_lookup(&label_pubkey, label_context);
513+
if (label_tweak != NULL) {
514+
found = 1;
515+
found_idx = i;
516+
break;
517+
}
518+
519+
secp256k1_gej_neg(&label_gej, &tx_output_gej);
520+
/* If not found, negate the tx_output and calculate second scan label candidate:
521+
* label2 = -tx_output - P_output */
522+
secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL);
523+
secp256k1_ge_set_gej(&label_ge, &label_gej);
524+
secp256k1_pubkey_save(&label_pubkey, &label_ge);
525+
label_tweak = label_lookup(&label_pubkey, label_context);
526+
if (label_tweak != NULL) {
527+
found = 1;
528+
found_idx = i;
529+
break;
530+
}
531+
}
532+
}
533+
if (found) {
534+
found_outputs[n_found]->output = *tx_outputs[found_idx];
535+
secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &t_k_scalar);
536+
if (label_lookup != NULL && label_tweak != NULL) {
537+
found_outputs[n_found]->found_with_label = 1;
538+
if (!secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak)) {
539+
return 0;
540+
}
541+
secp256k1_pubkey_save(&found_outputs[n_found]->label, &label_ge);
542+
} else {
543+
found_outputs[n_found]->found_with_label = 0;
544+
/* TODO: instead of using the tx_output, set the label with a properly invalid pubkey */
545+
secp256k1_pubkey_save(&found_outputs[n_found]->label, &P_output_ge);
546+
}
547+
/* Set everything for the next round of scanning */
548+
label_tweak = label_tweak32;
549+
n_found++;
550+
k++;
551+
} else {
552+
break;
553+
}
554+
}
555+
*n_found_outputs = n_found;
556+
return 1;
557+
}
558+
428559
#endif

0 commit comments

Comments
 (0)