Skip to content

Commit 651812d

Browse files
committed
silentpayments: add sender routine
Given a set of private keys, the smallest outpoint, and list of recipients this function handles the entire sender flow: 1. Sum up the private keys 2. Calculate the input_hash 3. For each recipient: 3a. Calculate a shared secret 3b. Create the requested number of outputs This function assumes a single sender context in that it requires the sender to have access to all of the private keys. In the future, this API may be expanded to allow for a multiple senders or for a single sender who does not have access to all private keys at any given time, but for now these modes are considered out of scope / unsafe.
1 parent d1917de commit 651812d

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

include/secp256k1_silentpayments.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define SECP256K1_SILENTPAYMENTS_H
33

44
#include "secp256k1.h"
5+
#include "secp256k1_extrakeys.h"
56

67
#ifdef __cplusplus
78
extern "C" {
@@ -39,6 +40,59 @@ typedef struct {
3940
size_t index;
4041
} secp256k1_silentpayments_recipient;
4142

43+
/** Create Silent Payment outputs for recipient(s).
44+
*
45+
* Given a list of n private keys a_1...a_n (one for each silent payment
46+
* eligible input to spend), a serialized outpoint, and a list of recipients,
47+
* create the taproot outputs:
48+
*
49+
* a_sum = a_1 + a_2 + ... + a_n
50+
* input_hash = hash(outpoint_smallest || (a_sum * G))
51+
* taproot_output = B_spend + hash(a_sum * input_hash * B_scan || k) * G
52+
*
53+
* If necessary, the private keys are negated to enforce the right y-parity.
54+
* For that reason, the private keys have to be passed in via two different parameter
55+
* pairs, depending on whether the seckeys correspond to x-only outputs or not.
56+
*
57+
* Returns: 1 if creation of outputs was successful. 0 if an error occured.
58+
* Args: ctx: pointer to a context object
59+
* Out: generated_outputs: pointer to an array of pointers to xonly pubkeys, one per recipient.
60+
* The order of outputs here matches the original ordering of the
61+
* recipients array.
62+
* In: recipients: pointer to an array of pointers to silent payment recipients,
63+
* where each recipient is a scan public key, a spend public key, and
64+
* an index indicating its position in the original ordering.
65+
* The recipient array will be sorted in place, but generated outputs
66+
* are saved in the `generated_outputs` array to match the ordering
67+
* from the index field. This ensures the caller is able to match the
68+
* generated outputs to the correct silent payment addresses. The same
69+
* recipient can be passed multiple times to create multiple
70+
* outputs for the same recipient.
71+
* n_recipients: the number of recipients. This is equal to the total
72+
* number of outputs to be generated as each recipient may passed
73+
* multiple times to generate multiple outputs for the same recipient
74+
* outpoint_smallest36: serialized smallest outpoint
75+
* taproot_seckeys: pointer to an array of pointers to 32-byte private keys
76+
* of taproot inputs (can be NULL if no private keys of
77+
* taproot inputs are used)
78+
* n_taproot_seckeys: the number of sender's taproot input private keys
79+
* plain_seckeys: pointer to an array of pointers to 32-byte private keys
80+
* of non-taproot inputs (can be NULL if no private keys of
81+
* non-taproot inputs are used)
82+
* n_plain_seckeys: the number of sender's non-taproot input private keys
83+
*/
84+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_create_outputs(
85+
const secp256k1_context *ctx,
86+
secp256k1_xonly_pubkey **generated_outputs,
87+
const secp256k1_silentpayments_recipient **recipients,
88+
size_t n_recipients,
89+
const unsigned char *outpoint_smallest36,
90+
const secp256k1_keypair * const *taproot_seckeys,
91+
size_t n_taproot_seckeys,
92+
const unsigned char * const *plain_seckeys,
93+
size_t n_plain_seckeys
94+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
95+
4296
#ifdef __cplusplus
4397
}
4498
#endif

src/modules/silentpayments/main_impl.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,91 @@ int secp256k1_silentpayments_create_output_pubkey(const secp256k1_context *ctx,
169169
return 1;
170170
}
171171

172+
int secp256k1_silentpayments_sender_create_outputs(
173+
const secp256k1_context *ctx,
174+
secp256k1_xonly_pubkey **generated_outputs,
175+
const secp256k1_silentpayments_recipient **recipients,
176+
size_t n_recipients,
177+
const unsigned char *outpoint_smallest36,
178+
const secp256k1_keypair * const *taproot_seckeys,
179+
size_t n_taproot_seckeys,
180+
const unsigned char * const *plain_seckeys,
181+
size_t n_plain_seckeys
182+
) {
183+
size_t i, k;
184+
secp256k1_scalar a_sum_scalar, addend;
185+
secp256k1_ge A_sum_ge;
186+
secp256k1_gej A_sum_gej;
187+
unsigned char input_hash[32];
188+
unsigned char a_sum[32];
189+
unsigned char shared_secret[33];
190+
secp256k1_silentpayments_recipient last_recipient;
191+
192+
/* Sanity check inputs. */
193+
VERIFY_CHECK(ctx != NULL);
194+
ARG_CHECK(recipients != NULL);
195+
ARG_CHECK(n_recipients >= 1);
196+
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
197+
ARG_CHECK(plain_seckeys == NULL || n_plain_seckeys >= 1);
198+
ARG_CHECK(taproot_seckeys == NULL || n_taproot_seckeys >= 1);
199+
ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL));
200+
ARG_CHECK((n_plain_seckeys + n_taproot_seckeys) >= 1);
201+
ARG_CHECK(outpoint_smallest36 != NULL);
202+
/* ensure the index field is set correctly */
203+
for (i = 0; i < n_recipients; i++) {
204+
ARG_CHECK(recipients[i]->index == i);
205+
}
206+
207+
/* Compute input private keys sum: a_sum = a_1 + a_2 + ... + a_n */
208+
a_sum_scalar = secp256k1_scalar_zero;
209+
for (i = 0; i < n_plain_seckeys; i++) {
210+
if (!secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i])) {
211+
return 0;
212+
}
213+
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
214+
VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar));
215+
}
216+
/* private keys used for taproot outputs have to be negated if they resulted in an odd point */
217+
for (i = 0; i < n_taproot_seckeys; i++) {
218+
secp256k1_ge addend_point;
219+
if (!secp256k1_keypair_load(ctx, &addend, &addend_point, taproot_seckeys[i])) {
220+
return 0;
221+
}
222+
if (secp256k1_fe_is_odd(&addend_point.y)) {
223+
secp256k1_scalar_negate(&addend, &addend);
224+
}
225+
226+
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
227+
VERIFY_CHECK(!secp256k1_scalar_is_zero(&a_sum_scalar));
228+
}
229+
if (secp256k1_scalar_is_zero(&a_sum_scalar)) {
230+
/* TODO: do we need a special error return code for this case? */
231+
return 0;
232+
}
233+
secp256k1_scalar_get_b32(a_sum, &a_sum_scalar);
234+
235+
/* Compute input_hash = hash(outpoint_L || (a_sum * G)) */
236+
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &A_sum_gej, &a_sum_scalar);
237+
secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej);
238+
secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge);
239+
secp256k1_silentpayments_recipient_sort(ctx, recipients, n_recipients);
240+
last_recipient = *recipients[0];
241+
k = 0;
242+
for (i = 0; i < n_recipients; i++) {
243+
if ((secp256k1_ec_pubkey_cmp(ctx, &last_recipient.scan_pubkey, &recipients[i]->scan_pubkey) != 0) || (i == 0)) {
244+
/* if we are on a different scan pubkey, its time to recreate the the shared secret and reset k to 0 */
245+
if (!secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, a_sum, &recipients[i]->scan_pubkey, input_hash)) {
246+
return 0;
247+
}
248+
k = 0;
249+
}
250+
if (!secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret, &recipients[i]->spend_pubkey, k)) {
251+
return 0;
252+
}
253+
k++;
254+
last_recipient = *recipients[i];
255+
}
256+
return 1;
257+
}
258+
172259
#endif

0 commit comments

Comments
 (0)