diff --git a/include/secp256k1_schnorrsig.h b/include/secp256k1_schnorrsig.h index 27d5d635f7..c3746e0437 100644 --- a/include/secp256k1_schnorrsig.h +++ b/include/secp256k1_schnorrsig.h @@ -56,6 +56,77 @@ SECP256K1_API int secp256k1_schnorrsig_parse( const unsigned char *in64 ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); +/** Anti Nonce Sidechannel Protocol + * + * The next functions can be used to prevent a signing device from exfiltrating the secret signing + * keys through biased signature nonces. The general idea is that a host provides additional + * randomness to the signing device client and the client commits to the randomness in the nonce + * using sign-to-contract. + * In order to make the randomness unpredictable, the host and client must engage in a + * commit-reveal protocol as follows: + * 1. The host draws the randomness, commits to it with the anti_nonce_sidechan_host_commit + * function and sends the commitment to the client. + * 2. The client commits to its sign-to-contract original nonce (which is the nonce without the + * sign-to-contract tweak) using the hosts commitment by calling the + * secp256k1_schnorrsig_anti_nonce_sidechan_client_commit function. The client gets the original + * nonce of the sign-to-contract commitment using secp256k1_s2c_commit_get_original_nonce and + * sends it to the host. + * 3. The host replies with the randomness generated in step 1. + * 4. The client uses anti_nonce_sidechan_client_setrand to check that the hosts commitment opens + * to the provided randomness. If not, it waits until the host sends the correct randomness or + * the protocol restarts. If the randomness matches the commitment, the client signs with the + * nonce_function_bipschnorr using the s2c context as nonce data and sends the signature and + * negated nonce flag to the host. + * 5. The host checks that the signature contains an sign-to-contract commitment to the randomness + * by calling verify_s2c_commit with the original nonce received in step 2 and the signature and + * negated nonce flag received in step 4. If verification does not succeed, it waits until the + * client sends a signature with a correct commitment or the protocol is restarted. + */ + +/** Create a randomness commitment on the host as part of the Anti Nonce Sidechannel Protocol. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: pointer to a context object (cannot be NULL) + * Out: rand_commitment32: pointer to 32-byte array to store the returned commitment (cannot be NULL) + * In: rand32: the 32-byte randomness to commit to (cannot be NULL) + */ +SECP256K1_API int secp256k1_schnorrsig_anti_nonce_sidechan_host_commit( + secp256k1_context *ctx, + unsigned char *rand_commitment32, + const unsigned char *rand32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Compute commitment on the client as part of the Anti Nonce Sidechannel Protocol. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: pointer to a context object (cannot be NULL) + * Out: s2c_ctx: pointer to an s2c context where the opening will be placed (cannot be NULL) + * In: msg32: the 32-byte message hash to be signed (cannot be NULL) + * seckey32: the 32-byte secret key used for signing (cannot be NULL) + * rand_commitment32: the 32-byte randomness commitment from the host (cannot be NULL) + */ +SECP256K1_API int secp256k1_schnorrsig_anti_nonce_sidechan_client_commit( + secp256k1_context *ctx, + secp256k1_s2c_commit_context *s2c_ctx, + const unsigned char *msg32, + const unsigned char *seckey32, + const unsigned char *rand_commitment32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Set host randomness on the client as part of the Anti Nonce Sidechannel Protocol. + * + * Returns: 1: given randomness matches randomness commitment stored in s2c_ctx + * 0: failure + * Args: ctx: pointer to a context object (cannot be NULL) + * Out: s2c_ctx: pointer to an s2c context where the randomness will be stored (cannot be NULL) + * In: rand32: 32-byte randomness matching the previously received commitment (cannot be NULL) + */ +SECP256K1_API int secp256k1_schnorrsig_anti_nonce_sidechan_client_setrand( + secp256k1_context *ctx, + secp256k1_s2c_commit_context *s2c_ctx, + const unsigned char *rand32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + /** Create a Schnorr signature. * * Returns 1 on success, 0 on failure. diff --git a/src/modules/schnorrsig/main_impl.h b/src/modules/schnorrsig/main_impl.h index 853292eb7f..45c9d72f7b 100644 --- a/src/modules/schnorrsig/main_impl.h +++ b/src/modules/schnorrsig/main_impl.h @@ -52,6 +52,50 @@ int secp256k1_schnorrsig_verify_s2c_commit(const secp256k1_context* ctx, const s return secp256k1_ec_commit_verify(ctx, &pubnonce, original_nonce, data32, 32); } +int secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(secp256k1_context *ctx, unsigned char *rand_commitment32, const unsigned char *rand32) { + secp256k1_sha256 sha; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(rand_commitment32 != NULL); + ARG_CHECK(rand32 != NULL); + + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, rand32, 32); + secp256k1_sha256_finalize(&sha, rand_commitment32); + + return 1; +} + +int secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(secp256k1_context *ctx, secp256k1_s2c_commit_context *s2c_ctx, const unsigned char *msg32, const unsigned char *seckey32, const unsigned char *rand_commitment32) { + unsigned char nonce32[32]; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(s2c_ctx != NULL); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(seckey32 != NULL); + ARG_CHECK(rand_commitment32 != NULL); + + memcpy(s2c_ctx->data_hash, rand_commitment32, 32); + return secp256k1_nonce_function_bipschnorr_no_s2c_tweak(ctx, nonce32, msg32, seckey32, NULL, s2c_ctx, 0); +} + +int secp256k1_schnorrsig_anti_nonce_sidechan_client_setrand(secp256k1_context *ctx, secp256k1_s2c_commit_context *s2c_ctx, const unsigned char *rand32) { + secp256k1_sha256 sha; + unsigned char rand_hash[32]; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(s2c_ctx != NULL); + ARG_CHECK(rand32 != NULL); + + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, rand32, 32); + secp256k1_sha256_finalize(&sha, rand_hash); + if (memcmp(rand_hash, s2c_ctx->data_hash, 32) != 0) { + return 0; + } + memcpy(s2c_ctx->data, rand32, 32); + return 1; +} + int secp256k1_schnorrsig_sign(const secp256k1_context* ctx, secp256k1_schnorrsig *sig, int *nonce_is_negated, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, void *ndata) { secp256k1_scalar x; secp256k1_scalar e; diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index c9301edd80..d22a79604c 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -26,6 +26,8 @@ void test_schnorrsig_api(secp256k1_scratch_space *scratch) { unsigned char sk3[32]; unsigned char msg[32]; unsigned char data32[32]; + unsigned char rand32[32]; + unsigned char rand_commitment32[32]; unsigned char sig64[64]; secp256k1_pubkey pk[3]; secp256k1_schnorrsig sig; @@ -116,6 +118,39 @@ void test_schnorrsig_api(secp256k1_scratch_space *scratch) { CHECK(secp256k1_schnorrsig_verify_s2c_commit(vrfy, &sig, data32, NULL, nonce_is_negated) == 0); CHECK(ecount == 4); + secp256k1_rand256(rand32); + ecount = 0; + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(none, rand_commitment32, rand32) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(none, NULL, rand32) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(none, rand_commitment32, NULL) == 0); + CHECK(ecount == 2); + + ecount = 0; + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, &s2c_ctx, msg, sk1, rand_commitment32) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(none, &s2c_ctx, msg, sk1, rand_commitment32) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, NULL, msg, sk1, rand_commitment32) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, &s2c_ctx, NULL, sk1, rand_commitment32) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, &s2c_ctx, msg, NULL, rand_commitment32) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(sign, &s2c_ctx, msg, sk1, NULL) == 0); + CHECK(ecount == 5); + + ecount = 0; + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_setrand(none, &s2c_ctx, rand32) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_setrand(none, NULL, rand32) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_setrand(none, &s2c_ctx, NULL) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_schnorrsig_sign(sign, &sig, &nonce_is_negated, msg, sk1, NULL, &s2c_ctx) == 1); + CHECK(secp256k1_schnorrsig_verify_s2c_commit(vrfy, &sig, rand32, &s2c_ctx.original_pubnonce, nonce_is_negated) == 1); + ecount = 0; CHECK(secp256k1_schnorrsig_verify(none, &sig, msg, &pk[0]) == 0); CHECK(ecount == 1); @@ -780,6 +815,38 @@ void test_schnorrsig_s2c_commit_verify(void) { } } +void test_schnorrsig_anti_nonce_sidechannel(void) { + unsigned char msg32[32]; + unsigned char key32[32]; + unsigned char algo16[16]; + unsigned char rand32[32]; + unsigned char rand_commitment32[32]; + secp256k1_s2c_commit_context s2c_ctx; + secp256k1_pubkey s2c_original_nonce; + secp256k1_schnorrsig sig; + int nonce_is_negated; + + secp256k1_rand256(msg32); + secp256k1_rand256(key32); + secp256k1_rand256(rand32); + memset(algo16, 23, sizeof(algo16)); + + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_host_commit(ctx, rand_commitment32, rand32) == 1); + + /* Host sends rand_commitment32 to client. */ + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_commit(ctx, &s2c_ctx, msg32, key32, rand_commitment32) == 1); + + /* Client sends s2c original nonce. Host replies with rand32. */ + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_setrand(ctx, &s2c_ctx, rand32) == 1); + /* Providing wrong data results in an error. */ + CHECK(secp256k1_schnorrsig_anti_nonce_sidechan_client_setrand(ctx, &s2c_ctx, rand_commitment32) == 0); + CHECK(secp256k1_s2c_commit_get_original_nonce(ctx, &s2c_original_nonce, &s2c_ctx) == 1); + CHECK(secp256k1_schnorrsig_sign(ctx, &sig, &nonce_is_negated, msg32, key32, NULL, &s2c_ctx) == 1); + + /* Client sends signature to host. */ + CHECK(secp256k1_schnorrsig_verify_s2c_commit(ctx, &sig, rand32, &s2c_original_nonce, nonce_is_negated) == 1); +} + void run_schnorrsig_tests(void) { int i; secp256k1_scratch_space *scratch = secp256k1_scratch_space_create(ctx, 1024 * 1024); @@ -794,6 +861,8 @@ void run_schnorrsig_tests(void) { * a test. */ test_schnorrsig_s2c_commit_verify(); } + test_schnorrsig_anti_nonce_sidechannel(); + secp256k1_scratch_space_destroy(scratch); }