Skip to content

Commit 58fd134

Browse files
jonasnickdeadalnix
authored andcommitted
extrakeys: Add xonly_pubkey_tweak_add & xonly_pubkey_tweak_add_test
Summary: This is a partial backport of secp256k1 [[bitcoin-core/secp256k1#558 | PR558]] : bitcoin-core/secp256k1@910d9c2 Depends on D7639 Test Plan: cmake -GNinja .. -DSECP256K1_ENABLE_MODULE_EXTRAKEYS=On ninja check-secp256k1 Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D7641
1 parent cd45ae7 commit 58fd134

File tree

3 files changed

+279
-0
lines changed

3 files changed

+279
-0
lines changed

Diff for: include/secp256k1_extrakeys.h

+64
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,70 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_from_pubke
7676
const secp256k1_pubkey *pubkey
7777
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);
7878

79+
/** Tweak an x-only public key by adding the generator multiplied with tweak32
80+
* to it.
81+
*
82+
* Note that the resulting point can not in general be represented by an x-only
83+
* pubkey because it may have an odd Y coordinate. Instead, the output_pubkey
84+
* is a normal secp256k1_pubkey.
85+
*
86+
* Returns: 0 if the arguments are invalid or the resulting public key would be
87+
* invalid (only when the tweak is the negation of the corresponding
88+
* secret key). 1 otherwise.
89+
*
90+
* Args: ctx: pointer to a context object initialized for verification
91+
* (cannot be NULL)
92+
* Out: output_pubkey: pointer to a public key to store the result. Will be set
93+
* to an invalid value if this function returns 0 (cannot
94+
* be NULL)
95+
* In: internal_pubkey: pointer to an x-only pubkey to apply the tweak to.
96+
* (cannot be NULL).
97+
* tweak32: pointer to a 32-byte tweak. If the tweak is invalid
98+
* according to secp256k1_ec_seckey_verify, this function
99+
* returns 0. For uniformly random 32-byte arrays the
100+
* chance of being invalid is negligible (around 1 in
101+
* 2^128) (cannot be NULL).
102+
*/
103+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add(
104+
const secp256k1_context* ctx,
105+
secp256k1_pubkey *output_pubkey,
106+
const secp256k1_xonly_pubkey *internal_pubkey,
107+
const unsigned char *tweak32
108+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
109+
110+
/** Checks that a tweaked pubkey is the result of calling
111+
* secp256k1_xonly_pubkey_tweak_add with internal_pubkey and tweak32.
112+
*
113+
* The tweaked pubkey is represented by its 32-byte x-only serialization and
114+
* its pk_parity, which can both be obtained by converting the result of
115+
* tweak_add to a secp256k1_xonly_pubkey.
116+
*
117+
* Note that this alone does _not_ verify that the tweaked pubkey is a
118+
* commitment. If the tweak is not chosen in a specific way, the tweaked pubkey
119+
* can easily be the result of a different internal_pubkey and tweak.
120+
*
121+
* Returns: 0 if the arguments are invalid or the tweaked pubkey is not the
122+
* result of tweaking the internal_pubkey with tweak32. 1 otherwise.
123+
* Args: ctx: pointer to a context object initialized for verification
124+
* (cannot be NULL)
125+
* In: tweaked_pubkey32: pointer to a serialized xonly_pubkey (cannot be NULL)
126+
* tweaked_pk_parity: the parity of the tweaked pubkey (whose serialization
127+
* is passed in as tweaked_pubkey32). This must match the
128+
* pk_parity value that is returned when calling
129+
* secp256k1_xonly_pubkey with the tweaked pubkey, or
130+
* this function will fail.
131+
* internal_pubkey: pointer to an x-only public key object to apply the
132+
* tweak to (cannot be NULL)
133+
* tweak32: pointer to a 32-byte tweak (cannot be NULL)
134+
*/
135+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_xonly_pubkey_tweak_add_check(
136+
const secp256k1_context* ctx,
137+
const unsigned char *tweaked_pubkey32,
138+
int tweaked_pk_parity,
139+
const secp256k1_xonly_pubkey *internal_pubkey,
140+
const unsigned char *tweak32
141+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
142+
79143
#ifdef __cplusplus
80144
}
81145
#endif

Diff for: src/modules/extrakeys/main_impl.h

+40
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,44 @@ int secp256k1_xonly_pubkey_from_pubkey(const secp256k1_context* ctx, secp256k1_x
8585
return 1;
8686
}
8787

88+
int secp256k1_xonly_pubkey_tweak_add(const secp256k1_context* ctx, secp256k1_pubkey *output_pubkey, const secp256k1_xonly_pubkey *internal_pubkey, const unsigned char *tweak32) {
89+
secp256k1_ge pk;
90+
91+
VERIFY_CHECK(ctx != NULL);
92+
ARG_CHECK(output_pubkey != NULL);
93+
memset(output_pubkey, 0, sizeof(*output_pubkey));
94+
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
95+
ARG_CHECK(internal_pubkey != NULL);
96+
ARG_CHECK(tweak32 != NULL);
97+
98+
if (!secp256k1_xonly_pubkey_load(ctx, &pk, internal_pubkey)
99+
|| !secp256k1_ec_pubkey_tweak_add_helper(&ctx->ecmult_ctx, &pk, tweak32)) {
100+
return 0;
101+
}
102+
secp256k1_pubkey_save(output_pubkey, &pk);
103+
return 1;
104+
}
105+
106+
int secp256k1_xonly_pubkey_tweak_add_check(const secp256k1_context* ctx, const unsigned char *tweaked_pubkey32, int tweaked_pk_parity, const secp256k1_xonly_pubkey *internal_pubkey, const unsigned char *tweak32) {
107+
secp256k1_ge pk;
108+
unsigned char pk_expected32[32];
109+
110+
VERIFY_CHECK(ctx != NULL);
111+
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
112+
ARG_CHECK(internal_pubkey != NULL);
113+
ARG_CHECK(tweaked_pubkey32 != NULL);
114+
ARG_CHECK(tweak32 != NULL);
115+
116+
if (!secp256k1_xonly_pubkey_load(ctx, &pk, internal_pubkey)
117+
|| !secp256k1_ec_pubkey_tweak_add_helper(&ctx->ecmult_ctx, &pk, tweak32)) {
118+
return 0;
119+
}
120+
secp256k1_fe_normalize_var(&pk.x);
121+
secp256k1_fe_normalize_var(&pk.y);
122+
secp256k1_fe_get_b32(pk_expected32, &pk.x);
123+
124+
return memcmp(&pk_expected32, tweaked_pubkey32, 32) == 0
125+
&& secp256k1_fe_is_odd(&pk.y) == tweaked_pk_parity;
126+
}
127+
88128
#endif

Diff for: src/modules/extrakeys/tests_impl.h

+175
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,184 @@ void test_xonly_pubkey(void) {
137137
secp256k1_context_destroy(verify);
138138
}
139139

140+
void test_xonly_pubkey_tweak(void) {
141+
unsigned char zeros64[64] = { 0 };
142+
unsigned char overflows[32];
143+
unsigned char sk[32];
144+
secp256k1_pubkey internal_pk;
145+
secp256k1_xonly_pubkey internal_xonly_pk;
146+
secp256k1_pubkey output_pk;
147+
int pk_parity;
148+
unsigned char tweak[32];
149+
int i;
150+
151+
int ecount;
152+
secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount);
153+
secp256k1_context *sign = api_test_context(SECP256K1_CONTEXT_SIGN, &ecount);
154+
secp256k1_context *verify = api_test_context(SECP256K1_CONTEXT_VERIFY, &ecount);
155+
156+
memset(overflows, 0xff, sizeof(overflows));
157+
secp256k1_rand256(tweak);
158+
secp256k1_rand256(sk);
159+
CHECK(secp256k1_ec_pubkey_create(ctx, &internal_pk, sk) == 1);
160+
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &internal_xonly_pk, &pk_parity, &internal_pk) == 1);
161+
162+
ecount = 0;
163+
CHECK(secp256k1_xonly_pubkey_tweak_add(none, &output_pk, &internal_xonly_pk, tweak) == 0);
164+
CHECK(ecount == 1);
165+
CHECK(secp256k1_xonly_pubkey_tweak_add(sign, &output_pk, &internal_xonly_pk, tweak) == 0);
166+
CHECK(ecount == 2);
167+
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 1);
168+
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, NULL, &internal_xonly_pk, tweak) == 0);
169+
CHECK(ecount == 3);
170+
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, NULL, tweak) == 0);
171+
CHECK(ecount == 4);
172+
/* NULL internal_xonly_pk zeroes the output_pk */
173+
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
174+
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, NULL) == 0);
175+
CHECK(ecount == 5);
176+
/* NULL tweak zeroes the output_pk */
177+
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
178+
179+
/* Invalid tweak zeroes the output_pk */
180+
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, overflows) == 0);
181+
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
182+
183+
/* A zero tweak is fine */
184+
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, zeros64) == 1);
185+
186+
/* Fails if the resulting key was infinity */
187+
for (i = 0; i < count; i++) {
188+
secp256k1_scalar scalar_tweak;
189+
/* Because sk may be negated before adding, we need to try with tweak =
190+
* sk as well as tweak = -sk. */
191+
secp256k1_scalar_set_b32(&scalar_tweak, sk, NULL);
192+
secp256k1_scalar_negate(&scalar_tweak, &scalar_tweak);
193+
secp256k1_scalar_get_b32(tweak, &scalar_tweak);
194+
CHECK((secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, sk) == 0)
195+
|| (secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 0));
196+
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
197+
}
198+
199+
/* Invalid pk with a valid tweak */
200+
memset(&internal_xonly_pk, 0, sizeof(internal_xonly_pk));
201+
secp256k1_rand256(tweak);
202+
ecount = 0;
203+
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 0);
204+
CHECK(ecount == 1);
205+
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
206+
207+
secp256k1_context_destroy(none);
208+
secp256k1_context_destroy(sign);
209+
secp256k1_context_destroy(verify);
210+
}
211+
212+
void test_xonly_pubkey_tweak_check(void) {
213+
unsigned char zeros64[64] = { 0 };
214+
unsigned char overflows[32];
215+
unsigned char sk[32];
216+
secp256k1_pubkey internal_pk;
217+
secp256k1_xonly_pubkey internal_xonly_pk;
218+
secp256k1_pubkey output_pk;
219+
secp256k1_xonly_pubkey output_xonly_pk;
220+
unsigned char output_pk32[32];
221+
unsigned char buf32[32];
222+
int pk_parity;
223+
unsigned char tweak[32];
224+
225+
int ecount;
226+
secp256k1_context *none = api_test_context(SECP256K1_CONTEXT_NONE, &ecount);
227+
secp256k1_context *sign = api_test_context(SECP256K1_CONTEXT_SIGN, &ecount);
228+
secp256k1_context *verify = api_test_context(SECP256K1_CONTEXT_VERIFY, &ecount);
229+
230+
memset(overflows, 0xff, sizeof(overflows));
231+
secp256k1_rand256(tweak);
232+
secp256k1_rand256(sk);
233+
CHECK(secp256k1_ec_pubkey_create(ctx, &internal_pk, sk) == 1);
234+
CHECK(secp256k1_xonly_pubkey_from_pubkey(none, &internal_xonly_pk, &pk_parity, &internal_pk) == 1);
235+
236+
ecount = 0;
237+
CHECK(secp256k1_xonly_pubkey_tweak_add(verify, &output_pk, &internal_xonly_pk, tweak) == 1);
238+
CHECK(secp256k1_xonly_pubkey_from_pubkey(verify, &output_xonly_pk, &pk_parity, &output_pk) == 1);
239+
CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &output_xonly_pk) == 1);
240+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(none, buf32, pk_parity, &internal_xonly_pk, tweak) == 0);
241+
CHECK(ecount == 1);
242+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(sign, buf32, pk_parity, &internal_xonly_pk, tweak) == 0);
243+
CHECK(ecount == 2);
244+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, pk_parity, &internal_xonly_pk, tweak) == 1);
245+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, NULL, pk_parity, &internal_xonly_pk, tweak) == 0);
246+
CHECK(ecount == 3);
247+
/* invalid pk_parity value */
248+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, 2, &internal_xonly_pk, tweak) == 0);
249+
CHECK(ecount == 3);
250+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, pk_parity, NULL, tweak) == 0);
251+
CHECK(ecount == 4);
252+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(verify, buf32, pk_parity, &internal_xonly_pk, NULL) == 0);
253+
CHECK(ecount == 5);
254+
255+
memset(tweak, 1, sizeof(tweak));
256+
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &internal_xonly_pk, NULL, &internal_pk) == 1);
257+
CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &output_pk, &internal_xonly_pk, tweak) == 1);
258+
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &output_xonly_pk, &pk_parity, &output_pk) == 1);
259+
CHECK(secp256k1_xonly_pubkey_serialize(ctx, output_pk32, &output_xonly_pk) == 1);
260+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, pk_parity, &internal_xonly_pk, tweak) == 1);
261+
262+
/* Wrong pk_parity */
263+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, !pk_parity, &internal_xonly_pk, tweak) == 0);
264+
/* Wrong public key */
265+
CHECK(secp256k1_xonly_pubkey_serialize(ctx, buf32, &internal_xonly_pk) == 1);
266+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, buf32, pk_parity, &internal_xonly_pk, tweak) == 0);
267+
268+
/* Overflowing tweak not allowed */
269+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk32, pk_parity, &internal_xonly_pk, overflows) == 0);
270+
CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &output_pk, &internal_xonly_pk, overflows) == 0);
271+
CHECK(memcmp(&output_pk, zeros64, sizeof(output_pk)) == 0);
272+
CHECK(ecount == 5);
273+
274+
secp256k1_context_destroy(none);
275+
secp256k1_context_destroy(sign);
276+
secp256k1_context_destroy(verify);
277+
}
278+
279+
/* Starts with an initial pubkey and recursively creates N_PUBKEYS - 1
280+
* additional pubkeys by calling tweak_add. Then verifies every tweak starting
281+
* from the last pubkey. */
282+
#define N_PUBKEYS 32
283+
void test_xonly_pubkey_tweak_recursive(void) {
284+
unsigned char sk[32];
285+
secp256k1_pubkey pk[N_PUBKEYS];
286+
unsigned char pk_serialized[32];
287+
unsigned char tweak[N_PUBKEYS - 1][32];
288+
int i;
289+
290+
secp256k1_rand256(sk);
291+
CHECK(secp256k1_ec_pubkey_create(ctx, &pk[0], sk) == 1);
292+
/* Add tweaks */
293+
for (i = 0; i < N_PUBKEYS - 1; i++) {
294+
secp256k1_xonly_pubkey xonly_pk;
295+
memset(tweak[i], i + 1, sizeof(tweak[i]));
296+
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, NULL, &pk[i]) == 1);
297+
CHECK(secp256k1_xonly_pubkey_tweak_add(ctx, &pk[i + 1], &xonly_pk, tweak[i]) == 1);
298+
}
299+
300+
/* Verify tweaks */
301+
for (i = N_PUBKEYS - 1; i > 0; i--) {
302+
secp256k1_xonly_pubkey xonly_pk;
303+
int pk_parity;
304+
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, &pk_parity, &pk[i]) == 1);
305+
CHECK(secp256k1_xonly_pubkey_serialize(ctx, pk_serialized, &xonly_pk) == 1);
306+
CHECK(secp256k1_xonly_pubkey_from_pubkey(ctx, &xonly_pk, NULL, &pk[i - 1]) == 1);
307+
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, pk_serialized, pk_parity, &xonly_pk, tweak[i - 1]) == 1);
308+
}
309+
}
310+
#undef N_PUBKEYS
311+
140312
void run_extrakeys_tests(void) {
141313
/* xonly key test cases */
142314
test_xonly_pubkey();
315+
test_xonly_pubkey_tweak();
316+
test_xonly_pubkey_tweak_check();
317+
test_xonly_pubkey_tweak_recursive();
143318
}
144319

145320
#endif

0 commit comments

Comments
 (0)