Skip to content

Commit 4fb8716

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 987d829 commit 4fb8716

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed

include/secp256k1_silentpayments.h

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

141+
/** Opaque data structure that holds silent payments public input data.
142+
*
143+
* This structure does not contain secret data. Guaranteed to be 98 bytes in size. It can be safely
144+
* copied/moved. Created with `secp256k1_silentpayments_compute_public_data`. Can be serialized as
145+
* a compressed public key using `secp256k1_silentpayments_public_data_serialize`. The serialization
146+
* is intended for sending the public input data to light clients. Light clients can use this
147+
* serialization with `secp256k1_silentpayments_public_data_parse`.
148+
*/
149+
typedef struct {
150+
unsigned char data[98];
151+
} secp256k1_silentpayments_public_data;
152+
153+
/** Parse a 33-byte sequence into a silent_payments_public_data object.
154+
*
155+
* Returns: 1 if the data was able to be parsed.
156+
* 0 if the sequence is invalid (e.g. does not represnt a valid public key).
157+
*
158+
* Args: ctx: pointer to a context object.
159+
* Out: public_data: pointer to a silentpayments_public_data object. If 1 is returned, it is set to a
160+
* parsed version of input33.
161+
* In: input33: pointer to a serialized silentpayments_public_data.
162+
*/
163+
164+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_parse(
165+
const secp256k1_context *ctx,
166+
secp256k1_silentpayments_public_data *public_data,
167+
const unsigned char *input33
168+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
169+
170+
/** Serialize a silentpayments_public_data object into a 33-byte sequence.
171+
*
172+
* Returns: 1 always.
173+
*
174+
* Args: ctx: pointer to a context object.
175+
* Out: output33: pointer to a 32-byte array to place the serialized key in.
176+
* In: public_data: pointer to an initialized silentpayments_public_data object.
177+
*/
178+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_serialize(
179+
const secp256k1_context *ctx,
180+
unsigned char *output33,
181+
const secp256k1_silentpayments_public_data *public_data
182+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
183+
184+
/** Compute Silent Payment public data from input public keys and transaction inputs.
185+
*
186+
* Given a list of n public keys A_1...A_n (one for each silent payment
187+
* eligible input to spend) and a serialized outpoint_smallest, compute
188+
* the corresponding input public tweak data:
189+
*
190+
* A_sum = A_1 + A_2 + ... + A_n
191+
* input_hash = hash(outpoint_lowest || A_sum)
192+
*
193+
* The public keys have to be passed in via two different parameter pairs,
194+
* one for regular and one for x-only public keys, in order to avoid the need
195+
* of users converting to a common pubkey format before calling this function.
196+
* The resulting data is can be used for scanning on the recipient side, or stored
197+
* in an index for late use (e.g. wallet rescanning, vending data to light clients).
198+
*
199+
* If calling this function for scanning, the reciever must provide an output param
200+
* for the `input_hash`. If calling this function for simply aggregating the inputs
201+
* for later use, the caller can save the result with `silentpayments_public_data_serialize`.
202+
*
203+
* Returns: 1 if tweak data creation was successful. 0 if an error occured.
204+
* Args: ctx: pointer to a context object
205+
* Out: public_data: pointer to public_data object containing the summed public key and
206+
* input_hash.
207+
* In: outpoint_smallest36: serialized smallest outpoint
208+
* xonly_pubkeys: pointer to an array of pointers to taproot x-only
209+
* public keys (can be NULL if no taproot inputs are used)
210+
* n_xonly_pubkeys: the number of taproot input public keys
211+
* plain_pubkeys: pointer to an array of pointers to non-taproot
212+
* public keys (can be NULL if no non-taproot inputs are used)
213+
* n_plain_pubkeys: the number of non-taproot input public keys
214+
*/
215+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_recipient_public_data_create(
216+
const secp256k1_context *ctx,
217+
secp256k1_silentpayments_public_data *public_data,
218+
const unsigned char *outpoint_smallest36,
219+
const secp256k1_xonly_pubkey * const *xonly_pubkeys,
220+
size_t n_xonly_pubkeys,
221+
const secp256k1_pubkey * const *plain_pubkeys,
222+
size_t n_plain_pubkeys
223+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
224+
141225
#ifdef __cplusplus
142226
}
143227
#endif

src/modules/silentpayments/main_impl.h

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,4 +328,109 @@ int secp256k1_silentpayments_recipient_create_labelled_spend_pubkey(const secp25
328328
return 1;
329329
}
330330

331+
int secp256k1_silentpayments_recipient_public_data_create(
332+
const secp256k1_context *ctx,
333+
secp256k1_silentpayments_public_data *public_data,
334+
const unsigned char *outpoint_smallest36,
335+
const secp256k1_xonly_pubkey * const *xonly_pubkeys,
336+
size_t n_xonly_pubkeys,
337+
const secp256k1_pubkey * const *plain_pubkeys,
338+
size_t n_plain_pubkeys
339+
) {
340+
size_t i;
341+
size_t pubkeylen = 65;
342+
secp256k1_pubkey A_sum;
343+
secp256k1_ge A_sum_ge, addend;
344+
secp256k1_gej A_sum_gej;
345+
unsigned char input_hash_local[32];
346+
347+
/* Sanity check inputs */
348+
VERIFY_CHECK(ctx != NULL);
349+
ARG_CHECK(public_data != NULL);
350+
ARG_CHECK(plain_pubkeys == NULL || n_plain_pubkeys >= 1);
351+
ARG_CHECK(xonly_pubkeys == NULL || n_xonly_pubkeys >= 1);
352+
ARG_CHECK((plain_pubkeys != NULL) || (xonly_pubkeys != NULL));
353+
ARG_CHECK((n_plain_pubkeys + n_xonly_pubkeys) >= 1);
354+
ARG_CHECK(outpoint_smallest36 != NULL);
355+
memset(input_hash_local, 0, 32);
356+
357+
/* Compute input public keys sum: A_sum = A_1 + A_2 + ... + A_n */
358+
secp256k1_gej_set_infinity(&A_sum_gej);
359+
for (i = 0; i < n_plain_pubkeys; i++) {
360+
secp256k1_pubkey_load(ctx, &addend, plain_pubkeys[i]);
361+
secp256k1_gej_add_ge(&A_sum_gej, &A_sum_gej, &addend);
362+
}
363+
for (i = 0; i < n_xonly_pubkeys; i++) {
364+
secp256k1_xonly_pubkey_load(ctx, &addend, xonly_pubkeys[i]);
365+
secp256k1_gej_add_ge(&A_sum_gej, &A_sum_gej, &addend);
366+
}
367+
if (secp256k1_gej_is_infinity(&A_sum_gej)) {
368+
/* TODO: do we need a special error return code for this case? */
369+
return 0;
370+
}
371+
secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej);
372+
373+
/* Compute input_hash = hash(outpoint_L || A_sum) */
374+
secp256k1_silentpayments_calculate_input_hash(input_hash_local, outpoint_smallest36, &A_sum_ge);
375+
secp256k1_pubkey_save(&A_sum, &A_sum_ge);
376+
/* serialize the public_data struct */
377+
public_data->data[0] = 0;
378+
secp256k1_ec_pubkey_serialize(ctx, &public_data->data[1], &pubkeylen, &A_sum, SECP256K1_EC_UNCOMPRESSED);
379+
memcpy(&public_data->data[1 + pubkeylen], input_hash_local, 32);
380+
return 1;
381+
}
382+
383+
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) {
384+
int combined;
385+
size_t pubkeylen = 65;
386+
VERIFY_CHECK(ctx != NULL);
387+
ARG_CHECK(pubkey != NULL);
388+
ARG_CHECK(public_data != NULL);
389+
390+
combined = (int)public_data->data[0];
391+
ARG_CHECK(combined == 0 || combined == 1);
392+
if (combined) {
393+
ARG_CHECK(combined == 1 && input_hash == NULL);
394+
} else {
395+
ARG_CHECK(combined == 0 && input_hash != NULL);
396+
memcpy(input_hash, &public_data->data[1 + pubkeylen], 32);
397+
}
398+
if (!secp256k1_ec_pubkey_parse(ctx, pubkey, &public_data->data[1], pubkeylen)) {
399+
return 0;
400+
}
401+
return 1;
402+
}
403+
404+
int secp256k1_silentpayments_recipient_public_data_serialize(const secp256k1_context *ctx, unsigned char *output33, const secp256k1_silentpayments_public_data *public_data) {
405+
secp256k1_pubkey pubkey;
406+
unsigned char input_hash[32];
407+
size_t pubkeylen = 33;
408+
409+
ARG_CHECK(public_data->data[0] == 0);
410+
if (!secp256k1_silentpayments_recipient_public_data_load(ctx, &pubkey, input_hash, public_data)) {
411+
return 0;
412+
}
413+
if (!secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, input_hash)) {
414+
return 0;
415+
}
416+
secp256k1_ec_pubkey_serialize(ctx, output33, &pubkeylen, &pubkey, SECP256K1_EC_COMPRESSED);
417+
return 1;
418+
}
419+
420+
int secp256k1_silentpayments_recipient_public_data_parse(const secp256k1_context *ctx, secp256k1_silentpayments_public_data *public_data, const unsigned char *input33) {
421+
size_t inputlen = 33;
422+
size_t pubkeylen = 65;
423+
secp256k1_pubkey pubkey;
424+
425+
ARG_CHECK(public_data != NULL);
426+
ARG_CHECK(input33 != NULL);
427+
if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, input33, inputlen)) {
428+
return 0;
429+
}
430+
public_data->data[0] = 1;
431+
secp256k1_ec_pubkey_serialize(ctx, &public_data->data[1], &pubkeylen, &pubkey, SECP256K1_EC_UNCOMPRESSED);
432+
memset(&public_data->data[1 + pubkeylen], 0, 32);
433+
return 1;
434+
}
435+
331436
#endif

0 commit comments

Comments
 (0)