Skip to content

Commit 9cbbba8

Browse files
committed
silentpayments: add opaque data type public_data
Add data type for passing around the summed input public key (A_sum) and the input hash tweak (input_hash). This data is passed to the scanner before the ECDH step as two separate elements so that the scanner can multiply b_scan * input_hash before doing ECDH. Add functions for deserializing / serializing a public_data object to and from a public key. When serializing a public_data object, the input_hash is multplied into A_sum. This is so the object can be stored as public key for wallet rescanning later, or to vend to light clients. For the light client, a `_parse` function is added which parses the compressed public key serialization into a `public_data` object.
1 parent 8a2bc9d commit 9cbbba8

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

include/secp256k1_silentpayments.h

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,139 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipien
139139
const secp256k1_pubkey *label
140140
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
141141

142+
/** Opaque data structure that holds silent payments public input data.
143+
*
144+
* This structure does not contain secret data. Guaranteed to be 98 bytes in size. It can be safely
145+
* copied/moved. Created with `secp256k1_silentpayments_public_data_create`. Can be serialized as
146+
* a compressed public key using `secp256k1_silentpayments_public_data_serialize`. The serialization
147+
* is intended for sending the public input data to light clients. Light clients can use this
148+
* serialization with `secp256k1_silentpayments_public_data_parse`.
149+
*/
150+
typedef struct {
151+
unsigned char data[98];
152+
} secp256k1_silentpayments_public_data;
153+
154+
/** Compute Silent Payment public data from input public keys and transaction inputs.
155+
*
156+
* Given a list of n public keys A_1...A_n (one for each silent payment
157+
* eligible input to spend) and a serialized outpoint_smallest, compute
158+
* the corresponding input public tweak data:
159+
*
160+
* A_sum = A_1 + A_2 + ... + A_n
161+
* input_hash = hash(outpoint_lowest || A_sum)
162+
*
163+
* The public keys have to be passed in via two different parameter pairs,
164+
* one for regular and one for x-only public keys, in order to avoid the need
165+
* of users converting to a common pubkey format before calling this function.
166+
* The resulting data is can be used for scanning on the recipient side, or stored
167+
* in an index for late use (e.g. wallet rescanning, vending data to light clients).
168+
*
169+
* If calling this function for scanning, the reciever must provide an output param
170+
* for the `input_hash`. If calling this function for simply aggregating the inputs
171+
* for later use, the caller can save the result with `silentpayments_public_data_serialize`.
172+
*
173+
* Returns: 1 if tweak data creation was successful. 0 if an error occured.
174+
* Args: ctx: pointer to a context object
175+
* Out: public_data: pointer to public_data object containing the summed public key and
176+
* input_hash.
177+
* In: outpoint_smallest36: serialized smallest outpoint
178+
* xonly_pubkeys: pointer to an array of pointers to taproot x-only
179+
* public keys (can be NULL if no taproot inputs are used)
180+
* n_xonly_pubkeys: the number of taproot input public keys
181+
* plain_pubkeys: pointer to an array of pointers to non-taproot
182+
* public keys (can be NULL if no non-taproot inputs are used)
183+
* n_plain_pubkeys: the number of non-taproot input public keys
184+
*/
185+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_create(
186+
const secp256k1_context *ctx,
187+
secp256k1_silentpayments_public_data *public_data,
188+
const unsigned char *outpoint_smallest36,
189+
const secp256k1_xonly_pubkey * const *xonly_pubkeys,
190+
size_t n_xonly_pubkeys,
191+
const secp256k1_pubkey * const *plain_pubkeys,
192+
size_t n_plain_pubkeys
193+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
194+
195+
/** Serialize a silentpayments_public_data object into a 33-byte sequence.
196+
*
197+
* Returns: 1 always.
198+
*
199+
* Args: ctx: pointer to a context object.
200+
* Out: output33: pointer to a 33-byte array to place the serialized `silentpayments_public_data` in.
201+
* In: public_data: pointer to an initialized silentpayments_public_data object.
202+
*/
203+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_serialize(
204+
const secp256k1_context *ctx,
205+
unsigned char *output33,
206+
const secp256k1_silentpayments_public_data *public_data
207+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
208+
209+
/** Parse a 33-byte sequence into a silent_payments_public_data object.
210+
*
211+
* Returns: 1 if the data was able to be parsed.
212+
* 0 if the sequence is invalid (e.g. does not represent a valid public key).
213+
*
214+
* Args: ctx: pointer to a context object.
215+
* Out: public_data: pointer to a silentpayments_public_data object. If 1 is returned, it is set to a
216+
* parsed version of input33.
217+
* In: input33: pointer to a serialized silentpayments_public_data.
218+
*/
219+
220+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_parse(
221+
const secp256k1_context *ctx,
222+
secp256k1_silentpayments_public_data *public_data,
223+
const unsigned char *input33
224+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
225+
226+
/** Scan for Silent Payment transaction outputs.
227+
*
228+
* Given a input public sum, an input_hash, a recipient's spend public key B_spend, and the relevant transaction
229+
* outputs, scan for outputs belong to the recipient and return the tweak(s) needed for spending
230+
* the output(s). An optional label_lookup callback function and label_context can be passed if the
231+
* recipient uses labels. This allows for checking if a label exists in the recipients label cache
232+
* and retrieving the label tweak during scanning.
233+
*
234+
* Returns: 1 if output scanning was successful. 0 if an error occured.
235+
* Args: ctx: pointer to a context object
236+
* Out: found_outputs: pointer to an array of pointers to found output objects. The found outputs
237+
* array MUST be initialized to be the same length as the tx_outputs array
238+
* n_found_outputs: pointer to an integer indicating the final size of the found outputs array.
239+
* This number represents the number of outputs found while scanning (0 if
240+
* none are found)
241+
* In: tx_outputs: pointer to the tx's x-only public key outputs
242+
* n_tx_outputs: the number of tx_outputs being scanned
243+
* scan_key: pointer to the recipient's scan key
244+
* 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
247+
* label_lookup: pointer to a callback function for looking up a label value. This fucntion
248+
* takes a label pubkey as an argument and returns a pointer to the label tweak
249+
* if the label exists, otherwise returns a nullptr (NULL if labels are not used)
250+
* label_context: pointer to a label context object (NULL if labels are not used)
251+
*/
252+
253+
typedef const unsigned char* (*secp256k1_silentpayments_label_lookup)(const secp256k1_pubkey*, const void*);
254+
typedef struct {
255+
secp256k1_xonly_pubkey output;
256+
unsigned char tweak[32];
257+
int found_with_label;
258+
secp256k1_pubkey label;
259+
} secp256k1_silentpayments_found_output;
260+
261+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_scan_outputs(
262+
const secp256k1_context *ctx,
263+
secp256k1_silentpayments_found_output **found_outputs,
264+
size_t *n_found_outputs,
265+
const secp256k1_xonly_pubkey * const *tx_outputs,
266+
size_t n_tx_outputs,
267+
const unsigned char *scan_key,
268+
const secp256k1_silentpayments_public_data *public_data,
269+
const secp256k1_pubkey *receiver_spend_pubkey,
270+
const secp256k1_silentpayments_label_lookup label_lookup,
271+
const void *label_context
272+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4)
273+
SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
274+
142275
#ifdef __cplusplus
143276
}
144277
#endif

src/modules/silentpayments/main_impl.h

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,114 @@ int secp256k1_silentpayments_recipient_create_labelled_spend_pubkey(const secp25
321321
return 1;
322322
}
323323

324+
int secp256k1_silentpayments_recipient_public_data_create(
325+
const secp256k1_context *ctx,
326+
secp256k1_silentpayments_public_data *public_data,
327+
const unsigned char *outpoint_smallest36,
328+
const secp256k1_xonly_pubkey * const *xonly_pubkeys,
329+
size_t n_xonly_pubkeys,
330+
const secp256k1_pubkey * const *plain_pubkeys,
331+
size_t n_plain_pubkeys
332+
) {
333+
size_t i;
334+
size_t pubkeylen = 65;
335+
secp256k1_pubkey A_sum;
336+
secp256k1_ge A_sum_ge, addend;
337+
secp256k1_gej A_sum_gej;
338+
unsigned char input_hash_local[32];
339+
340+
/* Sanity check inputs */
341+
VERIFY_CHECK(ctx != NULL);
342+
ARG_CHECK(public_data != NULL);
343+
ARG_CHECK(plain_pubkeys == NULL || n_plain_pubkeys >= 1);
344+
ARG_CHECK(xonly_pubkeys == NULL || n_xonly_pubkeys >= 1);
345+
ARG_CHECK((plain_pubkeys != NULL) || (xonly_pubkeys != NULL));
346+
ARG_CHECK((n_plain_pubkeys + n_xonly_pubkeys) >= 1);
347+
ARG_CHECK(outpoint_smallest36 != NULL);
348+
memset(input_hash_local, 0, 32);
349+
350+
/* Compute input public keys sum: A_sum = A_1 + A_2 + ... + A_n */
351+
secp256k1_gej_set_infinity(&A_sum_gej);
352+
for (i = 0; i < n_plain_pubkeys; i++) {
353+
if (!secp256k1_pubkey_load(ctx, &addend, plain_pubkeys[i])) {
354+
return 0;
355+
}
356+
secp256k1_gej_add_ge_var(&A_sum_gej, &A_sum_gej, &addend, NULL);
357+
}
358+
for (i = 0; i < n_xonly_pubkeys; i++) {
359+
if (!secp256k1_xonly_pubkey_load(ctx, &addend, xonly_pubkeys[i])) {
360+
return 0;
361+
}
362+
secp256k1_gej_add_ge_var(&A_sum_gej, &A_sum_gej, &addend, NULL);
363+
}
364+
/* If the caller passes in all valid public keys but the public keys
365+
* sum to 0 (the point at infinity), we can't do anything except tell
366+
* the caller to try again with a different set of input public keys,
367+
* e.g. skip the current transaction and move to the next */
368+
ARG_CHECK(secp256k1_gej_is_infinity(&A_sum_gej) == 0);
369+
secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej);
370+
371+
/* Compute input_hash = hash(outpoint_L || A_sum) */
372+
secp256k1_silentpayments_calculate_input_hash(input_hash_local, outpoint_smallest36, &A_sum_ge);
373+
secp256k1_pubkey_save(&A_sum, &A_sum_ge);
374+
/* serialize the public_data struct */
375+
public_data->data[0] = 0;
376+
secp256k1_ec_pubkey_serialize(ctx, &public_data->data[1], &pubkeylen, &A_sum, SECP256K1_EC_UNCOMPRESSED);
377+
memcpy(&public_data->data[1 + pubkeylen], input_hash_local, 32);
378+
return 1;
379+
}
380+
381+
static int secp256k1_silentpayments_recipient_public_data_load(const secp256k1_context *ctx, secp256k1_pubkey *pubkey, unsigned char *input_hash, const secp256k1_silentpayments_public_data *public_data) {
382+
int combined;
383+
size_t pubkeylen = 65;
384+
VERIFY_CHECK(ctx != NULL);
385+
ARG_CHECK(pubkey != NULL);
386+
ARG_CHECK(public_data != NULL);
387+
388+
combined = (int)public_data->data[0];
389+
ARG_CHECK(combined == 0 || combined == 1);
390+
if (combined) {
391+
ARG_CHECK(combined == 1 && input_hash == NULL);
392+
} else {
393+
ARG_CHECK(combined == 0 && input_hash != NULL);
394+
memcpy(input_hash, &public_data->data[1 + pubkeylen], 32);
395+
}
396+
if (!secp256k1_ec_pubkey_parse(ctx, pubkey, &public_data->data[1], pubkeylen)) {
397+
return 0;
398+
}
399+
return 1;
400+
}
401+
402+
int secp256k1_silentpayments_recipient_public_data_serialize(const secp256k1_context *ctx, unsigned char *output33, const secp256k1_silentpayments_public_data *public_data) {
403+
secp256k1_pubkey pubkey;
404+
unsigned char input_hash[32];
405+
size_t pubkeylen = 33;
406+
407+
ARG_CHECK(public_data->data[0] == 0);
408+
if (!secp256k1_silentpayments_recipient_public_data_load(ctx, &pubkey, input_hash, public_data)) {
409+
return 0;
410+
}
411+
if (!secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, input_hash)) {
412+
return 0;
413+
}
414+
secp256k1_ec_pubkey_serialize(ctx, output33, &pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED);
415+
return 1;
416+
}
417+
418+
int secp256k1_silentpayments_recipient_public_data_parse(const secp256k1_context *ctx, secp256k1_silentpayments_public_data *public_data, const unsigned char *input33) {
419+
size_t inputlen = 33;
420+
size_t pubkeylen = 65;
421+
secp256k1_pubkey pubkey;
422+
423+
ARG_CHECK(public_data != NULL);
424+
ARG_CHECK(input33 != NULL);
425+
if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, input33, inputlen)) {
426+
return 0;
427+
}
428+
public_data->data[0] = 1;
429+
secp256k1_ec_pubkey_serialize(ctx, &public_data->data[1], &pubkeylen, &pubkey, SECP256K1_EC_UNCOMPRESSED);
430+
memset(&public_data->data[1 + pubkeylen], 0, 32);
431+
return 1;
432+
}
433+
324434
#endif

0 commit comments

Comments
 (0)