Skip to content

Commit c657414

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 9cbbba8 commit c657414

File tree

2 files changed

+136
-5
lines changed

2 files changed

+136
-5
lines changed

include/secp256k1_silentpayments.h

Lines changed: 5 additions & 5 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`
245-
* multiplied in, see `_recipient_compute_public_data`).
246-
* recipient_spend_pubkey: pointer to the receiver's spend pubkey
245+
* multiplied in, see `_recipient_public_data_create`).
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
@@ -431,4 +431,135 @@ int secp256k1_silentpayments_recipient_public_data_parse(const secp256k1_context
431431
return 1;
432432
}
433433

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

0 commit comments

Comments
 (0)