Skip to content

Commit a0ee789

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 75d6f5b commit a0ee789

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed

include/secp256k1_silentpayments.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,57 @@ 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_tweak_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+
* input_hash: pointer to the input_hash. MUST be NULL if the input_hash is already
247+
* multipled into the input public key sum (see `_recipient_compute_public_data`)
248+
* label_lookup: pointer to a callback function for looking up a label value. This fucntion
249+
* takes a label pubkey as an argument and returns a pointer to the label tweak
250+
* if the label exists, otherwise returns a nullptr (NULL if labels are not used)
251+
* label_context: pointer to a label context object (NULL if labels are not used)
252+
*/
253+
254+
typedef const unsigned char* (*secp256k1_silentpayments_label_lookup)(const secp256k1_pubkey*, const void*);
255+
typedef struct {
256+
secp256k1_xonly_pubkey output;
257+
unsigned char tweak[32];
258+
int found_with_label;
259+
secp256k1_pubkey label;
260+
} secp256k1_silentpayments_found_output;
261+
262+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_scan_outputs(
263+
const secp256k1_context *ctx,
264+
secp256k1_silentpayments_found_output **found_outputs,
265+
size_t *n_found_outputs,
266+
const secp256k1_xonly_pubkey * const *tx_outputs,
267+
size_t n_tx_outputs,
268+
const unsigned char *scan_key,
269+
const secp256k1_silentpayments_public_data *public_data,
270+
const secp256k1_pubkey *receiver_spend_pubkey,
271+
const secp256k1_silentpayments_label_lookup label_lookup,
272+
const void *label_context
273+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4)
274+
SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
275+
225276
#ifdef __cplusplus
226277
}
227278
#endif

src/modules/silentpayments/main_impl.h

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,4 +433,137 @@ int secp256k1_silentpayments_recipient_public_data_parse(const secp256k1_context
433433
return 1;
434434
}
435435

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

0 commit comments

Comments
 (0)