Skip to content

Commit ae5fb7f

Browse files
committed
Add anti nonce sidechannel protocol for schnorrsigs
1 parent 16a7592 commit ae5fb7f

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

include/secp256k1_schnorrsig.h

+79
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,85 @@ SECP256K1_API int secp256k1_schnorrsig_parse(
5656
const unsigned char *in64
5757
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
5858

59+
/** Anti Nonce Sidechannel Protocol
60+
*
61+
* The next functions can be used to prevent a signing device from exfiltrating the secret signing
62+
* keys through biased signature nonces. The general idea is that a host provides additional
63+
* randomness to the signing device client and the client commits to the randomness in the nonce
64+
* using sign-to-contract.
65+
* In order to make the randomness unpredictable, the host and client must engage in a
66+
* commit-reveal protocol as follows:
67+
* 1. The host draws the randomness, commits to it with the `anti_nonce_sidechan_host_commit`
68+
* function and sends the commitment to the client.
69+
* 2. The client commits to its sign-to-contract original nonce (which is the nonce without the
70+
* sign-to-contract tweak) using the hosts commitment by calling the
71+
* `secp256k1_schnorrsig_anti_nonce_sidechan_client_commit` function. The client sends the
72+
* rusulting commitment to the host
73+
* 3. The host replies with the randomness generated in step 1.
74+
* 4. The client signs with `schnorrsig_sign` using the host provided randomness as `s2c_data` and
75+
* sends the signature and opening to the host.
76+
* 5. The host checks that the signature contains an sign-to-contract commitment to the randomness
77+
* by calling `secp256k1_schnorrsig_anti_nonce_sidechan_host_verify` with the client's
78+
* commitment from step 2 and the signature and opening received in step 4. If verification does
79+
* not succeed, the protocol failed and can be restarted.
80+
*/
81+
82+
/** Create a randomness commitment on the host as part of the Anti Nonce Sidechannel Protocol.
83+
*
84+
* Returns 1 on success, 0 on failure.
85+
* Args: ctx: pointer to a context object (cannot be NULL)
86+
* Out: rand_commitment32: pointer to 32-byte array to store the returned commitment (cannot be NULL)
87+
* In: rand32: the 32-byte randomness to commit to (cannot be NULL)
88+
*/
89+
SECP256K1_API int secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(
90+
secp256k1_context *ctx,
91+
unsigned char *rand_commitment32,
92+
const unsigned char *rand32
93+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
94+
95+
/** Compute commitment on the client as part of the Anti Nonce Sidechannel Protocol.
96+
*
97+
* Returns 1 on success, 0 on failure.
98+
* Args: ctx: pointer to a context object (cannot be NULL)
99+
* Out: client_commit: pointer to a pubkey where the clients public nonce will be
100+
* placed. This is the public nonce before doing the
101+
* sign-to-contract commitment to the hosts randomness (cannot
102+
* be NULL)
103+
* In: msg32: the 32-byte message hash to be signed (cannot be NULL)
104+
* seckey32: the 32-byte secret key used for signing (cannot be NULL)
105+
* rand_commitment32: the 32-byte randomness commitment from the host (cannot be NULL)
106+
*/
107+
SECP256K1_API int secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(
108+
secp256k1_context *ctx,
109+
secp256k1_pubkey *client_commit,
110+
const unsigned char *msg32,
111+
const unsigned char *seckey32,
112+
unsigned char *rand_commitment32
113+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
114+
115+
/** Verify that a clients signature contains the hosts randomness as part of the Anti
116+
* Nonce Sidechannel Protocol. Does not verify the signature itself.
117+
*
118+
* Returns 1 on success, 0 on failure.
119+
* Args: ctx: pointer to a context object (cannot be NULL)
120+
* In: sig: pointer to the signature whose randomness should be verified
121+
* (cannot be NULL)
122+
* rand32: pointer to the 32-byte randomness from the host which should
123+
* be included by the signature (cannot be NULL)
124+
* opening: pointer to the opening produced by the client when signing
125+
* with `rand32` as `s2c_data` (cannot be NULL)
126+
* client_commit: pointer to the client's commitment created in
127+
* `secp256k1_schnorrsig_anti_nonce_sidechan_client_commit`
128+
* (cannot be NULL)
129+
*/
130+
SECP256K1_API int secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(
131+
secp256k1_context *ctx,
132+
const secp256k1_schnorrsig *sig,
133+
const unsigned char *rand32,
134+
const secp256k1_s2c_opening *opening,
135+
const secp256k1_pubkey *client_commit
136+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
137+
59138
/** Create a Schnorr signature.
60139
*
61140
* Returns 1 on success, 0 on failure.

src/modules/schnorrsig/main_impl.h

+74
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,80 @@ int secp256k1_schnorrsig_verify_s2c_commit(const secp256k1_context* ctx, const s
5353
return secp256k1_ec_commit_verify(ctx, &pubnonce, &opening->original_pubnonce, data32, 32);
5454
}
5555

56+
int secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(secp256k1_context *ctx, unsigned char *rand_commitment32, const unsigned char *rand32) {
57+
secp256k1_sha256 sha;
58+
59+
VERIFY_CHECK(ctx != NULL);
60+
ARG_CHECK(rand_commitment32 != NULL);
61+
ARG_CHECK(rand32 != NULL);
62+
63+
secp256k1_sha256_initialize(&sha);
64+
secp256k1_sha256_write(&sha, rand32, 32);
65+
secp256k1_sha256_finalize(&sha, rand_commitment32);
66+
67+
return 1;
68+
}
69+
70+
int secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(secp256k1_context *ctx, secp256k1_pubkey *client_commit, const unsigned char *msg32, const unsigned char *seckey32, unsigned char *rand_commitment32) {
71+
unsigned char nonce32[32];
72+
secp256k1_scalar k;
73+
secp256k1_gej rj;
74+
secp256k1_ge r;
75+
76+
VERIFY_CHECK(ctx != NULL);
77+
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
78+
ARG_CHECK(client_commit != NULL);
79+
ARG_CHECK(msg32 != NULL);
80+
ARG_CHECK(seckey32 != NULL);
81+
ARG_CHECK(rand_commitment32 != NULL);
82+
83+
if (!secp256k1_nonce_function_bipschnorr(nonce32, msg32, seckey32, NULL, rand_commitment32, 0)) {
84+
return 0;
85+
}
86+
87+
secp256k1_scalar_set_b32(&k, nonce32, NULL);
88+
if (secp256k1_scalar_is_zero(&k)) {
89+
return 0;
90+
}
91+
92+
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k);
93+
secp256k1_ge_set_gej(&r, &rj);
94+
secp256k1_pubkey_save(client_commit, &r);
95+
return 1;
96+
}
97+
98+
SECP256K1_API int secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(secp256k1_context *ctx, const secp256k1_schnorrsig *sig, const unsigned char *rand32, const secp256k1_s2c_opening *opening, const secp256k1_pubkey *client_commit) {
99+
secp256k1_ge gcommit;
100+
secp256k1_ge gopening;
101+
secp256k1_gej pj;
102+
103+
VERIFY_CHECK(ctx != NULL);
104+
ARG_CHECK(sig != NULL);
105+
ARG_CHECK(rand32 != NULL);
106+
ARG_CHECK(opening != NULL);
107+
ARG_CHECK(client_commit != NULL);
108+
109+
/* Check that client_commit == opening->original_pubnonce */
110+
secp256k1_gej_set_infinity(&pj);
111+
if (!secp256k1_pubkey_load(ctx, &gcommit, client_commit)) {
112+
return 0;
113+
}
114+
secp256k1_ge_neg(&gcommit, &gcommit);
115+
secp256k1_gej_add_ge(&pj, &pj, &gcommit);
116+
if (!secp256k1_pubkey_load(ctx, &gopening, &opening->original_pubnonce)) {
117+
return 0;
118+
}
119+
secp256k1_gej_add_ge(&pj, &pj, &gopening);
120+
if (!secp256k1_gej_is_infinity(&pj)) {
121+
return 0;
122+
}
123+
124+
if (!secp256k1_schnorrsig_verify_s2c_commit(ctx, sig, rand32, opening)) {
125+
return 0;
126+
}
127+
return 1;
128+
}
129+
56130
int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, secp256k1_schnorrsig *sig, secp256k1_s2c_opening *s2c_opening, const unsigned char *msg32, const unsigned char *seckey, const unsigned char *s2c_data32, secp256k1_nonce_function noncefp, void *ndata) {
57131
secp256k1_scalar x;
58132
secp256k1_scalar e;

src/modules/schnorrsig/tests_impl.h

+86
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@ void test_schnorrsig_api(secp256k1_scratch_space *scratch) {
2727
unsigned char msg[32];
2828
unsigned char data32[32];
2929
unsigned char s2c_data32[32];
30+
unsigned char rand32[32];
31+
unsigned char rand_commitment32[32];
3032
unsigned char sig64[64];
3133
secp256k1_pubkey pk[3];
3234
secp256k1_schnorrsig sig;
3335
const secp256k1_schnorrsig *sigptr = &sig;
3436
const unsigned char *msgptr = msg;
3537
const secp256k1_pubkey *pkptr = &pk[0];
3638
secp256k1_s2c_opening s2c_opening;
39+
secp256k1_pubkey client_commit;
3740
unsigned char ones[32];
3841

3942
/** setup **/
@@ -124,6 +127,48 @@ void test_schnorrsig_api(secp256k1_scratch_space *scratch) {
124127
CHECK(ecount == 5);
125128
}
126129

130+
secp256k1_rand256(rand32);
131+
ecount = 0;
132+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(none, rand_commitment32, rand32) == 1);
133+
CHECK(ecount == 0);
134+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(none, NULL, rand32) == 0);
135+
CHECK(ecount == 1);
136+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(none, rand_commitment32, NULL) == 0);
137+
CHECK(ecount == 2);
138+
139+
ecount = 0;
140+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, &client_commit, msg, sk1, rand_commitment32) == 1);
141+
CHECK(ecount == 0);
142+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(none, &client_commit, msg, sk1, rand_commitment32) == 0);
143+
CHECK(ecount == 1);
144+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, NULL, msg, sk1, rand_commitment32) == 0);
145+
CHECK(ecount == 2);
146+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, &client_commit, NULL, sk1, rand_commitment32) == 0);
147+
CHECK(ecount == 3);
148+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, &client_commit, msg, NULL, rand_commitment32) == 0);
149+
CHECK(ecount == 4);
150+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, &client_commit, msg, sk1, NULL) == 0);
151+
CHECK(ecount == 5);
152+
153+
CHECK(secp256k1_schnorrsig_sign(sign, &sig, &s2c_opening, msg, sk1, rand32, NULL, NULL) == 1);
154+
155+
ecount = 0;
156+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(ctx, &sig, rand32, &s2c_opening, &client_commit) == 1);
157+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(none, &sig, rand32, &s2c_opening, &client_commit) == 0);
158+
CHECK(ecount == 1);
159+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(sign, &sig, rand32, &s2c_opening, &client_commit) == 0);
160+
CHECK(ecount == 2);
161+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(vrfy, &sig, rand32, &s2c_opening, &client_commit) == 1);
162+
CHECK(ecount == 2);
163+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(vrfy, NULL, rand32, &s2c_opening, &client_commit) == 0);
164+
CHECK(ecount == 3);
165+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(vrfy, &sig, NULL, &s2c_opening, &client_commit) == 0);
166+
CHECK(ecount == 4);
167+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(vrfy, &sig, rand32, NULL, &client_commit) == 0);
168+
CHECK(ecount == 5);
169+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(vrfy, &sig, rand32, &s2c_opening, NULL) == 0);
170+
CHECK(ecount == 6);
171+
127172
ecount = 0;
128173
CHECK(secp256k1_schnorrsig_verify(none, &sig, msg, &pk[0]) == 0);
129174
CHECK(ecount == 1);
@@ -807,6 +852,45 @@ void test_schnorrsig_s2c_commit_verify(void) {
807852
}
808853
}
809854

