Skip to content

Commit 542aef2

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 8f69297 commit 542aef2

File tree

4 files changed

+620
-2
lines changed

4 files changed

+620
-2
lines changed

include/secp256k1_silentpayments.h

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

src/modules/silentpayments/main_impl.h

Lines changed: 283 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,291 @@
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, uint32_t k) {
108+
secp256k1_sha256 hash;
109+
unsigned char hash_ser[32];
110+
unsigned char k_serialized[4];
111+
int overflow = 0;
112+
113+
/* Compute t_k = hash(shared_secret || ser_32(k)) [sha256 with tag "BIP0352/SharedSecret"] */
114+
secp256k1_silentpayments_sha256_init_sharedsecret(&hash);
115+
secp256k1_sha256_write(&hash, shared_secret33, 33);
116+
secp256k1_write_be32(k_serialized, k);
117+
secp256k1_sha256_write(&hash, k_serialized, sizeof(k_serialized));
118+
secp256k1_sha256_finalize(&hash, hash_ser);
119+
secp256k1_scalar_set_b32(t_k_scalar, hash_ser, &overflow);
120+
VERIFY_CHECK(!overflow);
121+
VERIFY_CHECK(!secp256k1_scalar_is_zero(t_k_scalar));
122+
/* While not technically "secret" data, explicitly clear hash_ser since leaking this would allow an attacker
123+
* to identify the resulting transaction as a silent payments transaction and potentially link the transaction
124+
* back to the silent payment address
125+
*/
126+
secp256k1_memclear(hash_ser, sizeof(hash_ser));
127+
}
128+
129+
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, uint32_t k) {
130+
secp256k1_ge P_output_ge;
131+
secp256k1_scalar t_k_scalar;
132+
int ret;
133+
134+
/* Calculate and return P_output_xonly = B_spend + t_k * G
135+
* This will fail if B_spend is the point at infinity or if
136+
* B_spend + t_k*G is the point at infinity.
137+
*/
138+
secp256k1_silentpayments_create_t_k(&t_k_scalar, shared_secret33, k);
139+
if (!secp256k1_pubkey_load(ctx, &P_output_ge, recipient_spend_pubkey)) {
140+
secp256k1_scalar_clear(&t_k_scalar);
141+
return 0;
142+
}
143+
ret = secp256k1_eckey_pubkey_tweak_add(&P_output_ge, &t_k_scalar);
144+
/* 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. */
145+
/* TODO: consider declassify ret */
146+
/* TODO: but we don't want to imply this can never happen */
147+
VERIFY_CHECK(ret);
148+
#ifndef VERIFY
149+
(void) ret;
150+
#endif
151+
secp256k1_xonly_pubkey_save(P_output_xonly, &P_output_ge);
152+
153+
/* While not technically "secret" data, explicitly clear t_k since leaking this would allow an attacker
154+
* to identify the resulting transaction as a silent payments transaction and potentially link the transaction
155+
* back to the silent payment address
156+
*/
157+
secp256k1_scalar_clear(&t_k_scalar);
158+
return 1;
159+
}
160+
161+
int secp256k1_silentpayments_sender_create_outputs(
162+
const secp256k1_context *ctx,
163+
secp256k1_xonly_pubkey **generated_outputs,
164+
const secp256k1_silentpayments_recipient **recipients,
165+
size_t n_recipients,
166+
const unsigned char *outpoint_smallest36,
167+
const secp256k1_keypair * const *taproot_seckeys,
168+
size_t n_taproot_seckeys,
169+
const unsigned char * const *plain_seckeys,
170+
size_t n_plain_seckeys
171+
) {
172+
size_t i, k;
173+
secp256k1_scalar a_sum_scalar, addend, input_hash_scalar;
174+
secp256k1_ge A_sum_ge;
175+
secp256k1_gej A_sum_gej;
176+
unsigned char input_hash[32];
177+
unsigned char shared_secret[33];
178+
secp256k1_silentpayments_recipient last_recipient;
179+
int overflow = 0;
180+
int ret;
181+
182+
/* Sanity check inputs. */
183+
VERIFY_CHECK(ctx != NULL);
184+
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
185+
ARG_CHECK(generated_outputs != NULL);
186+
ARG_CHECK(recipients != NULL);
187+
ARG_CHECK(n_recipients > 0);
188+
ARG_CHECK((plain_seckeys != NULL) || (taproot_seckeys != NULL));
189+
if (taproot_seckeys != NULL) {
190+
ARG_CHECK(n_taproot_seckeys > 0);
191+
} else {
192+
ARG_CHECK(n_taproot_seckeys == 0);
193+
}
194+
if (plain_seckeys != NULL) {
195+
ARG_CHECK(n_plain_seckeys > 0);
196+
} else {
197+
ARG_CHECK(n_plain_seckeys == 0);
198+
}
199+
ARG_CHECK(outpoint_smallest36 != NULL);
200+
/* ensure the index field is set correctly */
201+
for (i = 0; i < n_recipients; i++) {
202+
ARG_CHECK(recipients[i]->index == i);
203+
}
204+
205+
/* Compute input private keys sum: a_sum = a_1 + a_2 + ... + a_n */
206+
a_sum_scalar = secp256k1_scalar_zero;
207+
for (i = 0; i < n_plain_seckeys; i++) {
208+
ret = secp256k1_scalar_set_b32_seckey(&addend, plain_seckeys[i]);
209+
/* TODO: We can declassify return value, because scalar set only fails if the seckey is invalid */
210+
secp256k1_declassify(ctx, &ret, sizeof(ret));
211+
if (!ret) {
212+
/* TODO: clear a_sum_scalar */
213+
return 0;
214+
}
215+
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
216+
}
217+
/* private keys used for taproot outputs have to be negated if they resulted in an odd point */
218+
for (i = 0; i < n_taproot_seckeys; i++) {
219+
secp256k1_ge addend_point;
220+
ret = secp256k1_keypair_load(ctx, &addend, &addend_point, taproot_seckeys[i]);
221+
/* TODO: we can declassify return value */
222+
if (!ret) {
223+
/* TODO: clear a_sum_scalar */
224+
return 0;
225+
}
226+
secp256k1_declassify(ctx, &ret, sizeof(ret));
227+
if (secp256k1_fe_is_odd(&addend_point.y)) {
228+
secp256k1_scalar_negate(&addend, &addend);
229+
}
230+
secp256k1_scalar_add(&a_sum_scalar, &a_sum_scalar, &addend);
231+
}
232+
/* If there are any failures in loading/summing up the secret keys, fail early */
233+
/* TODO: can we declassify this? */
234+
/* Yes: We assume the adversary has access to a_sum_scalar*G */
235+
ret = secp256k1_scalar_is_zero(&a_sum_scalar);
236+
secp256k1_declassify(ctx, &ret, sizeof(ret));
237+
if (ret) {
238+
return 0;
239+
}
240+
/* Compute input_hash = hash(outpoint_L || (a_sum * G)) */
241+
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &A_sum_gej, &a_sum_scalar);
242+
secp256k1_ge_set_gej(&A_sum_ge, &A_sum_gej);
243+
/* TODO: comment */
244+
secp256k1_declassify(ctx, &A_sum_ge, sizeof(A_sum_ge));
245+
246+
/* Calculate the input hash and tweak a_sum, i.e., a_sum_tweaked = a_sum * input_hash */
247+
secp256k1_silentpayments_calculate_input_hash(input_hash, outpoint_smallest36, &A_sum_ge);
248+
secp256k1_scalar_set_b32(&input_hash_scalar, input_hash, &overflow);
249+
/* This should fail if input hash is greater than the curver order, but this is stastically improbable so
250+
* we only do a verify_check here.
251+
*/
252+
VERIFY_CHECK(!overflow);
253+
secp256k1_scalar_mul(&a_sum_scalar, &a_sum_scalar, &input_hash_scalar);
254+
/* _recipient_sort sorts the array of recipients in place by their scan public keys (lexicographically).
255+
* This ensures that all recipients with the same scan public key are grouped together, as specified in BIP0352.
256+
*
257+
* More specifically, this ensures `k` is incremented from 0 to the number of requested outputs for each recipient group,
258+
* where a recipient group is all addresses with the same scan public key.
259+
*/
260+
secp256k1_silentpayments_recipient_sort(ctx, recipients, n_recipients);
261+
last_recipient = *recipients[0];
262+
k = 0;
263+
for (i = 0; i < n_recipients; i++) {
264+
if ((i == 0) || (secp256k1_ec_pubkey_cmp(ctx, &last_recipient.scan_pubkey, &recipients[i]->scan_pubkey) != 0)) {
265+
/* If we are on a different scan pubkey, its time to recreate the the shared secret and reset k to 0.
266+
* It's very unlikely the scan public key is invalid by this point, since this means the caller would
267+
* have created the _silentpayments_recipient object incorrectly, but just to be sure we still check that
268+
* the public key is valid.
269+
*/
270+
secp256k1_ge pk;
271+
if (!secp256k1_pubkey_load(ctx, &pk, &recipients[i]->scan_pubkey)) {
272+
/* TODO: clean up */
273+
return 0;
274+
}
275+
secp256k1_silentpayments_create_shared_secret(ctx, shared_secret, &a_sum_scalar, &pk);
276+
k = 0;
277+
}
278+
if (!secp256k1_silentpayments_create_output_pubkey(ctx, generated_outputs[recipients[i]->index], shared_secret, &recipients[i]->spend_pubkey, k)) {
279+
/* TODO: clean up */
280+
return 0;
281+
}
282+
k++;
283+
last_recipient = *recipients[i];
284+
}
285+
/* Explicitly clear variables containing secret data */
286+
secp256k1_scalar_clear(&addend);
287+
secp256k1_scalar_clear(&a_sum_scalar);
288+
289+
/* While technically not "secret data," explicitly clear the shared secret since leaking this
290+
* could result in a third party being able to identify the transaction as a silent payments transaction
291+
* and potentially link the transaction back to a silent payment address
292+
*/
293+
secp256k1_memclear(&shared_secret, sizeof(shared_secret));
294+
return 1;
295+
}
15296

16297
#endif

0 commit comments

Comments
 (0)