Skip to content

Commit 71495dc

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 0b07ce0 commit 71495dc

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

include/secp256k1_silentpayments.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,59 @@ typedef struct {
4040
size_t index;
4141
} secp256k1_silentpayments_recipient;
4242

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 they seckeys correspond to x-only outputs or not.
56+
*
57+
* Returns: 1 if shared secret creation 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+
4396
#ifdef __cplusplus
4497
}
4598
#endif

src/modules/silentpayments/main_impl.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,94 @@ int secp256k1_silentpayments_create_output_pubkey(const secp256k1_context *ctx,
173173
return 1;
174174
}
175175

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

0 commit comments

Comments
 (0)