Skip to content

Commit f375161

Browse files
committed
silentpayments: sending
Add a routine for the entire sending flow which takes a set of private keys, the smallest outpoint, and list of recipients and returns a list of x-only public keys by performing the following steps: 1. Sum up the private keys 2. Calculate the input_hash 3. For each recipient group: 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. Internal to the library, add: 1. A function for creating shared secrets (i.e., a*B or b*A) 2. A function for generating the "SharedSecret" tagged hash 3. A function for creating a single output public key
1 parent 91fc7fc commit f375161

File tree

4 files changed

+611
-2
lines changed

4 files changed

+611
-2
lines changed

include/secp256k1_silentpayments.h

Lines changed: 87 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" {
@@ -25,6 +26,92 @@ extern "C" {
2526
* any further elliptic-curve operations from the wallet.
2627
*/
2728

29+
/** This struct serves as an input parameter for passing the silent payment
30+
* address data.
31+
*
32+
* The index field is for when more than one address is being sent to in
33+
* a transaction. Index is set based on the original ordering of the addresses
34+
* and used to return the generated outputs matching the original ordering.
35+
* When more than one recipient is used, the recipient array will be sorted in
36+
* place as part of generating the outputs, but the generated outputs will be
37+
* returned in the original ordering specified by the index to ensure the
38+
* caller is able to match up the generated outputs to the correct silent
39+
* payment address (e.g., to be able to assign the correct amounts to the
40+
* correct generated outputs in the final transaction).
41+
*/
42+
typedef struct {
43+
secp256k1_pubkey scan_pubkey;
44+
secp256k1_pubkey spend_pubkey;
45+
size_t index;
46+
} secp256k1_silentpayments_recipient;
47+
48+
/** Create Silent Payment outputs for recipient(s).
49+
*
50+
* Given a list of n secret keys a_1...a_n (one for each silent payment
51+
* eligible input to spend), a serialized outpoint, and a list of recipients,
52+
* create the taproot outputs.
53+
*
54+
* `outpoint_smallest` refers to the smallest outpoint lexicographically
55+
* from the transaction inputs (both silent payments eligible and non-eligible
56+
* inputs). This value MUST be the smallest outpoint out of all of the
57+
* transaction inputs, otherwise the recipient will be unable to find the
58+
* payment. Determining the smallest outpoint from the list of transaction
59+
* inputs is the responsibility of the caller. It is strongly recommended
60+
* that implementations ensure they are doing this correctly by using the
61+
* test vectors from BIP352.
62+
*
63+
* If necessary, the secret keys are negated to enforce the right y-parity.
64+
* For that reason, the secret keys have to be passed in via two different
65+
* parameter pairs, depending on whether the seckeys correspond to x-only
66+
* outputs or not.
67+
*
68+
* Returns: 1 if creation of outputs was successful. 0 if an error occured.
69+
* Args: ctx: pointer to a context object
70+
* Out: generated_outputs: pointer to an array of pointers to xonly pubkeys,
71+
* one per recipient.
72+
* The outputs here are sorted by the index value
73+
* provided in the recipient objects.
74+
* In: recipients: pointer to an array of pointers to silent payment
75+
* recipients, where each recipient is a scan public
76+
* key, a spend public key, and an index indicating
77+
* its position in the original ordering. The
78+
* recipient array will be sorted in place, but
79+
* generated outputs are saved in the
80+
* `generated_outputs` array to match the ordering
81+
* from the index field. This ensures the caller is
82+
* able to match the generated outputs to the
83+
* correct silent payment addresses. The same
84+
* recipient can be passed multiple times to create
85+
* multiple outputs for the same recipient.
86+
* n_recipients: the number of recipients. This is equal to the
87+
* total number of outputs to be generated as each
88+
* recipient may passed multiple times to generate
89+
* multiple outputs for the same recipient
90+
* outpoint_smallest: serialized (36-byte) smallest outpoint
91+
* (lexicographically) from the transaction inputs
92+
* taproot_seckeys: pointer to an array of pointers to taproot
93+
* keypair inputs (can be NULL if no secret keys
94+
* of taproot inputs are used)
95+
* n_taproot_seckeys: the number of sender's taproot input secret keys
96+
* plain_seckeys: pointer to an array of pointers to 32-byte
97+
* secret keys of non-taproot inputs (can be NULL
98+
* if no secret keys of non-taproot inputs are
99+
* used)
100+
* n_plain_seckeys: the number of sender's non-taproot input secret
101+
* keys
102+
*/
103+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_silentpayments_sender_create_outputs(
104+
const secp256k1_context *ctx,
105+
secp256k1_xonly_pubkey **generated_outputs,
106+
const secp256k1_silentpayments_recipient **recipients,
107+
size_t n_recipients,
108+
const unsigned char *outpoint_smallest36,
109+
const secp256k1_keypair * const *taproot_seckeys,
110+
size_t n_taproot_seckeys,
111+
const unsigned char * const *plain_seckeys,
112+
size_t n_plain_seckeys
113+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
114+
28115
#ifdef __cplusplus
29116
}
30117
#endif

src/modules/silentpayments/main_impl.h

Lines changed: 274 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,282 @@
77
#define SECP256K1_MODULE_SILENTPAYMENTS_MAIN_H
88

99
#include "../../../include/secp256k1.h"
10+
#include "../../../include/secp256k1_extrakeys.h"
1011
#include "../../../include/secp256k1_silentpayments.h"
1112

12-
/* TODO: implement functions for sender side. */
13+
/** Sort an array of silent payment recipients. This is used to group recipients by scan pubkey to
14+
* ensure the correct values of k are used when creating multiple outputs for a recipient. */
15+
static int secp256k1_silentpayments_recipient_sort_cmp(const void* pk1, const void* pk2, void *ctx) {
16+
return secp256k1_ec_pubkey_cmp((secp256k1_context *)ctx,
17+
&(*(const secp256k1_silentpayments_recipient **)pk1)->scan_pubkey,
18+
&(*(const secp256k1_silentpayments_recipient **)pk2)->scan_pubkey
19+
);
20+
}
1321

14-
/* TODO: implement functions for receiver side. */
22+
static void secp256k1_silentpayments_recipient_sort(const secp256k1_context* ctx, const secp256k1_silentpayments_recipient **recipients, size_t n_recipients) {
23+
24+
/* Suppress wrong warning (fixed in MSVC 19.33) */
25+
#if defined(_MSC_VER) && (_MSC_VER < 1933)
26+
#pragma warning(push)
27+
#pragma warning(disable: 4090)
28+
#endif
29+
30+
secp256k1_hsort(recipients, n_recipients, sizeof(*recipients), secp256k1_silentpayments_recipient_sort_cmp, (void *)ctx);
31+
32+
#if defined(_MSC_VER) && (_MSC_VER < 1933)
33+
#pragma warning(pop)
34+
#endif
35+
}
36+
37+
/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/Inputs". */
38+
static void secp256k1_silentpayments_sha256_init_inputs(secp256k1_sha256* hash) {
39+
secp256k1_sha256_initialize(hash);
40+
hash->s[0] = 0xd4143ffcul;
41+
hash->s[1] = 0x012ea4b5ul;
42+
hash->s[2] = 0x36e21c8ful;
43+
hash->s[3] = 0xf7ec7b54ul;
44+
hash->s[4] = 0x4dd4e2acul;
45+
hash->s[5] = 0x9bcaa0a4ul;
46+
hash->s[6] = 0xe244899bul;
47+
hash->s[7] = 0xcd06903eul;
48+
49+
hash->bytes = 64;
50+
}
51+
52+
static void secp256k1_silentpayments_calculate_input_hash(unsigned char *input_hash, const unsigned char *outpoint_smallest36, secp256k1_ge *pubkey_sum) {
53+
secp256k1_sha256 hash;
54+
unsigned char pubkey_sum_ser[33];
55+
size_t len;
56+
int ret;
57+
58+
secp256k1_silentpayments_sha256_init_inputs(&hash);
59+
secp256k1_sha256_write(&hash, outpoint_smallest36, 36);
60+
ret = secp256k1_eckey_pubkey_serialize(pubkey_sum, pubkey_sum_ser, &len, 1);
61+
VERIFY_CHECK(ret && len == sizeof(pubkey_sum_ser));
62+
(void)ret;
63+
secp256k1_sha256_write(&hash, pubkey_sum_ser, sizeof(pubkey_sum_ser));
64+
secp256k1_sha256_finalize(&hash, input_hash);
65+
}
66+
67+
static void secp256k1_silentpayments_create_shared_secret(const secp256k1_context *ctx, unsigned char *shared_secret33, const secp256k1_scalar *secret_component, const secp256k1_ge *public_component) {
68+
secp256k1_gej ss_j;
69+
secp256k1_ge ss;
70+
size_t len;
71+
int ret;
72+
73+
/* Compute shared_secret = tweaked_secret_component * Public_component */
74+
secp256k1_ecmult_const(&ss_j, public_component, secret_component);
75+
secp256k1_ge_set_gej(&ss, &ss_j);
76+
secp256k1_declassify(ctx, &ss, sizeof(ss));
77+
/* This can only fail if the shared secret is the point at infinity, which should be
78+
* impossible at this point, considering we have already validated the public key and
79+
* the secret key being used
80+
*/
81+
ret = secp256k1_eckey_pubkey_serialize(&ss, shared_secret33, &len, 1);
82+
VERIFY_CHECK(ret && len == 33);
83+
(void)ret;
84+
/* While not technically "secret" data, explicitly clear the shared secret since leaking this would allow an attacker
85+
* to identify the resulting transaction as a silent payments transaction and potentially link the transaction
86+
* back to the silent payment address
87+
*/
88+
secp256k1_ge_clear(&ss);
89+
secp256k1_gej_clear(&ss_j);
90+
}
91+
92+
/** Set hash state to the BIP340 tagged hash midstate for "BIP0352/SharedSecret". */
93+
static void secp256k1_silentpayments_sha256_init_sharedsecret(secp256k1_sha256* hash) {
94+
secp256k1_sha256_initialize(hash);
95+
hash->s[0] = 0x88831537ul;
96+
hash->s[1] = 0x5127079bul;
97+
hash->s[2] = 0x69c2137bul;
98+
hash->s[3] = 0xab0303e6ul;
99+
hash->s[4] = 0x98fa21faul;
100+
hash->s[5] = 0x4a888523ul;
101+
hash->s[6] = 0xbd99daabul;
102+
hash->s[7] = 0xf25e5e0aul;
103+
104+
hash->bytes = 64;
105+
}
106+
107+
static void secp256k1_silentpayments_create_t_k(secp256k1_scalar *t_k_scalar, const unsigned char *shared_secret33, unsigned int k) {
108+
secp256k1_sha256 hash;
109+
unsigned char hash_ser[32];
110+
unsigned char k_serialized[4];
111+
112+
/* Compute t_k = hash(shared_secret || ser_32(k)) [sha256 with tag "BIP0352/SharedSecret"] */
113+
secp256k1_silentpayments_sha256_init_sharedsecret(&hash);
114+
secp256k1_sha256_write(&hash, shared_secret33, 33);
115+
secp256k1_write_be32(k_serialized, k);
116+
secp256k1_sha256_write(&hash, k_serialized, sizeof(k_serialized));
117+
secp256k1_sha256_finalize(&hash, hash_ser);
118+
secp256k1_scalar_set_b32(t_k_scalar, hash_ser, NULL);
119+
/* While not technically "secret" data, explicitly clear hash_ser since leaking this would allow an attacker
120+
* to identify the resulting transaction as a silent payments transaction and potentially link the transaction
121+
* back to the silent payment address
122+
*/
123+
memset(hash_ser, 0, sizeof(hash_ser));
124+
}
125+
126+
static int secp256k1_silentpayments_create_output_pubkey(const secp256k1_context *ctx, secp256k1_xonly_pubkey *P_output_xonly, const unsigned char *shared_secret33, const secp256k1_pubkey *recipient_spend_pubkey, unsigned int k) {
127+
secp256k1_ge P_output_ge;
128+
secp256k1_scalar t_k_scalar;
129+
int ret;
130+
131+
/* Calculate and return P_output_xonly = B_spend + t_k * G
132+
* This will fail if B_spend is the point at infinity or if
133+
* B_spend + t_k*G is the point at infinity.
134+
*/
135+
secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret33, k);
136+
if (!secp256k1_pubkey_load(ctx, &P_output_ge, recipient_spend_pubkey)) {
137+
secp256k1_scalar_clear(&t_k_scalar);
138+
return 0;
139+
}
140+
ret = secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar);
141+
/* tweak add only fails if t_k_scalar is equal to the dlog of P_output_ge, but t_k_scalar is the output of a collision resistant hash function. */
142+
/* TODO: consider declassify ret */
143+
/* TODO: but we don't want to imply this can never happen */
144+
VERIFY_CHECK(ret);
145+
#ifndef VERIFY
146+
(void) ret;
147+
#endif
148+
secp256k1_xonly_pubkey_save(P_output_xonly, &P_output_ge);
149+
150+
/* While not technically "secret" data, explicitly clear t_k since leaking this would allow an attacker
151+
* to identify the resulting transaction as a silent payments transaction and potentially link the transaction
152+
* back to the silent payment address
153+
*/
154+
secp256k1_scalar_clear(&t_k_scalar);
155+
return 1;
156+
}
157+
158+
int secp256k1_silentpayments_sender_create_outputs(
159+
const secp256k1_context *ctx,
160+
secp256k1_xonly_pubkey **generated_outputs,
161+
const secp256k1_silentpayments_recipient **recipients,
162+
size_t n_recipients,
163+
const unsigned char *outpoint_smallest36,
164+
const secp256k1_keypair * const *taproot_seckeys,
165+
size_t n_taproot_seckeys,
166+
const unsigned char * const *plain_seckeys,
167+
size_t n_plain_seckeys
168+
) {
169+
size_t i, k;
170+
secp256k1_scalar a_sum_scalar, addend, input_hash_scalar;
171+
secp256k1_ge A_sum_ge;
172+
secp256k1_gej A_sum_gej;
173+
unsigned char input_hash[32];
174+
unsigned char shared_secret[33];
175+
secp256k1_silentpayments_recipient last_recipient;
176+
int overflow = 0;
177+
int ret;
178+
179+
/* Sanity check inputs. */
180+
VERIFY_CHECK(ctx != NULL);
181+
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
182+
ARG_CHECK(generated_outputs != NULL);
183+
ARG_CHECK(recipients != NULL);
184+
ARG_CHECK(n_recipients > 0);
185+
ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL));
186+
if (taproot_seckeys != NULL) {
187+
ARG_CHECK(n_taproot_seckeys > 0);
188+
} else {
189+
ARG_CHECK(n_taproot_seckeys == 0);
190+
}
191+
if (plain_seckeys != NULL) {
192+
ARG_CHECK(n_plain_seckeys > 0);
193+
} else {
194+
ARG_CHECK(n_plain_seckeys == 0);
195+
}
196+
ARG_CHECK(outpoint_smallest36 != NULL);
197+
/* ensure the index field is set correctly */
198+
for (i = 0; i < n_recipients; i++) {
199+
ARG_CHECK(recipients[i]->index == i);
200+
}
201+
202+
/* Compute input private keys sum: a_sum = a_1 + a_2 + ... + a_n */
203+
a_sum_scalar = secp256k1_scalar_zero;
204+
for (i = 0; i < n_plain_seckeys; i++) {
205+
ret = secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]);
206+
/* TODO: We can declassify return value, because scalar set only fails if the seckey is invalid */
207+
secp256k1_declassify(ctx, &ret, sizeof(ret));
208+
if (!ret) {
209+
/* TODO: clear a_sum_scalar */
210+
return 0;
211+
}
212+
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
213+
}
214+
/* private keys used for taproot outputs have to be negated if they resulted in an odd point */
215+
for (i = 0; i < n_taproot_seckeys; i++) {
216+
secp256k1_ge addend_point;
217+
ret = secp256k1_keypair_load(ctx, &addend, &addend_point, taproot_seckeys[i]);
218+
/* TODO: we can declassify return value */
219+
if (!ret) {
220+
/* TODO: clear a_sum_scalar */
221+
return 0;
222+
}
223+
secp256k1_declassify(ctx, &ret, sizeof(ret));
224+
if (secp256k1_fe_is_odd(&addend_point.y)) {
225+
secp256k1_scalar_negate(&addend, &addend);
226+
}
227+
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
228+
}
229+
/* If there are any failures in loading/summing up the secret keys, fail early */
230+
/* TODO: can we declassify this? */
231+
/* Yes: We assume the adversary has access to a_sum_scalar*G */
232+
ret = secp256k1_scalar_is_zero(&a_sum_scalar);
233+
secp256k1_declassify(ctx, &ret, sizeof(ret));
234+
if (ret) {
235+
return 0;
236+
}
237+
/* Compute input_hash = hash(outpoint_L || (a_sum * G)) */
238+
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &A_sum_gej, &a_sum_scalar);
239+
secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej);
240+
/* TODO: comment */
241+
secp256k1_declassify(ctx, &A_sum_ge, sizeof(A_sum_ge));
242+
243+
/* Calculate the input hash and tweak a_sum, i.e., a_sum_tweaked = a_sum * input_hash */
244+
secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge);
245+
secp256k1_scalar_set_b32(&input_hash_scalar, input_hash, &overflow);
246+
/* TODO: consider VERIFY_CHECK ??? */
247+
if (overflow) {
248+
return 0;
249+
}
250+
secp256k1_scalar_mul(&a_sum_scalar, &a_sum_scalar, &input_hash_scalar);
251+
secp256k1_silentpayments_recipient_sort(ctx, recipients, n_recipients);
252+
last_recipient = *recipients[0];
253+
k = 0;
254+
for (i = 0; i < n_recipients; i++) {
255+
if ((i == 0) || (secp256k1_ec_pubkey_cmp(ctx, &last_recipient.scan_pubkey, &recipients[i]->scan_pubkey) != 0)) {
256+
/* If we are on a different scan pubkey, its time to recreate the the shared secret and reset k to 0.
257+
* It's very unlikely the scan public key is invalid by this point, since this means the caller would
258+
* have created the _silentpayments_recipient object incorrectly, but just to be sure we still check that
259+
* the public key is valid.
260+
*/
261+
secp256k1_ge pk;
262+
if (!secp256k1_pubkey_load(ctx, &pk, &recipients[i]->scan_pubkey)) {
263+
/* TODO: clean up */
264+
return 0;
265+
}
266+
secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, &a_sum_scalar, &pk);
267+
k = 0;
268+
}
269+
if (!secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret, &recipients[i]->spend_pubkey, k)) {
270+
/* TODO: clean up */
271+
return 0;
272+
}
273+
k++;
274+
last_recipient = *recipients[i];
275+
}
276+
/* Explicitly clear variables containing secret data */
277+
secp256k1_scalar_clear(&addend);
278+
secp256k1_scalar_clear(&a_sum_scalar);
279+
280+
/* While technically not "secret data," explicitly clear the shared secret since leaking this
281+
* could result in a third party being able to identify the transaction as a silent payments transaction
282+
* and potentially link the transaction back to a silent payment address
283+
*/
284+
memset(&shared_secret, 0, sizeof(shared_secret));
285+
return 1;
286+
}
15287

16288
#endif

0 commit comments

Comments
 (0)