Skip to content

Commit 016c20d

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 41cd97d commit 016c20d

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed

include/secp256k1_silentpayments.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,55 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien
222222
size_t n_plain_pubkeys
223223
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
224224

225+
/** Scan for Silent Payment transaction outputs.
226+
*
227+
* Given a input public sum, an input_hash, a recipient's spend public key B_spend, and the relevant transaction
228+
* outputs, scan for outputs belong to the recipient and return the tweak(s) needed for spending
229+
* the output(s). An optional label_lookup callback function and label_context can be passed if the
230+
* recipient uses labels. This allows for checking if a label exists in the recipients label cache
231+
* and retrieving the label tweak during scanning.
232+
*
233+
* Returns: 1 if output scanning was successful. 0 if an error occured.
234+
* Args: ctx: pointer to a context object
235+
* Out: found_outputs: pointer to an array of pointers to found output objects. The found outputs
236+
* array MUST be initialized to be the same length as the tx_outputs array
237+
* n_found_outputs: pointer to an integer indicating the final size of the found outputs array.
238+
* This number represents the number of outputs found while scanning (0 if
239+
* none are found)
240+
* In: tx_outputs: pointer to the tx's x-only public key outputs
241+
* n_tx_outputs: the number of tx_outputs being scanned
242+
* scan_key: pointer to the recipient's scan key
243+
* public_data: pointer to the input public key sum (optionaly, with the `input_hash`
244+
* multiplied in, see `_recipient_compute_public_data`).
245+
* recipient_spend_pubkey: pointer to the receiver's spend pubkey
246+
* label_lookup: pointer to a callback function for looking up a label value. This fucntion
247+
* takes a label pubkey as an argument and returns a pointer to the label tweak
248+
* if the label exists, otherwise returns a nullptr (NULL if labels are not used)
249+
* label_context: pointer to a label context object (NULL if labels are not used)
250+
*/
251+
252+
typedef const unsigned char* (*secp256k1_silentpayments_label_lookup)(const secp256k1_pubkey*, const void*);
253+
typedef struct {
254+
secp256k1_xonly_pubkey output;
255+
unsigned char tweak[32];
256+
int found_with_label;
257+
secp256k1_pubkey label;
258+
} secp256k1_silentpayments_found_output;
259+
260+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_scan_outputs(
261+
const secp256k1_context *ctx,
262+
secp256k1_silentpayments_found_output **found_outputs,
263+
size_t *n_found_outputs,
264+
const secp256k1_xonly_pubkey * const *tx_outputs,
265+
size_t n_tx_outputs,
266+
const unsigned char *scan_key,
267+
const secp256k1_silentpayments_public_data *public_data,
268+
const secp256k1_pubkey *receiver_spend_pubkey,
269+
const secp256k1_silentpayments_label_lookup label_lookup,
270+
const void *label_context
271+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4)
272+
SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
273+
225274
#ifdef __cplusplus
226275
}
227276
#endif

src/modules/silentpayments/main_impl.h

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,4 +429,139 @@ int secp256k1_silentpayments_recipient_public_data_parse(const secp256k1_context
429429
return 1;
430430
}
431431

