From 826bd04b43f823813c633449223595031d5c31f7 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sat, 5 Dec 2020 22:40:54 +0000 Subject: [PATCH 1/5] add eccommit functionality Co-authored-by: Marko Bencun Co-authored-by: Jonas Nick --- Makefile.am | 2 + src/eccommit.h | 28 +++++++++++++ src/eccommit_impl.h | 73 ++++++++++++++++++++++++++++++++++ src/modules/musig/example.c | 2 +- src/secp256k1.c | 1 + src/tests.c | 78 +++++++++++++++++++++++++++++++++++++ 6 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/eccommit.h create mode 100644 src/eccommit_impl.h diff --git a/Makefile.am b/Makefile.am index 1cba7a34..7309fa4a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,6 +16,8 @@ noinst_HEADERS += src/group.h noinst_HEADERS += src/group_impl.h noinst_HEADERS += src/num_gmp.h noinst_HEADERS += src/num_gmp_impl.h +noinst_HEADERS += src/eccommit.h +noinst_HEADERS += src/eccommit_impl.h noinst_HEADERS += src/ecdsa.h noinst_HEADERS += src/ecdsa_impl.h noinst_HEADERS += src/eckey.h diff --git a/src/eccommit.h b/src/eccommit.h new file mode 100644 index 00000000..6bb11039 --- /dev/null +++ b/src/eccommit.h @@ -0,0 +1,28 @@ +/********************************************************************** + * Copyright (c) 2020 The libsecp256k1-zkp Developers * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef SECP256K1_ECCOMMIT_H +#define SECP256K1_ECCOMMIT_H + +/** Helper function to add a 32-byte value to a scalar */ +static int secp256k1_ec_seckey_tweak_add_helper(secp256k1_scalar *sec, const unsigned char *tweak); +/** Helper function to add a 32-byte value, times G, to an EC point */ +static int secp256k1_ec_pubkey_tweak_add_helper(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge *p, const unsigned char *tweak); + +/** Serializes elem as a 33 byte array. This is non-constant time with respect to + * whether pubp is the point at infinity. Thus, you may need to declassify + * pubp->infinity before calling this function. */ +static int secp256k1_ec_commit_pubkey_serialize_const(secp256k1_ge *pubp, unsigned char *buf33); +/** Compute an ec commitment tweak as hash(pubkey, data). */ +static int secp256k1_ec_commit_tweak(unsigned char *tweak32, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size); +/** Compute an ec commitment as pubkey + hash(pubkey, data)*G. */ +static int secp256k1_ec_commit(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size); +/** Compute a secret key commitment as seckey + hash(pubkey, data). */ +static int secp256k1_ec_commit_seckey(const secp256k1_ecmult_gen_context* ecmult_gen_ctx, secp256k1_scalar* seckey, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size); +/** Verify an ec commitment as pubkey + hash(pubkey, data)*G ?= commitment. */ +static int secp256k1_ec_commit_verify(const secp256k1_ecmult_context* ecmult_ctx, const secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size); + +#endif /* SECP256K1_ECCOMMIT_H */ diff --git a/src/eccommit_impl.h b/src/eccommit_impl.h new file mode 100644 index 00000000..641c07d2 --- /dev/null +++ b/src/eccommit_impl.h @@ -0,0 +1,73 @@ +/********************************************************************** + * Copyright (c) 2020 The libsecp256k1 Developers * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#include + +#include "eckey.h" +#include "hash.h" + +/* from secp256k1.c */ +static int secp256k1_ec_seckey_tweak_add_helper(secp256k1_scalar *sec, const unsigned char *tweak); +static int secp256k1_ec_pubkey_tweak_add_helper(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge *pubp, const unsigned char *tweak); + +static int secp256k1_ec_commit_pubkey_serialize_const(secp256k1_ge *pubp, unsigned char *buf33) { + if (secp256k1_ge_is_infinity(pubp)) { + return 0; + } + secp256k1_fe_normalize(&pubp->x); + secp256k1_fe_normalize(&pubp->y); + secp256k1_fe_get_b32(&buf33[1], &pubp->x); + buf33[0] = secp256k1_fe_is_odd(&pubp->y) ? SECP256K1_TAG_PUBKEY_ODD : SECP256K1_TAG_PUBKEY_EVEN; + return 1; +} + +/* Compute an ec commitment tweak as hash(pubp, data). */ +static int secp256k1_ec_commit_tweak(unsigned char *tweak32, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) +{ + unsigned char rbuf[33]; + + if (!secp256k1_ec_commit_pubkey_serialize_const(pubp, rbuf)) { + return 0; + } + secp256k1_sha256_write(sha, rbuf, sizeof(rbuf)); + secp256k1_sha256_write(sha, data, data_size); + secp256k1_sha256_finalize(sha, tweak32); + return 1; +} + +/* Compute an ec commitment as pubp + hash(pubp, data)*G. */ +static int secp256k1_ec_commit(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) { + unsigned char tweak[32]; + + *commitp = *pubp; + return secp256k1_ec_commit_tweak(tweak, commitp, sha, data, data_size) + && secp256k1_ec_pubkey_tweak_add_helper(ecmult_ctx, commitp, tweak); +} + +/* Compute the seckey of an ec commitment from the original secret key of the pubkey as seckey + + * hash(pubp, data). */ +static int secp256k1_ec_commit_seckey(secp256k1_scalar* seckey, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) { + unsigned char tweak[32]; + return secp256k1_ec_commit_tweak(tweak, pubp, sha, data, data_size) + && secp256k1_ec_seckey_tweak_add_helper(seckey, tweak); +} + +/* Verify an ec commitment as pubp + hash(pubp, data)*G ?= commitment. */ +static int secp256k1_ec_commit_verify(const secp256k1_ecmult_context* ecmult_ctx, const secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) { + secp256k1_gej pj; + secp256k1_ge p; + + if (!secp256k1_ec_commit(ecmult_ctx, &p, pubp, sha, data, data_size)) { + return 0; + } + + /* Return p == commitp */ + secp256k1_ge_neg(&p, &p); + secp256k1_gej_set_ge(&pj, &p); + secp256k1_gej_add_ge_var(&pj, &pj, commitp, NULL); + return secp256k1_gej_is_infinity(&pj); +} + diff --git a/src/modules/musig/example.c b/src/modules/musig/example.c index 2c5b7006..fa3f5833 100644 --- a/src/modules/musig/example.c +++ b/src/modules/musig/example.c @@ -107,7 +107,7 @@ int sign(const secp256k1_context* ctx, unsigned char seckeys[][32], const secp25 for (i = 0; i < N_SIGNERS; i++) { for (j = 0; j < N_SIGNERS; j++) { /* To check whether signing was successful, it suffices to either verify - * the the combined signature with the combined public key using + * the combined signature with the combined public key using * secp256k1_schnorrsig_verify, or verify all partial signatures of all * signers individually. Verifying the combined signature is cheaper but * verifying the individual partial signatures has the advantage that it diff --git a/src/secp256k1.c b/src/secp256k1.c index 42309a03..0c7a2575 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -13,6 +13,7 @@ #include "field_impl.h" #include "scalar_impl.h" #include "group_impl.h" +#include "eccommit_impl.h" #include "ecmult_impl.h" #include "ecmult_const_impl.h" #include "ecmult_gen_impl.h" diff --git a/src/tests.c b/src/tests.c index 33aef6ed..bd6f7fd1 100644 --- a/src/tests.c +++ b/src/tests.c @@ -2609,6 +2609,83 @@ void run_ec_combine(void) { } } +void test_ec_commit(void) { + secp256k1_scalar seckey_s; + secp256k1_ge pubkey; + secp256k1_gej pubkeyj; + secp256k1_ge commitment; + unsigned char data[32]; + secp256k1_sha256 sha; + + /* Create random keypair and data */ + random_scalar_order_test(&seckey_s); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pubkeyj, &seckey_s); + secp256k1_ge_set_gej(&pubkey, &pubkeyj); + secp256k1_testrand256_test(data); + + /* Commit to data and verify */ + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit(&ctx->ecmult_ctx, &commitment, &pubkey, &sha, data, 32) == 1); + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit_verify(&ctx->ecmult_ctx, &commitment, &pubkey, &sha, data, 32) == 1); + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit_seckey(&seckey_s, &pubkey, &sha, data, 32) == 1); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pubkeyj, &seckey_s); + ge_equals_gej(&commitment, &pubkeyj); + + /* Check that verification fails with different data */ + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit_verify(&ctx->ecmult_ctx, &commitment, &pubkey, &sha, data, 31) == 0); + + /* Check that commmitting fails when the inner pubkey is the point at + * infinity */ + secp256k1_sha256_initialize(&sha); + secp256k1_ge_set_infinity(&pubkey); + CHECK(secp256k1_ec_commit(&ctx->ecmult_ctx, &commitment, &pubkey, &sha, data, 32) == 0); + secp256k1_scalar_set_int(&seckey_s, 0); + CHECK(secp256k1_ec_commit_seckey(&seckey_s, &pubkey, &sha, data, 32) == 0); + CHECK(secp256k1_ec_commit_verify(&ctx->ecmult_ctx, &commitment, &pubkey, &sha, data, 32) == 0); +} + +void test_ec_commit_api(void) { + unsigned char seckey[32]; + secp256k1_scalar seckey_s; + secp256k1_ge pubkey; + secp256k1_gej pubkeyj; + secp256k1_ge commitment; + unsigned char data[32]; + secp256k1_sha256 sha; + + memset(data, 23, sizeof(data)); + + /* Create random keypair */ + random_scalar_order_test(&seckey_s); + secp256k1_scalar_get_b32(seckey, &seckey_s); + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pubkeyj, &seckey_s); + secp256k1_ge_set_gej(&pubkey, &pubkeyj); + + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit(&ctx->ecmult_ctx, &commitment, &pubkey, &sha, data, 1) == 1); + /* The same pubkey can be both input and output of the function */ + { + secp256k1_ge pubkey_tmp = pubkey; + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit(&ctx->ecmult_ctx, &pubkey_tmp, &pubkey_tmp, &sha, data, 1) == 1); + ge_equals_ge(&commitment, &pubkey_tmp); + } + + secp256k1_sha256_initialize(&sha); + CHECK(secp256k1_ec_commit_verify(&ctx->ecmult_ctx, &commitment, &pubkey, &sha, data, 1) == 1); +} + +void run_ec_commit(void) { + int i; + for (i = 0; i < count * 8; i++) { + test_ec_commit(); + } + test_ec_commit_api(); +} + void test_group_decompress(const secp256k1_fe* x) { /* The input itself, normalized. */ secp256k1_fe fex = *x; @@ -5858,6 +5935,7 @@ int main(int argc, char **argv) { run_ecmult_const_tests(); run_ecmult_multi_tests(); run_ec_combine(); + run_ec_commit(); /* endomorphism tests */ run_endomorphism_tests(); From 8e46cac5b31c3a3127d33d46466c29e97545cf16 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sat, 5 Dec 2020 23:18:54 +0000 Subject: [PATCH 2/5] ecdsa-s2c: block in module Co-authored-by: Marko Bencun Co-authored-by: Jonas Nick --- .travis.yml | 9 +-- Makefile.am | 5 ++ configure.ac | 15 +++++ contrib/travis.sh | 1 + include/secp256k1_ecdsa_s2c.h | 58 +++++++++++++++++ src/modules/ecdsa_s2c/Makefile.am.include | 3 + src/modules/ecdsa_s2c/main_impl.h | 28 +++++++++ src/modules/ecdsa_s2c/tests_impl.h | 76 +++++++++++++++++++++++ src/secp256k1.c | 4 ++ src/tests.c | 9 +++ 10 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 include/secp256k1_ecdsa_s2c.h create mode 100644 src/modules/ecdsa_s2c/Makefile.am.include create mode 100755 src/modules/ecdsa_s2c/main_impl.h create mode 100644 src/modules/ecdsa_s2c/tests_impl.h diff --git a/.travis.yml b/.travis.yml index 9b7fe6f3..6826614e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,18 +17,19 @@ compiler: - gcc env: global: - - WIDEMUL=auto BIGNUM=auto STATICPRECOMPUTATION=yes ECMULTGENPRECISION=auto ASM=no BUILD=check WITH_VALGRIND=yes RUN_VALGRIND=no EXTRAFLAGS= HOST= ECDH=no RECOVERY=no SCHNORRSIG=no EXPERIMENTAL=no CTIMETEST=yes BENCH=yes ITERS=2 GENERATOR=no RANGEPROOF=no WHITELIST=no SCHNORRSIG=no MUSIG=no + - WIDEMUL=auto BIGNUM=auto STATICPRECOMPUTATION=yes ECMULTGENPRECISION=auto ASM=no BUILD=check WITH_VALGRIND=yes RUN_VALGRIND=no EXTRAFLAGS= HOST= ECDH=no RECOVERY=no ECDSA_S2C=no SCHNORRSIG=no EXPERIMENTAL=no CTIMETEST=yes BENCH=yes ITERS=2 GENERATOR=no RANGEPROOF=no WHITELIST=no SCHNORRSIG=no MUSIG=no matrix: - WIDEMUL=int64 EXPERIMENTAL=yes RANGEPROOF=yes WHITELIST=yes GENERATOR=yes SCHNORRSIG=yes MUSIG=yes - WIDEMUL=int128 EXPERIMENTAL=yes RANGEPROOF=yes WHITELIST=yes GENERATOR=yes SCHNORRSIG=yes MUSIG=yes - WIDEMUL=int64 RECOVERY=yes - - WIDEMUL=int64 ECDH=yes EXPERIMENTAL=yes SCHNORRSIG=yes MUSIG=yes + - WIDEMUL=int64 ECDH=yes EXPERIMENTAL=yes ECDSA_S2C=yes SCHNORRSIG=yes MUSIG=yes - WIDEMUL=int128 - - WIDEMUL=int128 RECOVERY=yes EXPERIMENTAL=yes SCHNORRSIG=yes MUSIG=yes - - WIDEMUL=int128 ECDH=yes EXPERIMENTAL=yes SCHNORRSIG=yes MUSIG=yes + - WIDEMUL=int128 RECOVERY=yes EXPERIMENTAL=yes ECDSA_S2C=yes SCHNORRSIG=yes MUSIG=yes + - WIDEMUL=int128 ECDH=yes EXPERIMENTAL=yes ECDSA_S2C=yes SCHNORRSIG=yes MUSIG=yes - WIDEMUL=int128 ASM=x86_64 - BIGNUM=no - BIGNUM=no RECOVERY=yes EXPERIMENTAL=yes SCHNORRSIG=yes MUSIG=yes + - BIGNUM=no RECOVERY=yes EXPERIMENTAL=yes ECDSA_S2C=yes SCHNORRSIG=yes MUSIG=yes - BIGNUM=no STATICPRECOMPUTATION=no - BUILD=distcheck WITH_VALGRIND=no CTIMETEST=no BENCH=no - CPPFLAGS=-DDETERMINISTIC diff --git a/Makefile.am b/Makefile.am index 7309fa4a..434360f8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -184,3 +184,8 @@ endif if ENABLE_MODULE_SCHNORRSIG include src/modules/schnorrsig/Makefile.am.include endif + +if ENABLE_MODULE_ECDSA_S2C +include src/modules/ecdsa_s2c/Makefile.am.include +endif + diff --git a/configure.ac b/configure.ac index f4a341bc..27b245eb 100644 --- a/configure.ac +++ b/configure.ac @@ -161,6 +161,11 @@ AC_ARG_ENABLE(module_schnorrsig, [enable_module_schnorrsig=$enableval], [enable_module_schnorrsig=no]) +AC_ARG_ENABLE(module_ecdsa_s2c, + AS_HELP_STRING([--enable-module-ecdsa-s2c],[enable ECDSA sign-to-contract module [default=no]]), + [enable_module_ecdsa_s2c=$enableval], + [enable_module_ecdsa_s2c=no]) + AC_ARG_ENABLE(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions [default=no]]), [use_external_default_callbacks=$enableval], @@ -509,6 +514,10 @@ if test x"$enable_module_extrakeys" = x"yes"; then AC_DEFINE(ENABLE_MODULE_EXTRAKEYS, 1, [Define this symbol to enable the extrakeys module]) fi +if test x"$enable_module_ecdsa_s2c" = x"yes"; then + AC_DEFINE(ENABLE_MODULE_ECDSA_S2C, 1, [Define this symbol to enable the ECDSA sign-to-contract module]) +fi + if test x"$use_external_asm" = x"yes"; then AC_DEFINE(USE_EXTERNAL_ASM, 1, [Define this symbol if an external (non-inline) assembly implementation is used]) fi @@ -532,6 +541,7 @@ if test x"$enable_experimental" = x"yes"; then AC_MSG_NOTICE([Building MuSig module: $enable_module_musig]) AC_MSG_NOTICE([Building extrakeys module: $enable_module_extrakeys]) AC_MSG_NOTICE([Building schnorrsig module: $enable_module_schnorrsig]) + AC_MSG_NOTICE([Building ECDSA sign-to-contract module: $enable_module_ecdsa_s2c]) AC_MSG_NOTICE([******]) @@ -565,6 +575,9 @@ else if test x"$enable_module_schnorrsig" = x"yes"; then AC_MSG_ERROR([schnorrsig module is experimental. Use --enable-experimental to allow.]) fi + if test x"$enable_module_ecdsa_s2c" = x"yes"; then + AC_MSG_ERROR([ECDSA sign-to-contract module module is experimental. Use --enable-experimental to allow.]) + fi if test x"$set_asm" = x"arm"; then AC_MSG_ERROR([ARM assembly optimization is experimental. Use --enable-experimental to allow.]) fi @@ -601,6 +614,7 @@ AM_CONDITIONAL([ENABLE_MODULE_RANGEPROOF], [test x"$enable_module_rangeproof" = AM_CONDITIONAL([ENABLE_MODULE_WHITELIST], [test x"$enable_module_whitelist" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_EXTRAKEYS], [test x"$enable_module_extrakeys" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_SCHNORRSIG], [test x"$enable_module_schnorrsig" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_ECDSA_S2C], [test x"$enable_module_ecdsa_s2c" = x"yes"]) AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$use_external_asm" = x"yes"]) AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm"]) AM_CONDITIONAL([ENABLE_MODULE_SURJECTIONPROOF], [test x"$enable_module_surjectionproof" = x"yes"]) @@ -625,6 +639,7 @@ echo " module ecdh = $enable_module_ecdh" echo " module recovery = $enable_module_recovery" echo " module extrakeys = $enable_module_extrakeys" echo " module schnorrsig = $enable_module_schnorrsig" +echo " module ecdsa-s2c = $enable_module_ecdsa_s2c" echo echo " asm = $set_asm" echo " bignum = $set_bignum" diff --git a/contrib/travis.sh b/contrib/travis.sh index c667c151..fb3e4d0c 100755 --- a/contrib/travis.sh +++ b/contrib/travis.sh @@ -17,6 +17,7 @@ fi --with-test-override-wide-multiply="$WIDEMUL" --with-bignum="$BIGNUM" --with-asm="$ASM" \ --enable-ecmult-static-precomputation="$STATICPRECOMPUTATION" --with-ecmult-gen-precision="$ECMULTGENPRECISION" \ --enable-module-ecdh="$ECDH" --enable-module-recovery="$RECOVERY" \ + --enable-module-ecdsa-s2c="$ECDSA_S2C" \ --enable-module-rangeproof="$RANGEPROOF" --enable-module-whitelist="$WHITELIST" --enable-module-generator="$GENERATOR" \ --enable-module-schnorrsig="$SCHNORRSIG" --enable-module-musig="$MUSIG"\ --with-valgrind="$WITH_VALGRIND" \ diff --git a/include/secp256k1_ecdsa_s2c.h b/include/secp256k1_ecdsa_s2c.h new file mode 100644 index 00000000..7f54e71f --- /dev/null +++ b/include/secp256k1_ecdsa_s2c.h @@ -0,0 +1,58 @@ +#ifndef SECP256K1_ECDSA_S2C_H +#define SECP256K1_ECDSA_S2C_H + +#include "secp256k1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Data structure that holds a sign-to-contract ("s2c") opening information. + * Sign-to-contract allows a signer to commit to some data as part of a signature. It + * can be used as an Out-argument in certain signing functions. + * + * The exact representation of data inside is implementation defined and not + * guaranteed to be portable between different platforms or versions. It is + * however guaranteed to be 64 bytes in size, and can be safely copied/moved. + * If you need to convert to a format suitable for storage, transmission, or + * comparison, use secp256k1_ecdsa_s2c_opening_serialize and secp256k1_ecdsa_s2c_opening_parse. + */ +typedef struct { + unsigned char data[64]; +} secp256k1_ecdsa_s2c_opening; + +/** Parse a sign-to-contract opening. + * + * Returns: 1 if the opening could be parsed + * 0 if the opening could not be parsed + * Args: ctx: a secp256k1 context object. + * Out: opening: pointer to an opening object. If 1 is returned, it is set to a + * parsed version of input. If not, its value is unspecified. + * In: input33: pointer to 33-byte array with a serialized opening + * + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_opening_parse( + const secp256k1_context* ctx, + secp256k1_ecdsa_s2c_opening* opening, + const unsigned char* input33 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Serialize a sign-to-contract opening into a byte sequence. + * + * Returns: 1 if the opening was successfully serialized. + * 0 if the opening could not be serialized + * Args: ctx: a secp256k1 context object + * Out: output33: pointer to a 33-byte array to place the serialized opening in + * In: opening: a pointer to an initialized `secp256k1_ecdsa_s2c_opening` + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_opening_serialize( + const secp256k1_context* ctx, + unsigned char* output33, + const secp256k1_ecdsa_s2c_opening* opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_ECDSA_S2C_H */ diff --git a/src/modules/ecdsa_s2c/Makefile.am.include b/src/modules/ecdsa_s2c/Makefile.am.include new file mode 100644 index 00000000..b4939a91 --- /dev/null +++ b/src/modules/ecdsa_s2c/Makefile.am.include @@ -0,0 +1,3 @@ +include_HEADERS += include/secp256k1_ecdsa_s2c.h +noinst_HEADERS += src/modules/ecdsa_s2c/main_impl.h +noinst_HEADERS += src/modules/ecdsa_s2c/tests_impl.h diff --git a/src/modules/ecdsa_s2c/main_impl.h b/src/modules/ecdsa_s2c/main_impl.h new file mode 100755 index 00000000..cf152359 --- /dev/null +++ b/src/modules/ecdsa_s2c/main_impl.h @@ -0,0 +1,28 @@ +/********************************************************************** + * Copyright (c) 2019-2020 Marko Bencun, Jonas Nick * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef SECP256K1_MODULE_ECDSA_S2C_MAIN_H +#define SECP256K1_MODULE_ECDSA_S2C_MAIN_H + +#include "include/secp256k1.h" +#include "include/secp256k1_ecdsa_s2c.h" + +int secp256k1_ecdsa_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_ecdsa_s2c_opening* opening, const unsigned char* input33) { + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(opening != NULL); + ARG_CHECK(input33 != NULL); + return secp256k1_ec_pubkey_parse(ctx, (secp256k1_pubkey*) opening, input33, 33); +} + +int secp256k1_ecdsa_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char* output33, const secp256k1_ecdsa_s2c_opening* opening) { + size_t out_len = 33; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output33 != NULL); + ARG_CHECK(opening != NULL); + return secp256k1_ec_pubkey_serialize(ctx, output33, &out_len, (const secp256k1_pubkey*) opening, SECP256K1_EC_COMPRESSED); +} + +#endif /* SECP256K1_ECDSA_S2C_MAIN_H */ diff --git a/src/modules/ecdsa_s2c/tests_impl.h b/src/modules/ecdsa_s2c/tests_impl.h new file mode 100644 index 00000000..cd7e18f6 --- /dev/null +++ b/src/modules/ecdsa_s2c/tests_impl.h @@ -0,0 +1,76 @@ +/********************************************************************** + * Copyright (c) 2019-2020 Marko Bencun, Jonas Nick * + * Distributed under the MIT software license, see the accompanying * + * file COPYING or http://www.opensource.org/licenses/mit-license.php.* + **********************************************************************/ + +#ifndef SECP256K1_MODULE_ECDSA_S2C_TESTS_H +#define SECP256K1_MODULE_ECDSA_S2C_TESTS_H + +#include "include/secp256k1_ecdsa_s2c.h" + +void run_s2c_opening_test(void) { + int i = 0; + unsigned char output[33]; + secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + + unsigned char input[33] = { + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02 + }; + secp256k1_ecdsa_s2c_opening opening; + int32_t ecount = 0; + + secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount); + + /* First parsing, then serializing works */ + CHECK(secp256k1_ecdsa_s2c_opening_parse(none, &opening, input) == 1); + CHECK(secp256k1_ecdsa_s2c_opening_serialize(none, output, &opening) == 1); + CHECK(secp256k1_ecdsa_s2c_opening_parse(none, &opening, input) == 1); + CHECK(ecount == 0); + + CHECK(secp256k1_ecdsa_s2c_opening_parse(none, NULL, input) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_opening_parse(none, &opening, NULL) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_opening_parse(none, &opening, input) == 1); + + CHECK(secp256k1_ecdsa_s2c_opening_serialize(none, NULL, &opening) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_s2c_opening_serialize(none, output, NULL) == 0); + + CHECK(ecount == 4); + /* Invalid pubkey makes parsing fail */ + input[0] = 0; /* bad oddness bit */ + CHECK(secp256k1_ecdsa_s2c_opening_parse(none, &opening, input) == 0); + input[0] = 2; + input[31] = 1; /* point not on the curve */ + CHECK(secp256k1_ecdsa_s2c_opening_parse(none, &opening, input) == 0); + CHECK(ecount == 4); /* neither of the above are API errors */ + + /* Try parsing and serializing a bunch of openings */ + for (i = 0; i < count; i++) { + /* This is expected to fail in about 50% of iterations because the + * points' x-coordinates are uniformly random */ + if (secp256k1_ecdsa_s2c_opening_parse(none, &opening, input) == 1) { + CHECK(secp256k1_ecdsa_s2c_opening_serialize(none, output, &opening) == 1); + CHECK(memcmp(output, input, sizeof(output)) == 0); + } + secp256k1_testrand256(&input[1]); + /* Set pubkey oddness tag to first bit of input[1] */ + input[0] = (input[1] & 1) + 2; + i++; + } + + secp256k1_context_destroy(none); +} + + +static void run_ecdsa_s2c_tests(void) { + run_s2c_opening_test(); +} + +#endif /* SECP256K1_MODULE_ECDSA_S2C_TESTS_H */ diff --git a/src/secp256k1.c b/src/secp256k1.c index 0c7a2575..a48a7371 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -786,6 +786,10 @@ int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey * # include "modules/schnorrsig/main_impl.h" #endif +#ifdef ENABLE_MODULE_ECDSA_S2C +# include "modules/ecdsa_s2c/main_impl.h" +#endif + #ifdef ENABLE_MODULE_MUSIG # include "modules/musig/main_impl.h" #endif diff --git a/src/tests.c b/src/tests.c index bd6f7fd1..d0dc061a 100644 --- a/src/tests.c +++ b/src/tests.c @@ -5697,6 +5697,10 @@ void run_ecdsa_openssl(void) { # include "modules/schnorrsig/tests_impl.h" #endif +#ifdef ENABLE_MODULE_ECDSA_S2C +# include "modules/ecdsa_s2c/tests_impl.h" +#endif + void run_secp256k1_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -5998,6 +6002,11 @@ int main(int argc, char **argv) { run_schnorrsig_tests(); #endif +#ifdef ENABLE_MODULE_ECDSA_S2C + /* ECDSA sign to contract */ + run_ecdsa_s2c_tests(); +#endif + /* util tests */ run_secp256k1_memczero_test(); From 290dee566e14efa852d4e5437546f6a8ff8bfa1a Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sat, 5 Dec 2020 23:34:14 +0000 Subject: [PATCH 3/5] ecdsa-s2c: add actual sign-to-contract functionality Co-authored-by: Marko Bencun Co-authored-by: Jonas Nick --- include/secp256k1_ecdsa_s2c.h | 37 ++++++ src/modules/ecdsa_s2c/main_impl.h | 112 ++++++++++++++++++ src/modules/ecdsa_s2c/tests_impl.h | 184 ++++++++++++++++++++++++++++- src/modules/recovery/main_impl.h | 2 +- src/secp256k1.c | 45 ++++++- 5 files changed, 376 insertions(+), 4 deletions(-) diff --git a/include/secp256k1_ecdsa_s2c.h b/include/secp256k1_ecdsa_s2c.h index 7f54e71f..b28003f0 100644 --- a/include/secp256k1_ecdsa_s2c.h +++ b/include/secp256k1_ecdsa_s2c.h @@ -51,6 +51,43 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_opening_seria const secp256k1_ecdsa_s2c_opening* opening ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); +/** Same as secp256k1_ecdsa_sign, but s2c_data32 is committed to inside the nonce + * + * Returns: 1: signature created + * 0: the nonce generation function failed, or the private key was invalid. + * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) + * Out: sig: pointer to an array where the signature will be placed (cannot be NULL) + * s2c_opening: if non-NULL, pointer to an secp256k1_ecdsa_s2c_opening structure to populate + * In: msg32: the 32-byte message hash being signed (cannot be NULL) + * seckey: pointer to a 32-byte secret key (cannot be NULL) + * s2c_data32: pointer to a 32-byte data to commit to in the nonce (cannot be NULL) + */ +SECP256K1_API int secp256k1_ecdsa_s2c_sign( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature* sig, + secp256k1_ecdsa_s2c_opening* s2c_opening, + const unsigned char* msg32, + const unsigned char* seckey, + const unsigned char* s2c_data32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + +/** Verify a sign-to-contract commitment. + * + * Returns: 1: the signature contains a commitment to data32 (though it does + * not necessarily need to be a valid siganture!) + * 0: incorrect opening + * Args: ctx: a secp256k1 context object, initialized for verification. + * In: sig: the signature containing the sign-to-contract commitment (cannot be NULL) + * data32: the 32-byte data that was committed to (cannot be NULL) + * opening: pointer to the opening created during signing (cannot be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_verify_commit( + const secp256k1_context* ctx, + const secp256k1_ecdsa_signature *sig, + const unsigned char *data32, + const secp256k1_ecdsa_s2c_opening *opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/modules/ecdsa_s2c/main_impl.h b/src/modules/ecdsa_s2c/main_impl.h index cf152359..7246ddc7 100755 --- a/src/modules/ecdsa_s2c/main_impl.h +++ b/src/modules/ecdsa_s2c/main_impl.h @@ -10,6 +10,14 @@ #include "include/secp256k1.h" #include "include/secp256k1_ecdsa_s2c.h" +static void secp256k1_ecdsa_s2c_opening_save(secp256k1_ecdsa_s2c_opening* opening, secp256k1_ge* ge) { + secp256k1_pubkey_save((secp256k1_pubkey*) opening, ge); +} + +static int secp256k1_ecdsa_s2c_opening_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_ecdsa_s2c_opening* opening) { + return secp256k1_pubkey_load(ctx, ge, (const secp256k1_pubkey*) opening); +} + int secp256k1_ecdsa_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_ecdsa_s2c_opening* opening, const unsigned char* input33) { VERIFY_CHECK(ctx != NULL); ARG_CHECK(opening != NULL); @@ -25,4 +33,108 @@ int secp256k1_ecdsa_s2c_opening_serialize(const secp256k1_context* ctx, unsigned return secp256k1_ec_pubkey_serialize(ctx, output33, &out_len, (const secp256k1_pubkey*) opening, SECP256K1_EC_COMPRESSED); } +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("s2c/ecdsa/point")||SHA256("s2c/ecdsa/point"). */ +static void secp256k1_s2c_ecdsa_point_sha256_tagged(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0xa9b21c7bul; + sha->s[1] = 0x358c3e3eul; + sha->s[2] = 0x0b6863d1ul; + sha->s[3] = 0xc62b2035ul; + sha->s[4] = 0xb44b40ceul; + sha->s[5] = 0x254a8912ul; + sha->s[6] = 0x0f85d0d4ul; + sha->s[7] = 0x8a5bf91cul; + + sha->bytes = 64; +} + +/* Initializes SHA256 with fixed midstate. This midstate was computed by applying + * SHA256 to SHA256("s2c/ecdsa/data")||SHA256("s2c/ecdsa/data"). */ +static void secp256k1_s2c_ecdsa_data_sha256_tagged(secp256k1_sha256 *sha) { + secp256k1_sha256_initialize(sha); + sha->s[0] = 0xfeefd675ul; + sha->s[1] = 0x73166c99ul; + sha->s[2] = 0xe2309cb8ul; + sha->s[3] = 0x6d458113ul; + sha->s[4] = 0x01d3a512ul; + sha->s[5] = 0x00e18112ul; + sha->s[6] = 0x37ee0874ul; + sha->s[7] = 0x421fc55ful; + + sha->bytes = 64; +} + +int secp256k1_ecdsa_s2c_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature* signature, secp256k1_ecdsa_s2c_opening* s2c_opening, const unsigned char + *msg32, const unsigned char *seckey, const unsigned char* s2c_data32) { + secp256k1_scalar r, s; + int ret; + unsigned char ndata[32]; + secp256k1_sha256 s2c_sha; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(signature != NULL); + ARG_CHECK(seckey != NULL); + ARG_CHECK(s2c_data32 != NULL); + + /* Provide `s2c_data32` to the nonce function as additional data to + * derive the nonce. It is first hashed because it should be possible + * to derive nonces even if only a SHA256 commitment to the data is + * known. This is important in the ECDSA anti-klepto protocol. */ + secp256k1_s2c_ecdsa_data_sha256_tagged(&s2c_sha); + secp256k1_sha256_write(&s2c_sha, s2c_data32, 32); + secp256k1_sha256_finalize(&s2c_sha, ndata); + + secp256k1_s2c_ecdsa_point_sha256_tagged(&s2c_sha); + ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, NULL, &s2c_sha, s2c_opening, s2c_data32, msg32, seckey, NULL, ndata); + secp256k1_scalar_cmov(&r, &secp256k1_scalar_zero, !ret); + secp256k1_scalar_cmov(&s, &secp256k1_scalar_zero, !ret); + secp256k1_ecdsa_signature_save(signature, &r, &s); + return ret; +} + +int secp256k1_ecdsa_s2c_verify_commit(const secp256k1_context* ctx, const secp256k1_ecdsa_signature* sig, const unsigned char* data32, const secp256k1_ecdsa_s2c_opening* opening) { + secp256k1_ge commitment_ge; + secp256k1_ge original_pubnonce_ge; + unsigned char x_bytes[32]; + secp256k1_scalar sigr, sigs, x_scalar; + secp256k1_sha256 s2c_sha; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx)); + ARG_CHECK(sig != NULL); + ARG_CHECK(data32 != NULL); + ARG_CHECK(opening != NULL); + + if (!secp256k1_ecdsa_s2c_opening_load(ctx, &original_pubnonce_ge, opening)) { + return 0; + } + secp256k1_s2c_ecdsa_point_sha256_tagged(&s2c_sha); + if (!secp256k1_ec_commit(&ctx->ecmult_ctx, &commitment_ge, &original_pubnonce_ge, &s2c_sha, data32, 32)) { + return 0; + } + + /* Check that sig_r == commitment_x (mod n) + * sig_r is the x coordinate of R represented by a scalar. + * commitment_x is the x coordinate of the commitment (field element). + * + * Note that we are only checking the x-coordinate -- this is because the y-coordinate + * is not part of the ECDSA signature (and therefore not part of the commitment!) + */ + secp256k1_ecdsa_signature_load(ctx, &sigr, &sigs, sig); + + secp256k1_fe_normalize(&commitment_ge.x); + secp256k1_fe_get_b32(x_bytes, &commitment_ge.x); + /* Do not check overflow; overflowing a scalar does not affect whether + * or not the R value is a cryptographic commitment, only whether it + * is a valid R value for an ECDSA signature. If users care about that + * they should use `ecdsa_verify` or `anti_klepto_host_verify`. In other + * words, this check would be (at best) unnecessary, and (at worst) + * insufficient. */ + secp256k1_scalar_set_b32(&x_scalar, x_bytes, NULL); + return secp256k1_scalar_eq(&sigr, &x_scalar); +} + #endif /* SECP256K1_ECDSA_S2C_MAIN_H */ diff --git a/src/modules/ecdsa_s2c/tests_impl.h b/src/modules/ecdsa_s2c/tests_impl.h index cd7e18f6..6e8dae8f 100644 --- a/src/modules/ecdsa_s2c/tests_impl.h +++ b/src/modules/ecdsa_s2c/tests_impl.h @@ -9,6 +9,27 @@ #include "include/secp256k1_ecdsa_s2c.h" +static void test_ecdsa_s2c_tagged_hash(void) { + unsigned char tag_data[14] = "s2c/ecdsa/data"; + unsigned char tag_point[15] = "s2c/ecdsa/point"; + secp256k1_sha256 sha; + secp256k1_sha256 sha_optimized; + unsigned char output[32]; + unsigned char output_optimized[32]; + + secp256k1_sha256_initialize_tagged(&sha, tag_data, sizeof(tag_data)); + secp256k1_s2c_ecdsa_data_sha256_tagged(&sha_optimized); + secp256k1_sha256_finalize(&sha, output); + secp256k1_sha256_finalize(&sha_optimized, output_optimized); + CHECK(secp256k1_memcmp_var(output, output_optimized, 32) == 0); + + secp256k1_sha256_initialize_tagged(&sha, tag_point, sizeof(tag_point)); + secp256k1_s2c_ecdsa_point_sha256_tagged(&sha_optimized); + secp256k1_sha256_finalize(&sha, output); + secp256k1_sha256_finalize(&sha_optimized, output_optimized); + CHECK(secp256k1_memcmp_var(output, output_optimized, 32) == 0); +} + void run_s2c_opening_test(void) { int i = 0; unsigned char output[33]; @@ -62,15 +83,176 @@ void run_s2c_opening_test(void) { secp256k1_testrand256(&input[1]); /* Set pubkey oddness tag to first bit of input[1] */ input[0] = (input[1] & 1) + 2; - i++; } secp256k1_context_destroy(none); } +static void test_ecdsa_s2c_api(void) { + secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + secp256k1_context *both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); + + secp256k1_ecdsa_s2c_opening s2c_opening; + secp256k1_ecdsa_signature sig; + const unsigned char msg[32] = "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"; + const unsigned char sec[32] = "ssssssssssssssssssssssssssssssss"; + const unsigned char s2c_data[32] = "dddddddddddddddddddddddddddddddd"; + secp256k1_pubkey pk; + + int32_t ecount; + secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(sign, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(vrfy, counting_illegal_callback_fn, &ecount); + secp256k1_context_set_illegal_callback(both, counting_illegal_callback_fn, &ecount); + CHECK(secp256k1_ec_pubkey_create(ctx, &pk, sec)); + + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_sign(both, NULL, &s2c_opening, msg, sec, s2c_data) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_sign(both, &sig, NULL, msg, sec, s2c_data) == 1); + CHECK(ecount == 1); /* NULL opening is not an API error */ + CHECK(secp256k1_ecdsa_s2c_sign(both, &sig, &s2c_opening, NULL, sec, s2c_data) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_sign(both, &sig, &s2c_opening, msg, NULL, s2c_data) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_s2c_sign(both, &sig, &s2c_opening, msg, sec, NULL) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_s2c_sign(none, &sig, &s2c_opening, msg, sec, s2c_data) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_s2c_sign(vrfy, &sig, &s2c_opening, msg, sec, s2c_data) == 0); + CHECK(ecount == 6); + CHECK(secp256k1_ecdsa_s2c_sign(sign, &sig, &s2c_opening, msg, sec, s2c_data) == 1); + CHECK(ecount == 6); + + CHECK(secp256k1_ecdsa_verify(ctx, &sig, msg, &pk) == 1); + + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_verify_commit(both, NULL, s2c_data, &s2c_opening) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(both, &sig, NULL, &s2c_opening) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_verify_commit(both, &sig, s2c_data, NULL) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_s2c_verify_commit(none, &sig, s2c_data, &s2c_opening) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_s2c_verify_commit(sign, &sig, s2c_data, &s2c_opening) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &sig, s2c_data, &s2c_opening) == 1); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &sig, sec, &s2c_opening) == 0); + CHECK(ecount == 5); /* wrong data is not an API error */ + + /* Signing with NULL s2c_opening gives the same result */ + CHECK(secp256k1_ecdsa_s2c_sign(sign, &sig, NULL, msg, sec, s2c_data) == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &sig, s2c_data, &s2c_opening) == 1); + + secp256k1_context_destroy(both); + secp256k1_context_destroy(vrfy); + secp256k1_context_destroy(sign); + secp256k1_context_destroy(none); +} + +/* When using sign-to-contract commitments, the nonce function is fixed, so we can use fixtures to test. */ +typedef struct { + unsigned char s2c_data[32]; + unsigned char expected_s2c_opening[33]; +} ecdsa_s2c_test; + +static ecdsa_s2c_test ecdsa_s2c_tests[] = { + { + "\x1b\xf6\xfb\x42\xf4\x1e\xb8\x76\xc4\xd7\xaa\x0d\x67\x24\x2b\x00\xba\xab\x99\xdc\x20\x84\x49\x3e\x4e\x63\x27\x7f\xa1\xf7\x7f\x22", + "\x03\xf0\x30\xde\xf3\x18\x8c\x0f\x56\xfc\xea\x87\x43\x5b\x30\x76\x43\xf4\x5d\xaf\xe2\x2c\xbc\x82\xfd\x56\x03\x4f\xae\x97\x41\x7d\x3a", + }, + { + "\x35\x19\x9a\x8f\xbf\x84\xad\x6e\xf6\x9a\x18\x4c\x1b\x19\x28\x5b\xef\xbe\x06\xe6\x0b\x62\x64\xe6\xd3\x73\x89\x3f\x68\x55\xe2\x4a", + "\x03\x90\x17\x17\xce\x7c\x74\x84\xa2\xce\x1b\x7d\xc7\x40\x3b\x14\xe0\x35\x49\x71\x39\x3e\xc0\x92\xa7\xf3\xe0\xc8\xe4\xe2\xd2\x63\x9d", + }, +}; + +static void test_ecdsa_s2c_fixed_vectors(void) { + const unsigned char privkey[32] = { + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + }; + const unsigned char message[32] = { + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + }; + size_t i; + + for (i = 0; i < sizeof(ecdsa_s2c_tests) / sizeof(ecdsa_s2c_tests[0]); i++) { + secp256k1_ecdsa_s2c_opening s2c_opening; + unsigned char opening_ser[33]; + const ecdsa_s2c_test *test = &ecdsa_s2c_tests[i]; + secp256k1_ecdsa_signature signature; + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, message, privkey, test->s2c_data) == 1); + CHECK(secp256k1_ecdsa_s2c_opening_serialize(ctx, opening_ser, &s2c_opening) == 1); + CHECK(memcmp(test->expected_s2c_opening, opening_ser, sizeof(opening_ser)) == 0); + } +} + +static void test_ecdsa_s2c_sign_verify(void) { + unsigned char privkey[32]; + secp256k1_pubkey pubkey; + unsigned char message[32]; + unsigned char noncedata[32]; + unsigned char s2c_data[32]; + unsigned char s2c_data2[32]; + secp256k1_ecdsa_signature signature; + secp256k1_ecdsa_s2c_opening s2c_opening; + + /* Generate a random key, message, noncedata and s2c_data. */ + { + secp256k1_scalar key; + random_scalar_order_test(&key); + secp256k1_scalar_get_b32(privkey, &key); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1); + + secp256k1_testrand256_test(message); + secp256k1_testrand256_test(noncedata); + secp256k1_testrand256_test(s2c_data); + secp256k1_testrand256_test(s2c_data2); + } + + { /* invalid privkeys */ + unsigned char zero_privkey[32] = {0}; + unsigned char overflow_privkey[32] = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, NULL, message, zero_privkey, s2c_data) == 0); + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, NULL, message, overflow_privkey, s2c_data) == 0); + } + /* Check that the sign-to-contract signature is valid, with s2c_data. Also check the commitment. */ + { + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, message, privkey, s2c_data) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature, message, &pubkey) == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(ctx, &signature, s2c_data, &s2c_opening) == 1); + } + /* Check that an invalid commitment does not verify */ + { + unsigned char sigbytes[64]; + size_t i; + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, message, privkey, s2c_data) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature, message, &pubkey) == 1); + + CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, sigbytes, &signature) == 1); + for(i = 0; i < 32; i++) { + /* change one byte */ + sigbytes[i] = (((int)sigbytes[i]) + 1) % 256; + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &signature, sigbytes) == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(ctx, &signature, s2c_data, &s2c_opening) == 0); + /* revert */ + sigbytes[i] = (((int)sigbytes[i]) + 255) % 256; + } + } +} static void run_ecdsa_s2c_tests(void) { run_s2c_opening_test(); + test_ecdsa_s2c_tagged_hash(); + test_ecdsa_s2c_api(); + test_ecdsa_s2c_fixed_vectors(); + test_ecdsa_s2c_sign_verify(); } #endif /* SECP256K1_MODULE_ECDSA_S2C_TESTS_H */ diff --git a/src/modules/recovery/main_impl.h b/src/modules/recovery/main_impl.h index e2576aa9..4a225dcb 100644 --- a/src/modules/recovery/main_impl.h +++ b/src/modules/recovery/main_impl.h @@ -129,7 +129,7 @@ int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecd ARG_CHECK(signature != NULL); ARG_CHECK(seckey != NULL); - ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, &recid, msg32, seckey, noncefp, noncedata); + ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, &recid, NULL, NULL, NULL, msg32, seckey, noncefp, noncedata); secp256k1_ecdsa_recoverable_signature_save(signature, &r, &s, recid); return ret; } diff --git a/src/secp256k1.c b/src/secp256k1.c index a48a7371..68390303 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -37,6 +37,18 @@ # include "modules/rangeproof/rangeproof.h" #endif +#ifdef ENABLE_MODULE_ECDSA_S2C +# include "include/secp256k1_ecdsa_s2c.h" +static void secp256k1_ecdsa_s2c_opening_save(secp256k1_ecdsa_s2c_opening* opening, secp256k1_ge* ge); +#else +typedef void secp256k1_ecdsa_s2c_opening; +static void secp256k1_ecdsa_s2c_opening_save(secp256k1_ecdsa_s2c_opening* opening, secp256k1_ge* ge) { + (void) opening; + (void) ge; + VERIFY_CHECK(0); +} +#endif + #define ARG_CHECK(cond) do { \ if (EXPECT(!(cond), 0)) { \ secp256k1_callback_call(&ctx->illegal_callback, #cond); \ @@ -488,7 +500,7 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m const secp256k1_nonce_function secp256k1_nonce_function_rfc6979 = nonce_function_rfc6979; const secp256k1_nonce_function secp256k1_nonce_function_default = nonce_function_rfc6979; -static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, int* recid, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) { +static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, int* recid, secp256k1_sha256* s2c_sha, secp256k1_ecdsa_s2c_opening *s2c_opening, const unsigned char* s2c_data32, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) { secp256k1_scalar sec, non, msg; int ret = 0; int is_sec_valid; @@ -503,6 +515,11 @@ static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_sc if (noncefp == NULL) { noncefp = secp256k1_nonce_function_default; } + /* sign-to-contract commitments only work with the default nonce function, + * because we need to ensure that s2c_data is actually hashed into the nonce and + * not just ignored. Otherwise an attacker can exfiltrate the secret key by + * signing the same message thrice with different commitments. */ + VERIFY_CHECK(s2c_data32 == NULL || noncefp == secp256k1_nonce_function_default); /* Fail if the secret key is invalid. */ is_sec_valid = secp256k1_scalar_set_b32_seckey(&sec, seckey); @@ -518,6 +535,30 @@ static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_sc /* The nonce is still secret here, but it being invalid is is less likely than 1:2^255. */ secp256k1_declassify(ctx, &is_nonce_valid, sizeof(is_nonce_valid)); if (is_nonce_valid) { + if (s2c_data32 != NULL) { + secp256k1_gej nonce_pj; + secp256k1_ge nonce_p; + + /* Compute original nonce commitment/pubkey */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_pj, &non); + secp256k1_ge_set_gej(&nonce_p, &nonce_pj); + if (s2c_opening != NULL) { + secp256k1_ecdsa_s2c_opening_save(s2c_opening, &nonce_p); + } + + /* Because the nonce is valid, the nonce point isn't the point + * at infinity and we can declassify that information to be able to + * serialize the point. */ + secp256k1_declassify(ctx, &nonce_p.infinity, sizeof(nonce_p.infinity)); + + /* Tweak nonce with s2c commitment. */ + ret = secp256k1_ec_commit_seckey(&non, &nonce_p, s2c_sha, s2c_data32, 32); + secp256k1_declassify(ctx, &ret, sizeof(ret)); /* may be secret that the tweak falied, but happens with negligible probability */ + if (!ret) { + break; + } + } + ret = secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, r, s, &sec, &msg, &non, recid); /* The final signature is no longer a secret, nor is the fact that we were successful or not. */ secp256k1_declassify(ctx, &ret, sizeof(ret)); @@ -553,7 +594,7 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature ARG_CHECK(signature != NULL); ARG_CHECK(seckey != NULL); - ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, NULL, msg32, seckey, noncefp, noncedata); + ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, NULL, NULL, NULL, NULL, msg32, seckey, noncefp, noncedata); secp256k1_ecdsa_signature_save(signature, &r, &s); return ret; } From 396b558273ce88969d4b0abc86e003f7557224f7 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 6 Dec 2020 16:31:42 +0000 Subject: [PATCH 4/5] ecdsa-s2c: add anti-klepto protocol Co-authored-by: Marko Bencun Co-authored-by: Jonas Nick --- include/secp256k1_ecdsa_s2c.h | 139 +++++++++++++++++++++++++ src/modules/ecdsa_s2c/main_impl.h | 58 +++++++++++ src/modules/ecdsa_s2c/tests_impl.h | 158 +++++++++++++++++++++++++++++ 3 files changed, 355 insertions(+) diff --git a/include/secp256k1_ecdsa_s2c.h b/include/secp256k1_ecdsa_s2c.h index b28003f0..482b0c13 100644 --- a/include/secp256k1_ecdsa_s2c.h +++ b/include/secp256k1_ecdsa_s2c.h @@ -3,6 +3,14 @@ #include "secp256k1.h" +/** This module implements the sign-to-contract scheme for ECDSA signatures, as + * well as the "ECDSA Anti-Klepto Protocol" that is based on sign-to-contract + * and is specified further down. The sign-to-contract scheme allows creating a + * signature that also commits to some data. This works by offsetting the public + * nonce point of the signature R by hash(R, data)*G where G is the secp256k1 + * group generator. + */ + #ifdef __cplusplus extern "C" { #endif @@ -88,6 +96,137 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_verify_commit const secp256k1_ecdsa_s2c_opening *opening ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** ECDSA Anti-Klepto Protocol + * + * The ecdsa_anti_klepto_* 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. + * + * The following scheme is described by Stepan Snigirev here: + * https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-February/017655.html + * and by Pieter Wuille (as "Scheme 6") here: + * https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-March/017667.html + * + * In order to ensure the host cannot trick the signing device into revealing its + * keys, or the signing device to bias the nonce despite the host's contributions, + * the host and client must engage in a commit-reveal protocol as follows: + * 1. The host draws randomness `rho` and computes a sha256 commitment to it using + * `secp256k1_ecdsa_anti_klepto_host_commit`. It sends this to the signing device. + * 2. The signing device computes a public nonce `R` using the host's commitment + * as auxiliary randomness, using `secp256k1_ecdsa_anti_klepto_signer_commit`. + * The signing device sends the resulting `R` to the host as a s2c_opening. + * + * If, at any point from this step onward, the hardware device fails, it is + * okay to restart the protocol using **exactly the same `rho`** and checking + * that the hardware device proposes **exactly the same** `R`. Otherwise, the + * hardware device may be selectively aborting and thereby biasing the set of + * nonces that are used in actual signatures. + * + * It takes many (>100) such aborts before there is a plausible attack, given + * current knowledge in 2020. However such aborts accumulate even across a total + * replacement of all relevant devices (but not across replacement of the actual + * signing keys with new independently random ones). + * + * In case the hardware device cannot be made to sign with the given `rho`, `R` + * pair, wallet authors should alert the user and present a very scary message + * implying that if this happens more than even a few times, say 20 or more times + * EVER, they should change hardware vendors and perhaps sweep their coins. + * + * 3. The host replies with `rho` generated in step 1. + * 4. The device signs with `secp256k1_anti_klepto_sign`, using `rho` as `host_data32`, + * and sends the signature to the host. + * 5. The host verifies that the signature's public nonce matches the opening from + * step 2 and its original randomness `rho`, using `secp256k1_anti_klepto_host_verify`. + * + * Rationale: + * - The reason for having a host commitment is to allow the signing device to + * deterministically derive a unique nonce even if the host restarts the protocol + * using the same message and keys. Otherwise the signer might reuse the original + * nonce in two iterations of the protocol with different `rho`, which leaks the + * the secret key. + * - The signer does not need to check that the host commitment matches the host's + * claimed `rho`. Instead it re-derives the commitment (and its original `R`) from + * the provided `rho`. If this differs from the original commitment, the result + * will be an invalid `s2c_opening`, but since `R` was unique there is no risk to + * the signer's secret keys. Because of this, the signing device does not need to + * maintain any state about the progress of the protocol. + */ + +/** Create the initial host commitment to `rho`. Part of the ECDSA Anti-Klepto 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). It must come from + * a cryptographically secure RNG. As per the protocol, this value must not + * be revealed to the client until after the host has received the client + * commitment. + */ +SECP256K1_API int secp256k1_ecdsa_anti_klepto_host_commit( + const secp256k1_context* ctx, + unsigned char* rand_commitment32, + const unsigned char* rand32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Compute signer's original nonce. Part of the ECDSA Anti-Klepto Protocol. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) + * Out: s2c_opening: pointer to an s2c_opening where the signer's public nonce 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_ecdsa_anti_klepto_signer_commit( + const secp256k1_context* ctx, + secp256k1_ecdsa_s2c_opening* s2c_opening, + 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); + +/** Same as secp256k1_ecdsa_sign, but commits to host randomness in the nonce. Part of the + * ECDSA Anti-Klepto Protocol. + * + * Returns: 1: signature created + * 0: the nonce generation function failed, or the private key was invalid. + * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) + * Out: sig: pointer to an array where the signature will be placed (cannot be NULL) + * In: msg32: the 32-byte message hash being signed (cannot be NULL) + * seckey: pointer to a 32-byte secret key (cannot be NULL) + * host_data32: pointer to 32-byte host-provided randomness (cannot be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_anti_klepto_sign( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature* sig, + const unsigned char* msg32, + const unsigned char* seckey, + const unsigned char* host_data32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Verify a signature was correctly constructed using the ECDSA Anti-Klepto Protocol. + * + * Returns: 1: the signature is valid and contains a commitment to host_data32 + * 0: incorrect opening + * Args: ctx: a secp256k1 context object, initialized for verification. + * In: sig: the signature produced by the signer (cannot be NULL) + * msghash32: the 32-byte message hash being verified (cannot be NULL) + * pubkey: pointer to the signer's public key (cannot be NULL) + * host_data32: the 32-byte data provided by the host (cannot be NULL) + * opening: the s2c opening provided by the signer (cannot be NULL) + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_anti_klepto_host_verify( + const secp256k1_context* ctx, + const secp256k1_ecdsa_signature *sig, + const unsigned char *msg32, + const secp256k1_pubkey *pubkey, + const unsigned char *host_data32, + const secp256k1_ecdsa_s2c_opening *opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + #ifdef __cplusplus } #endif diff --git a/src/modules/ecdsa_s2c/main_impl.h b/src/modules/ecdsa_s2c/main_impl.h index 7246ddc7..093bab85 100755 --- a/src/modules/ecdsa_s2c/main_impl.h +++ b/src/modules/ecdsa_s2c/main_impl.h @@ -137,4 +137,62 @@ int secp256k1_ecdsa_s2c_verify_commit(const secp256k1_context* ctx, const secp25 return secp256k1_scalar_eq(&sigr, &x_scalar); } +/*** anti-klepto ***/ +int secp256k1_ecdsa_anti_klepto_host_commit(const 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_s2c_ecdsa_data_sha256_tagged(&sha); + secp256k1_sha256_write(&sha, rand32, 32); + secp256k1_sha256_finalize(&sha, rand_commitment32); + return 1; +} + +int secp256k1_ecdsa_anti_klepto_signer_commit(const secp256k1_context* ctx, secp256k1_ecdsa_s2c_opening* opening, const unsigned char* msg32, const unsigned char* seckey32, const unsigned char* rand_commitment32) { + unsigned char nonce32[32]; + secp256k1_scalar k; + secp256k1_gej rj; + secp256k1_ge r; + unsigned int count = 0; + int is_nonce_valid = 0; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(opening != NULL); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(seckey32 != NULL); + ARG_CHECK(rand_commitment32 != NULL); + + memset(nonce32, 0, 32); + while (!is_nonce_valid) { + /* cast to void* removes const qualifier, but secp256k1_nonce_function_default does not modify it */ + if (!secp256k1_nonce_function_default(nonce32, msg32, seckey32, NULL, (void*)rand_commitment32, count)) { + secp256k1_callback_call(&ctx->error_callback, "(cryptographically unreachable) generated bad nonce"); + } + is_nonce_valid = secp256k1_scalar_set_b32_seckey(&k, nonce32); + /* The nonce is still secret here, but it being invalid is is less likely than 1:2^255. */ + secp256k1_declassify(ctx, &is_nonce_valid, sizeof(is_nonce_valid)); + count++; + } + + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k); + secp256k1_ge_set_gej(&r, &rj); + secp256k1_ecdsa_s2c_opening_save(opening, &r); + memset(nonce32, 0, 32); + secp256k1_scalar_clear(&k); + return 1; +} + +int secp256k1_anti_klepto_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char* msg32, const unsigned char* seckey, const unsigned char* host_data32) { + return secp256k1_ecdsa_s2c_sign(ctx, sig, NULL, msg32, seckey, host_data32); +} + +int secp256k1_anti_klepto_host_verify(const secp256k1_context* ctx, const secp256k1_ecdsa_signature *sig, const unsigned char *msg32, const secp256k1_pubkey *pubkey, const unsigned char *host_data32, const secp256k1_ecdsa_s2c_opening *opening) { + return secp256k1_ecdsa_s2c_verify_commit(ctx, sig, host_data32, opening) && + secp256k1_ecdsa_verify(ctx, sig, msg32, pubkey); +} + #endif /* SECP256K1_ECDSA_S2C_MAIN_H */ diff --git a/src/modules/ecdsa_s2c/tests_impl.h b/src/modules/ecdsa_s2c/tests_impl.h index 6e8dae8f..c76e6b11 100644 --- a/src/modules/ecdsa_s2c/tests_impl.h +++ b/src/modules/ecdsa_s2c/tests_impl.h @@ -99,6 +99,8 @@ static void test_ecdsa_s2c_api(void) { const unsigned char msg[32] = "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"; const unsigned char sec[32] = "ssssssssssssssssssssssssssssssss"; const unsigned char s2c_data[32] = "dddddddddddddddddddddddddddddddd"; + const unsigned char hostrand[32] = "hrhrhrhrhrhrhrhrhrhrhrhrhrhrhrhr"; + unsigned char hostrand_commitment[32]; secp256k1_pubkey pk; int32_t ecount; @@ -148,6 +150,65 @@ static void test_ecdsa_s2c_api(void) { CHECK(secp256k1_ecdsa_s2c_sign(sign, &sig, NULL, msg, sec, s2c_data) == 1); CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &sig, s2c_data, &s2c_opening) == 1); + /* anti-klepto */ + ecount = 0; + CHECK(secp256k1_ecdsa_anti_klepto_host_commit(none, NULL, hostrand) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_anti_klepto_host_commit(none, hostrand_commitment, NULL) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_anti_klepto_host_commit(none, hostrand_commitment, hostrand) == 1); + CHECK(ecount == 2); + + ecount = 0; + CHECK(secp256k1_ecdsa_anti_klepto_signer_commit(both, NULL, msg, sec, hostrand_commitment) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_anti_klepto_signer_commit(both, &s2c_opening, NULL, sec, hostrand_commitment) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_anti_klepto_signer_commit(both, &s2c_opening, msg, NULL, hostrand_commitment) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_anti_klepto_signer_commit(both, &s2c_opening, msg, sec, NULL) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_anti_klepto_signer_commit(none, &s2c_opening, msg, sec, hostrand_commitment) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_ecdsa_anti_klepto_signer_commit(vrfy, &s2c_opening, msg, sec, hostrand_commitment) == 0); + CHECK(ecount == 6); + CHECK(secp256k1_ecdsa_anti_klepto_signer_commit(sign, &s2c_opening, msg, sec, hostrand_commitment) == 1); + CHECK(ecount == 6); + + ecount = 0; + CHECK(secp256k1_anti_klepto_sign(both, NULL, msg, sec, hostrand) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_anti_klepto_sign(both, &sig, NULL, sec, hostrand) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_anti_klepto_sign(both, &sig, msg, NULL, hostrand) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_anti_klepto_sign(both, &sig, msg, sec, NULL) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_anti_klepto_sign(none, &sig, msg, sec, hostrand) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_anti_klepto_sign(vrfy, &sig, msg, sec, hostrand) == 0); + CHECK(ecount == 6); + CHECK(secp256k1_anti_klepto_sign(both, &sig, msg, sec, hostrand) == 1); + CHECK(ecount == 6); + + ecount = 0; + CHECK(secp256k1_anti_klepto_host_verify(both, NULL, msg, &pk, hostrand, &s2c_opening) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_anti_klepto_host_verify(both, &sig, NULL, &pk, hostrand, &s2c_opening) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_anti_klepto_host_verify(both, &sig, msg, NULL, hostrand, &s2c_opening) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_anti_klepto_host_verify(both, &sig, msg, &pk, NULL, &s2c_opening) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_anti_klepto_host_verify(both, &sig, msg, &pk, hostrand, NULL) == 0); + CHECK(ecount == 5); + CHECK(secp256k1_anti_klepto_host_verify(none, &sig, msg, &pk, hostrand, &s2c_opening) == 0); + CHECK(ecount == 6); + CHECK(secp256k1_anti_klepto_host_verify(sign, &sig, msg, &pk, hostrand, &s2c_opening) == 0); + CHECK(ecount == 7); + CHECK(secp256k1_anti_klepto_host_verify(vrfy, &sig, msg, &pk, hostrand, &s2c_opening) == 1); + CHECK(ecount == 7); + secp256k1_context_destroy(both); secp256k1_context_destroy(vrfy); secp256k1_context_destroy(sign); @@ -156,18 +217,24 @@ static void test_ecdsa_s2c_api(void) { /* When using sign-to-contract commitments, the nonce function is fixed, so we can use fixtures to test. */ typedef struct { + /* Data to commit to */ unsigned char s2c_data[32]; + /* Original nonce */ unsigned char expected_s2c_opening[33]; + /* Original nonce (anti-klepto protocol, which mixes in host randomness) */ + unsigned char expected_s2c_klepto_opening[33]; } ecdsa_s2c_test; static ecdsa_s2c_test ecdsa_s2c_tests[] = { { "\x1b\xf6\xfb\x42\xf4\x1e\xb8\x76\xc4\xd7\xaa\x0d\x67\x24\x2b\x00\xba\xab\x99\xdc\x20\x84\x49\x3e\x4e\x63\x27\x7f\xa1\xf7\x7f\x22", "\x03\xf0\x30\xde\xf3\x18\x8c\x0f\x56\xfc\xea\x87\x43\x5b\x30\x76\x43\xf4\x5d\xaf\xe2\x2c\xbc\x82\xfd\x56\x03\x4f\xae\x97\x41\x7d\x3a", + "\x02\xdf\x63\x75\x5d\x1f\x32\x92\xbf\xfe\xd8\x29\x86\xb1\x06\x49\x7c\x93\xb1\xf8\xbd\xc0\x45\x4b\x6b\x0b\x0a\x47\x79\xc0\xef\x71\x88", }, { "\x35\x19\x9a\x8f\xbf\x84\xad\x6e\xf6\x9a\x18\x4c\x1b\x19\x28\x5b\xef\xbe\x06\xe6\x0b\x62\x64\xe6\xd3\x73\x89\x3f\x68\x55\xe2\x4a", "\x03\x90\x17\x17\xce\x7c\x74\x84\xa2\xce\x1b\x7d\xc7\x40\x3b\x14\xe0\x35\x49\x71\x39\x3e\xc0\x92\xa7\xf3\xe0\xc8\xe4\xe2\xd2\x63\x9d", + "\x02\xc0\x4a\xc7\xf7\x71\xe8\xeb\xdb\xf3\x15\xff\x5e\x58\xb7\xfe\x95\x16\x10\x21\x03\x50\x00\x66\x17\x2c\x4f\xac\x5b\x20\xf9\xe0\xea", }, }; @@ -190,6 +257,7 @@ static void test_ecdsa_s2c_fixed_vectors(void) { CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, message, privkey, test->s2c_data) == 1); CHECK(secp256k1_ecdsa_s2c_opening_serialize(ctx, opening_ser, &s2c_opening) == 1); CHECK(memcmp(test->expected_s2c_opening, opening_ser, sizeof(opening_ser)) == 0); + CHECK(secp256k1_ecdsa_s2c_verify_commit(ctx, &signature, test->s2c_data, &s2c_opening) == 1); } } @@ -247,12 +315,102 @@ static void test_ecdsa_s2c_sign_verify(void) { } } +static void test_ecdsa_anti_klepto_signer_commit(void) { + size_t i; + unsigned char privkey[32] = { + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + }; + unsigned char message[32] = { + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + }; + /* Check that original pubnonce is derived from s2c_data */ + for (i = 0; i < sizeof(ecdsa_s2c_tests) / sizeof(ecdsa_s2c_tests[0]); i++) { + secp256k1_ecdsa_s2c_opening s2c_opening; + unsigned char buf[33]; + const ecdsa_s2c_test *test = &ecdsa_s2c_tests[i]; + CHECK(secp256k1_ecdsa_anti_klepto_signer_commit(ctx, &s2c_opening, message, privkey, test->s2c_data) == 1); + CHECK(secp256k1_ecdsa_s2c_opening_serialize(ctx, buf, &s2c_opening) == 1); + CHECK(memcmp(test->expected_s2c_klepto_opening, buf, sizeof(buf)) == 0); + } +} + +/* This tests the full ECDSA Anti-Klepto Protocol */ +static void test_ecdsa_anti_klepto(void) { + unsigned char signer_privkey[32]; + unsigned char host_msg[32]; + unsigned char host_commitment[32]; + unsigned char host_nonce_contribution[32]; + secp256k1_pubkey signer_pubkey; + secp256k1_ecdsa_signature signature; + secp256k1_ecdsa_s2c_opening s2c_opening; + + /* Generate a random key, message. */ + { + secp256k1_scalar key; + random_scalar_order_test(&key); + secp256k1_scalar_get_b32(signer_privkey, &key); + CHECK(secp256k1_ec_pubkey_create(ctx, &signer_pubkey, signer_privkey) == 1); + secp256k1_testrand256_test(host_msg); + secp256k1_testrand256_test(host_nonce_contribution); + } + + /* Protocol step 1. */ + CHECK(secp256k1_ecdsa_anti_klepto_host_commit(ctx, host_commitment, host_nonce_contribution) == 1); + /* Protocol step 2. */ + CHECK(secp256k1_ecdsa_anti_klepto_signer_commit(ctx, &s2c_opening, host_msg, signer_privkey, host_commitment) == 1); + /* Protocol step 3: host_nonce_contribution send to signer to be used in step 4. */ + /* Protocol step 4. */ + CHECK(secp256k1_anti_klepto_sign(ctx, &signature, host_msg, signer_privkey, host_nonce_contribution) == 1); + /* Protocol step 5. */ + CHECK(secp256k1_anti_klepto_host_verify(ctx, &signature, host_msg, &signer_pubkey, host_nonce_contribution, &s2c_opening) == 1); + /* Protocol step 5 (explicitly) */ + CHECK(secp256k1_ecdsa_s2c_verify_commit(ctx, &signature, host_nonce_contribution, &s2c_opening) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature, host_msg, &signer_pubkey) == 1); + + { /* host_verify: commitment does not match */ + unsigned char sigbytes[64]; + size_t i; + CHECK(secp256k1_ecdsa_signature_serialize_compact(ctx, sigbytes, &signature) == 1); + for(i = 0; i < 32; i++) { + /* change one byte */ + sigbytes[i] += 1; + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &signature, sigbytes) == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(ctx, &signature, host_nonce_contribution, &s2c_opening) == 0); + CHECK(secp256k1_anti_klepto_host_verify(ctx, &signature, host_msg, &signer_pubkey, host_nonce_contribution, &s2c_opening) == 0); + /* revert */ + sigbytes[i] -= 1; + } + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &signature, sigbytes) == 1); + } + { /* host_verify: message does not match */ + unsigned char bad_msg[32]; + secp256k1_testrand256_test(bad_msg); + CHECK(secp256k1_anti_klepto_host_verify(ctx, &signature, host_msg, &signer_pubkey, host_nonce_contribution, &s2c_opening) == 1); + CHECK(secp256k1_anti_klepto_host_verify(ctx, &signature, bad_msg, &signer_pubkey, host_nonce_contribution, &s2c_opening) == 0); + } + { /* s2c_sign: host provided data that didn't match commitment */ + secp256k1_ecdsa_s2c_opening orig_opening = s2c_opening; + unsigned char bad_nonce_contribution[32] = { 1, 2, 3, 4 }; + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, host_msg, signer_privkey, bad_nonce_contribution) == 1); + /* good signature but the opening (original public nonce does not match the original */ + CHECK(secp256k1_ecdsa_verify(ctx, &signature, host_msg, &signer_pubkey) == 1); + CHECK(secp256k1_anti_klepto_host_verify(ctx, &signature, host_msg, &signer_pubkey, host_nonce_contribution, &s2c_opening) == 0); + CHECK(secp256k1_anti_klepto_host_verify(ctx, &signature, host_msg, &signer_pubkey, bad_nonce_contribution, &s2c_opening) == 1); + CHECK(memcmp(&s2c_opening, &orig_opening, sizeof(s2c_opening)) != 0); + } +} + static void run_ecdsa_s2c_tests(void) { run_s2c_opening_test(); test_ecdsa_s2c_tagged_hash(); test_ecdsa_s2c_api(); test_ecdsa_s2c_fixed_vectors(); test_ecdsa_s2c_sign_verify(); + + test_ecdsa_anti_klepto_signer_commit(); + test_ecdsa_anti_klepto(); } #endif /* SECP256K1_MODULE_ECDSA_S2C_TESTS_H */ From 47efb5e39a1bf6330bd3bf6bc4b4416c5ca11878 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 21 Dec 2020 20:27:14 +0000 Subject: [PATCH 5/5] ecdsa-s2c: add ctime tests --- src/valgrind_ctime_test.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/valgrind_ctime_test.c b/src/valgrind_ctime_test.c index 3169e365..9750d185 100644 --- a/src/valgrind_ctime_test.c +++ b/src/valgrind_ctime_test.c @@ -25,6 +25,10 @@ #include "include/secp256k1_schnorrsig.h" #endif +#ifdef ENABLE_MODULE_ECDSA_S2C +#include "include/secp256k1_ecdsa_s2c.h" +#endif + int main(void) { secp256k1_context* ctx; secp256k1_ecdsa_signature signature; @@ -152,6 +156,31 @@ int main(void) { CHECK(ret == 1); #endif +#ifdef ENABLE_MODULE_ECDSA_S2C + { + unsigned char s2c_data[32] = {0}; + unsigned char s2c_data_comm[32] = {0}; + secp256k1_ecdsa_s2c_opening s2c_opening; + + VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + VALGRIND_MAKE_MEM_UNDEFINED(s2c_data, 32); + ret = secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, msg, key, s2c_data); + VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + CHECK(ret == 1); + + VALGRIND_MAKE_MEM_UNDEFINED(s2c_data, 32); + ret = secp256k1_ecdsa_anti_klepto_host_commit(ctx, s2c_data_comm, s2c_data); + VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + CHECK(ret == 1); + + VALGRIND_MAKE_MEM_UNDEFINED(key, 32); + VALGRIND_MAKE_MEM_UNDEFINED(s2c_data, 32); + ret = secp256k1_ecdsa_anti_klepto_signer_commit(ctx, &s2c_opening, msg, key, s2c_data); + VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret)); + CHECK(ret == 1); + } +#endif + secp256k1_context_destroy(ctx); return 0; }