Skip to content

Commit b448c7c

Browse files
jonasnickdeadalnix
authored andcommitted
schnorrsig: Add BIP-340 compatible signing and verification
Summary: This is a partial backport of secp256k1 [[bitcoin-core/secp256k1#558 | PR558]] : bitcoin-core/secp256k1@4e43520 Depends on D7647 Test Plan: ninja check-secp256k1 Reviewers: #bitcoin_abc, Fabien Reviewed By: #bitcoin_abc, Fabien Differential Revision: https://reviews.bitcoinabc.org/D7648
1 parent 50daa9e commit b448c7c

File tree

7 files changed

+862
-6
lines changed

7 files changed

+862
-6
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ bench_inv
22
bench_ecdh
33
bench_ecmult
44
bench_multiset
5+
bench_schnorrsig
56
bench_sign
67
bench_verify
7-
bench_schnorr_verify
88
bench_recover
99
bench_internal
1010
tests

.travis.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ env:
5050
- ECDH=no
5151
- RECOVERY=no
5252
- SCHNORR=yes
53+
- SCHNORRSIG=no
5354
- EXPERIMENTAL=no
5455
- JNI=no
5556
- OPENSSL_TESTS=auto
@@ -59,16 +60,16 @@ env:
5960
- ITERS=2
6061
jobs:
6162
- WIDEMUL=int64 RECOVERY=yes
62-
- WIDEMUL=int64 ECDH=yes EXPERIMENTAL=yes MULTISET=yes
63+
- WIDEMUL=int64 ECDH=yes EXPERIMENTAL=yes MULTISET=yes SCHNORRSIG=yes
6364
- WIDEMUL=int64 ENDOMORPHISM=yes
6465
- WIDEMUL=int128
65-
- WIDEMUL=int128 RECOVERY=yes
66+
- WIDEMUL=int128 RECOVERY=yes EXPERIMENTAL=yes SCHNORRSIG=yes
6667
- WIDEMUL=int128 ENDOMORPHISM=yes
67-
- WIDEMUL=int128 ENDOMORPHISM=yes ECDH=yes EXPERIMENTAL=yes MULTISET=yes
68+
- WIDEMUL=int128 ENDOMORPHISM=yes ECDH=yes EXPERIMENTAL=yes MULTISET=yes SCHNORRSIG=yes
6869
- WIDEMUL=int128 ASM=x86_64
6970
- WIDEMUL=int128 ENDOMORPHISM=yes ASM=x86_64
7071
- BIGNUM=no
71-
- BIGNUM=no ENDOMORPHISM=yes RECOVERY=yes EXPERIMENTAL=yes MULTISET=yes
72+
- BIGNUM=no ENDOMORPHISM=yes RECOVERY=yes EXPERIMENTAL=yes MULTISET=yes SCHNORRSIG=yes
7273
- BIGNUM=no STATICPRECOMPUTATION=no
7374
- AUTOTOOLS_TARGET=distcheck CMAKE_TARGET=install WITH_VALGRIND=no CTIMETEST=no BENCH=no
7475
- AUTOTOOLS_EXTRA_FLAGS=CPPFLAGS=-DDETERMINISTIC CMAKE_EXTRA_FLAGS=-DCMAKE_C_FLAGS=-DDETERMINISTIC

include/secp256k1_schnorrsig.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,52 @@ typedef int (*secp256k1_nonce_function_hardened)(
5858
*/
5959
SECP256K1_API extern const secp256k1_nonce_function_hardened secp256k1_nonce_function_bip340;
6060

61+
/** Create a Schnorr signature.
62+
*
63+
* Does _not_ strictly follow BIP-340 because it does not verify the resulting
64+
* signature. Instead, you can manually use secp256k1_schnorrsig_verify and
65+
* abort if it fails.
66+
*
67+
* Otherwise BIP-340 compliant if the noncefp argument is NULL or
68+
* secp256k1_nonce_function_bip340 and the ndata argument is 32-byte auxiliary
69+
* randomness.
70+
*
71+
* Returns 1 on success, 0 on failure.
72+
* Args: ctx: pointer to a context object, initialized for signing (cannot be NULL)
73+
* Out: sig64: pointer to a 64-byte array to store the serialized signature (cannot be NULL)
74+
* In: msg32: the 32-byte message being signed (cannot be NULL)
75+
* keypair: pointer to an initialized keypair (cannot be NULL)
76+
* noncefp: pointer to a nonce generation function. If NULL, secp256k1_nonce_function_bip340 is used
77+
* ndata: pointer to arbitrary data used by the nonce generation
78+
* function (can be NULL). If it is non-NULL and
79+
* secp256k1_nonce_function_bip340 is used, then ndata must be a
80+
* pointer to 32-byte auxiliary randomness as per BIP-340.
81+
*/
82+
SECP256K1_API int secp256k1_schnorrsig_sign(
83+
const secp256k1_context* ctx,
84+
unsigned char *sig64,
85+
const unsigned char *msg32,
86+
const secp256k1_keypair *keypair,
87+
secp256k1_nonce_function_hardened noncefp,
88+
void *ndata
89+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
90+
91+
/** Verify a Schnorr signature.
92+
*
93+
* Returns: 1: correct signature
94+
* 0: incorrect signature
95+
* Args: ctx: a secp256k1 context object, initialized for verification.
96+
* In: sig64: pointer to the 64-byte signature to verify (cannot be NULL)
97+
* msg32: the 32-byte message being verified (cannot be NULL)
98+
* pubkey: pointer to an x-only public key to verify with (cannot be NULL)
99+
*/
100+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_verify(
101+
const secp256k1_context* ctx,
102+
const unsigned char *sig64,
103+
const unsigned char *msg32,
104+
const secp256k1_xonly_pubkey *pubkey
105+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
106+
61107
#ifdef __cplusplus
62108
}
63109
#endif

src/modules/schnorrsig/main_impl.h

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,146 @@ static int nonce_function_bip340(unsigned char *nonce32, const unsigned char *ms
9393

9494
const secp256k1_nonce_function_hardened secp256k1_nonce_function_bip340 = nonce_function_bip340;
9595

96+
/* Initializes SHA256 with fixed midstate. This midstate was computed by applying
97+
* SHA256 to SHA256("BIP0340/challenge")||SHA256("BIP0340/challenge"). */
98+
static void secp256k1_schnorrsig_sha256_tagged(secp256k1_sha256 *sha) {
99+
secp256k1_sha256_initialize(sha);
100+
sha->s[0] = 0x9cecba11ul;
101+
sha->s[1] = 0x23925381ul;
102+
sha->s[2] = 0x11679112ul;
103+
sha->s[3] = 0xd1627e0ful;
104+
sha->s[4] = 0x97c87550ul;
105+
sha->s[5] = 0x003cc765ul;
106+
sha->s[6] = 0x90f61164ul;
107+
sha->s[7] = 0x33e9b66aul;
108+
sha->bytes = 64;
109+
}
110+
111+
int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *msg32, const secp256k1_keypair *keypair, secp256k1_nonce_function_hardened noncefp, void *ndata) {
112+
secp256k1_scalar sk;
113+
secp256k1_scalar e;
114+
secp256k1_scalar k;
115+
secp256k1_gej rj;
116+
secp256k1_ge pk;
117+
secp256k1_ge r;
118+
secp256k1_sha256 sha;
119+
unsigned char buf[32] = { 0 };
120+
unsigned char pk_buf[32];
121+
unsigned char seckey[32];
122+
int ret = 1;
123+
124+
VERIFY_CHECK(ctx != NULL);
125+
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
126+
ARG_CHECK(sig64 != NULL);
127+
ARG_CHECK(msg32 != NULL);
128+
ARG_CHECK(keypair != NULL);
129+
130+
if (noncefp == NULL) {
131+
noncefp = secp256k1_nonce_function_bip340;
132+
}
133+
134+
ret &= secp256k1_keypair_load(ctx, &sk, &pk, keypair);
135+
/* Because we are signing for a x-only pubkey, the secret key is negated
136+
* before signing if the point corresponding to the secret key does not
137+
* have an even Y. */
138+
if (secp256k1_fe_is_odd(&pk.y)) {
139+
secp256k1_scalar_negate(&sk, &sk);
140+
}
141+
142+
secp256k1_scalar_get_b32(seckey, &sk);
143+
secp256k1_fe_get_b32(pk_buf, &pk.x);
144+
ret &= !!noncefp(buf, msg32, seckey, pk_buf, bip340_algo16, ndata);
145+
secp256k1_scalar_set_b32(&k, buf, NULL);
146+
ret &= !secp256k1_scalar_is_zero(&k);
147+
secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret);
148+
149+
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k);
150+
secp256k1_ge_set_gej(&r, &rj);
151+
152+
/* We declassify r to allow using it as a branch point. This is fine
153+
* because r is not a secret. */
154+
secp256k1_declassify(ctx, &r, sizeof(r));
155+
secp256k1_fe_normalize_var(&r.y);
156+
if (secp256k1_fe_is_odd(&r.y)) {
157+
secp256k1_scalar_negate(&k, &k);
158+
}
159+
secp256k1_fe_normalize_var(&r.x);
160+
secp256k1_fe_get_b32(&sig64[0], &r.x);
161+
162+
/* tagged hash(r.x, pk.x, msg32) */
163+
secp256k1_schnorrsig_sha256_tagged(&sha);
164+
secp256k1_sha256_write(&sha, &sig64[0], 32);
165+
secp256k1_sha256_write(&sha, pk_buf, sizeof(pk_buf));
166+
secp256k1_sha256_write(&sha, msg32, 32);
167+
secp256k1_sha256_finalize(&sha, buf);
168+
169+
/* Set scalar e to the challenge hash modulo the curve order as per
170+
* BIP340. */
171+
secp256k1_scalar_set_b32(&e, buf, NULL);
172+
secp256k1_scalar_mul(&e, &e, &sk);
173+
secp256k1_scalar_add(&e, &e, &k);
174+
secp256k1_scalar_get_b32(&sig64[32], &e);
175+
176+
memczero(sig64, 64, !ret);
177+
secp256k1_scalar_clear(&k);
178+
secp256k1_scalar_clear(&sk);
179+
memset(seckey, 0, sizeof(seckey));
180+
181+
return ret;
182+
}
183+
184+
int secp256k1_schnorrsig_verify(const secp256k1_context* ctx, const unsigned char *sig64, const unsigned char *msg32, const secp256k1_xonly_pubkey *pubkey) {
185+
secp256k1_scalar s;
186+
secp256k1_scalar e;
187+
secp256k1_gej rj;
188+
secp256k1_ge pk;
189+
secp256k1_gej pkj;
190+
secp256k1_fe rx;
191+
secp256k1_ge r;
192+
secp256k1_sha256 sha;
193+
unsigned char buf[32];
194+
int overflow;
195+
196+
VERIFY_CHECK(ctx != NULL);
197+
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
198+
ARG_CHECK(sig64 != NULL);
199+
ARG_CHECK(msg32 != NULL);
200+
ARG_CHECK(pubkey != NULL);
201+
202+
if (!secp256k1_fe_set_b32(&rx, &sig64[0])) {
203+
return 0;
204+
}
205+
206+
secp256k1_scalar_set_b32(&s, &sig64[32], &overflow);
207+
if (overflow) {
208+
return 0;
209+
}
210+
211+
if (!secp256k1_xonly_pubkey_load(ctx, &pk, pubkey)) {
212+
return 0;
213+
}
214+
215+
secp256k1_schnorrsig_sha256_tagged(&sha);
216+
secp256k1_sha256_write(&sha, &sig64[0], 32);
217+
secp256k1_fe_get_b32(buf, &pk.x);
218+
secp256k1_sha256_write(&sha, buf, sizeof(buf));
219+
secp256k1_sha256_write(&sha, msg32, 32);
220+
secp256k1_sha256_finalize(&sha, buf);
221+
secp256k1_scalar_set_b32(&e, buf, NULL);
222+
223+
/* Compute rj = s*G + (-e)*pkj */
224+
secp256k1_scalar_negate(&e, &e);
225+
secp256k1_gej_set_ge(&pkj, &pk);
226+
secp256k1_ecmult(&ctx->ecmult_ctx, &rj, &pkj, &e, &s);
227+
228+
secp256k1_ge_set_gej_var(&r, &rj);
229+
if (secp256k1_ge_is_infinity(&r)) {
230+
return 0;
231+
}
232+
233+
secp256k1_fe_normalize_var(&r.y);
234+
return !secp256k1_fe_is_odd(&r.y) &&
235+
secp256k1_fe_equal_var(&rx, &r.x);
236+
}
237+
96238
#endif

0 commit comments

Comments
 (0)