432+
int secp256k1_silentpayments_recipient_scan_outputs(
433+
const secp256k1_context *ctx,
434+
secp256k1_silentpayments_found_output **found_outputs, size_t *n_found_outputs,
435+
const secp256k1_xonly_pubkey * const *tx_outputs, size_t n_tx_outputs,
436+
const unsigned char *scan_key,
437+
const secp256k1_silentpayments_public_data *public_data,
438+
const secp256k1_pubkey *receiver_spend_pubkey,
439+
const secp256k1_silentpayments_label_lookup label_lookup,
440+
const void *label_context
441+
) {
442+
secp256k1_scalar t_k_scalar;
443+
secp256k1_ge receiver_spend_pubkey_ge;
444+
secp256k1_xonly_pubkey P_output_xonly;
445+
secp256k1_pubkey A_sum;
446+
unsigned char shared_secret[33];
447+
size_t i, k, n_found;
448+
int found, combined;
449+
450+
/* Sanity check inputs */
451+
VERIFY_CHECK(ctx != NULL);
452+
ARG_CHECK(found_outputs != NULL);
453+
ARG_CHECK(tx_outputs != NULL);
454+
ARG_CHECK(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, &receiver_spend_pubkey_ge, receiver_spend_pubkey);
470+
if (!secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, scan_key, &A_sum, input_hash_ptr)) {
471+
return 0;
472+
}
473+
}
474+
475+
n_found = 0;
476+
k = 0;
477+
while (1) {
478+
secp256k1_ge P_output_ge = receiver_spend_pubkey_ge;
479+
/* Calculate t_k = hash(shared_secret || ser_32(k)) */
480+
secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret, k);
481+
482+
/* Calculate P_output = B_spend + t_k * G */
483+
if (!secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar)) {
484+
return 0;
485+
}
486+
487+
/* If the calculated output matches the one from the tx, we have a direct match and can
488+
* return without labels calculation (one of the two would result in point of infinity) */
489+
secp256k1_xonly_pubkey_save(&P_output_xonly, &P_output_ge);
490+
found = 0;
491+
for (i = 0; i < n_tx_outputs; i++) {
492+
if (secp256k1_xonly_pubkey_cmp(ctx, &P_output_xonly, tx_outputs[i]) == 0) {
493+
secp256k1_xonly_pubkey_save(&found_outputs[n_found]->output, &P_output_ge);
494+
secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &t_k_scalar);
495+
found_outputs[n_found]->found_with_label = 0;
496+
secp256k1_pubkey_save(&found_outputs[n_found]->label, &P_output_ge);
497+
found = 1;
498+
n_found++;
499+
k++;
500+
break;
501+
}
502+
503+
/* If desired, also calculate label candidates */
504+
if (label_lookup != NULL) {
505+
secp256k1_pubkey label_pubkey;
506+
secp256k1_ge P_output_negated_ge, tx_output_ge;
507+
secp256k1_ge label_ge;
508+
secp256k1_gej label_gej;
509+
const unsigned char *label_tweak;
510+
511+
/* Calculate negated P_output (common addend) first */
512+
secp256k1_ge_neg(&P_output_negated_ge, &P_output_ge);
513+
514+
/* Calculate first scan label candidate: label1 = tx_output - P_output */
515+
secp256k1_xonly_pubkey_load(ctx, &tx_output_ge, tx_outputs[i]);
516+
secp256k1_gej_set_ge(&label_gej, &tx_output_ge);
517+
secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL);
518+
secp256k1_ge_set_gej(&label_ge, &label_gej);
519+
secp256k1_pubkey_save(&label_pubkey, &label_ge);
520+
521+
label_tweak = label_lookup(&label_pubkey, label_context);
522+
if (label_tweak != NULL) {
523+
secp256k1_xonly_pubkey_save(&found_outputs[n_found]->output, &tx_output_ge);
524+
found_outputs[n_found]->found_with_label = 1;
525+
secp256k1_pubkey_save(&found_outputs[n_found]->label, &label_ge);
526+
secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &t_k_scalar);
527+
if (!secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak)) {
528+
return 0;
529+
}
530+
found = 1;
531+
n_found++;
532+
k++;
533+
break;
534+
}
535+
536+
/* Calculate second scan label candidate: label2 = -tx_output - P_output */
537+
secp256k1_gej_set_ge(&label_gej, &tx_output_ge);
538+
secp256k1_gej_neg(&label_gej, &label_gej);
539+
secp256k1_gej_add_ge_var(&label_gej, &label_gej, &P_output_negated_ge, NULL);
540+
secp256k1_ge_set_gej(&label_ge, &label_gej);
541+
secp256k1_pubkey_save(&label_pubkey, &label_ge);
542+
543+
label_tweak = label_lookup(&label_pubkey, label_context);
544+
if (label_tweak != NULL) {
545+
secp256k1_xonly_pubkey_save(&found_outputs[n_found]->output, &tx_output_ge);
546+
found_outputs[n_found]->found_with_label = 1;
547+
secp256k1_pubkey_save(&found_outputs[n_found]->label, &label_ge);
548+
secp256k1_scalar_get_b32(found_outputs[n_found]->tweak, &t_k_scalar);
549+
if (!secp256k1_ec_seckey_tweak_add(ctx, found_outputs[n_found]->tweak, label_tweak)) {
550+
return 0;
551+
}
552+
found = 1;
553+
n_found++;
554+
k++;
555+
break;
556+
}
557+
}
558+
}
559+
if (!found) {
560+
break;
561+
}
562+
}
563+
*n_found_outputs = n_found;
564+
return 1;
565+
}
566+
432567
#endif

0 commit comments

Comments
 (0)