855+
void test_schnorrsig_anti_nonce_sidechannel(void) {
856+
unsigned char msg32[32];
857+
unsigned char key32[32];
858+
unsigned char rand32[32];
859+
unsigned char rand_commitment32[32];
860+
secp256k1_s2c_opening s2c_opening;
861+
secp256k1_pubkey client_commit;
862+
secp256k1_schnorrsig sig;
863+
864+
secp256k1_rand256(msg32);
865+
secp256k1_rand256(key32);
866+
secp256k1_rand256(rand32);
867+
868+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(ctx, rand_commitment32, rand32) == 1);
869+
870+
/* Host sends rand_commitment32 to client. */
871+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(ctx, &client_commit, msg32, key32, rand_commitment32) == 1);
872+
/* Client sends client_commit to host. Host replies with rand32. */
873+
CHECK(secp256k1_schnorrsig_sign(ctx, &sig, &s2c_opening, msg32, key32, rand32, NULL, NULL) == 1);
874+
/* Client sends signature to host. */
875+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(ctx, &sig, rand32, &s2c_opening, &client_commit) == 1);
876+
877+
{
878+
/* Signature without commitment to randomness fails verification */
879+
secp256k1_schnorrsig sig_tmp;
880+
CHECK(secp256k1_schnorrsig_sign(ctx, &sig_tmp, NULL, msg32, key32, NULL, NULL, NULL) == 1);
881+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(ctx, &sig_tmp, rand32, &s2c_opening, &client_commit) == 0);
882+
}
883+
{
884+
/* If sign-to-contract opening doesn't match commitment, verification fails */
885+
secp256k1_schnorrsig sig_tmp;
886+
secp256k1_s2c_opening s2c_opening_tmp;
887+
unsigned char rand32_tmp[32];
888+
secp256k1_rand256(rand32_tmp);
889+
CHECK(secp256k1_schnorrsig_sign(ctx, &sig_tmp, &s2c_opening_tmp, msg32, key32, rand32_tmp, NULL, NULL) == 1);
890+
CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_verify(ctx, &sig_tmp, rand32, &s2c_opening_tmp, &client_commit) == 0);
891+
}
892+
}
893+
810894
void run_schnorrsig_tests(void) {
811895
int i;
812896
secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(ctx, 1024 * 1024);
@@ -821,6 +905,8 @@ void run_schnorrsig_tests(void) {
821905
* a test. */
822906
test_schnorrsig_s2c_commit_verify();
823907
}
908+
test_schnorrsig_anti_nonce_sidechannel();
909+
824910
secp256k1_scratch_space_destroy(scratch);
825911
}
826912

0 commit comments

Comments
 (0)