diff --git a/.travis.yml b/.travis.yml index 3d315b1010..3c3e5247f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,14 +11,15 @@ compiler: - gcc env: global: - - FIELD=auto BIGNUM=auto SCALAR=auto ENDOMORPHISM=no STATICPRECOMPUTATION=yes ECMULTGENPRECISION=auto ASM=no BUILD=check EXTRAFLAGS= HOST= ECDH=no RECOVERY=no EXPERIMENTAL=no CTIMETEST=yes BENCH=yes SECP256K1_BENCH_ITERS=2 + - FIELD=auto BIGNUM=auto SCALAR=auto ENDOMORPHISM=no STATICPRECOMPUTATION=yes ECMULTGENPRECISION=auto ASM=no BUILD=check EXTRAFLAGS= HOST= ECDH=no RECOVERY=no ECDSA_SIGN_TO_CONTRACT=no EXPERIMENTAL=no CTIMETEST=yes BENCH=yes SECP256K1_BENCH_ITERS=2 matrix: - SCALAR=32bit RECOVERY=yes - - SCALAR=32bit FIELD=32bit ECDH=yes EXPERIMENTAL=yes + - SCALAR=32bit FIELD=32bit ECDH=yes ECDSA_SIGN_TO_CONTRACT=yes EXPERIMENTAL=yes + - SCALAR=32bit FIELD=32bit EXPERIMENTAL=yes - SCALAR=64bit - FIELD=64bit RECOVERY=yes - FIELD=64bit ENDOMORPHISM=yes - - FIELD=64bit ENDOMORPHISM=yes ECDH=yes EXPERIMENTAL=yes + - FIELD=64bit ENDOMORPHISM=yes ECDH=yes ECDSA_SIGN_TO_CONTRACT=yes EXPERIMENTAL=yes - FIELD=64bit ASM=x86_64 - FIELD=64bit ENDOMORPHISM=yes ASM=x86_64 - FIELD=32bit ENDOMORPHISM=yes @@ -85,7 +86,7 @@ before_script: ./autogen.sh script: - if [ -n "$HOST" ]; then export USE_HOST="--host=$HOST"; fi - if [ "x$HOST" = "xi686-linux-gnu" ]; then export CC="$CC -m32"; fi - - ./configure --enable-experimental=$EXPERIMENTAL --enable-endomorphism=$ENDOMORPHISM --with-field=$FIELD --with-bignum=$BIGNUM --with-asm=$ASM --with-scalar=$SCALAR --enable-ecmult-static-precomputation=$STATICPRECOMPUTATION --with-ecmult-gen-precision=$ECMULTGENPRECISION --enable-module-ecdh=$ECDH --enable-module-recovery=$RECOVERY $EXTRAFLAGS $USE_HOST + - ./configure --enable-experimental=$EXPERIMENTAL --enable-endomorphism=$ENDOMORPHISM --with-field=$FIELD --with-bignum=$BIGNUM --with-asm=$ASM --with-scalar=$SCALAR --enable-ecmult-static-precomputation=$STATICPRECOMPUTATION --with-ecmult-gen-precision=$ECMULTGENPRECISION --enable-module-ecdh=$ECDH --enable-module-recovery=$RECOVERY --enable-module-ecdsa-sign-to-contract=$ECDSA_SIGN_TO_CONTRACT $EXTRAFLAGS $USE_HOST - if [ -n "$BUILD" ]; then make -j2 $BUILD; fi - # travis_wait extends the 10 minutes without output allowed (https://docs.travis-ci.com/user/common-build-problems/#build-times-out-because-no-output-was-received) - # the `--error-exitcode` is required to make the test fail if valgrind found errors, otherwise it'll return 0 (http://valgrind.org/docs/manual/manual-core.html) diff --git a/Makefile.am b/Makefile.am index d8c1c79e8c..50dff60b1a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -152,3 +152,7 @@ endif if ENABLE_MODULE_RECOVERY include src/modules/recovery/Makefile.am.include endif + +if ENABLE_MODULE_ECDSA_SIGN_TO_CONTRACT +include src/modules/ecdsa_sign_to_contract/Makefile.am.include +endif diff --git a/configure.ac b/configure.ac index 7f762fa31b..48165b3892 100644 --- a/configure.ac +++ b/configure.ac @@ -136,6 +136,11 @@ AC_ARG_ENABLE(module_recovery, [enable_module_recovery=$enableval], [enable_module_recovery=no]) +AC_ARG_ENABLE(module_ecdsa_sign_to_contract, + AS_HELP_STRING([--enable-module-ecdsa-sign-to-contract],[enable ECDSA sign-to-contract module [default=no]]), + [enable_module_ecdsa_sign_to_contract=$enableval], + [enable_module_ecdsa_sign_to_contract=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], @@ -493,6 +498,10 @@ if test x"$enable_module_recovery" = x"yes"; then AC_DEFINE(ENABLE_MODULE_RECOVERY, 1, [Define this symbol to enable the ECDSA pubkey recovery module]) fi +if test x"$enable_module_ecdsa_sign_to_contract" = x"yes"; then + AC_DEFINE(ENABLE_MODULE_ECDSA_SIGN_TO_CONTRACT, 1, [Define this symbol to enable the ECDSA sign-to-contract module]) +fi + AC_C_BIGENDIAN() if test x"$use_external_asm" = x"yes"; then @@ -508,11 +517,15 @@ if test x"$enable_experimental" = x"yes"; then AC_MSG_NOTICE([WARNING: experimental build]) AC_MSG_NOTICE([Experimental features do not have stable APIs or properties, and may not be safe for production use.]) AC_MSG_NOTICE([Building ECDH module: $enable_module_ecdh]) + AC_MSG_NOTICE([Building ECDSA sign-to-contract module: $enable_module_ecdsa_sign_to_contract]) AC_MSG_NOTICE([******]) else if test x"$enable_module_ecdh" = x"yes"; then AC_MSG_ERROR([ECDH module is experimental. Use --enable-experimental to allow.]) fi + if test x"$enable_module_ecdsa_sign_to_contract" = x"yes"; then + AC_MSG_ERROR([ECDA 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 @@ -531,6 +544,7 @@ AM_CONDITIONAL([USE_BENCHMARK], [test x"$use_benchmark" = x"yes"]) AM_CONDITIONAL([USE_ECMULT_STATIC_PRECOMPUTATION], [test x"$set_precomp" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"]) AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"]) +AM_CONDITIONAL([ENABLE_MODULE_ECDSA_SIGN_TO_CONTRACT], [test x"$enable_module_ecdsa_sign_to_contract" = 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"]) @@ -543,20 +557,21 @@ AC_OUTPUT echo echo "Build Options:" -echo " with endomorphism = $use_endomorphism" -echo " with ecmult precomp = $set_precomp" -echo " with external callbacks = $use_external_default_callbacks" -echo " with benchmarks = $use_benchmark" -echo " with coverage = $enable_coverage" -echo " module ecdh = $enable_module_ecdh" -echo " module recovery = $enable_module_recovery" +echo " with endomorphism = $use_endomorphism" +echo " with ecmult precomp = $set_precomp" +echo " with external callbacks = $use_external_default_callbacks" +echo " with benchmarks = $use_benchmark" +echo " with coverage = $enable_coverage" +echo " module ecdh = $enable_module_ecdh" +echo " module recovery = $enable_module_recovery" +echo " module ecdsa sign-to-contract = $enable_module_ecdsa_sign_to_contract" echo -echo " asm = $set_asm" -echo " bignum = $set_bignum" -echo " field = $set_field" -echo " scalar = $set_scalar" -echo " ecmult window size = $set_ecmult_window" -echo " ecmult gen prec. bits = $set_ecmult_gen_precision" +echo " asm = $set_asm" +echo " bignum = $set_bignum" +echo " field = $set_field" +echo " scalar = $set_scalar" +echo " ecmult window size = $set_ecmult_window" +echo " ecmult gen prec. bits = $set_ecmult_gen_precision" echo echo " valgrind = $enable_valgrind" echo " CC = $CC" diff --git a/include/secp256k1.h b/include/secp256k1.h index 1678406610..fa635d2b3a 100644 --- a/include/secp256k1.h +++ b/include/secp256k1.h @@ -6,6 +6,7 @@ extern "C" { #endif #include +#include /* These rules specify the order of arguments in API calls: * @@ -81,6 +82,29 @@ typedef struct { unsigned char data[64]; } secp256k1_ecdsa_signature; +/** 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. + * + * This structure is not opaque, but it is strongly discouraged to read or write to + * it directly. + * + * The exact representation of data inside is implementation defined and not + * guaranteed to be portable between different platforms or versions. It can + * be safely copied/moved. + */ +typedef struct { + /* magic is set during initialization */ + uint64_t magic; + /* Public nonce before applying the sign-to-contract commitment */ + secp256k1_pubkey original_pubnonce; + /* Byte indicating if signing algorithm negated the nonce. Alternatively when + * verifying we could compute the EC commitment of original_pubnonce and the + * data and negate if this would not be a valid nonce. But this would prevent + * batch verification of sign-to-contract commitments. */ + int nonce_is_negated; +} secp256k1_s2c_opening; + /** A pointer to a function to deterministically generate a nonce. * * Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail. @@ -446,6 +470,37 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact( const secp256k1_ecdsa_signature* sig ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); +/** Parse a sign-to-contract opening. + * + * Returns: 1 if the opening was fully valid. + * 0 if the opening could not be parsed or is invalid. + * 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 undefined. + * In: input33: pointer to 33-byte array with a serialized opening + * + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_s2c_opening_parse( + const secp256k1_context* ctx, + secp256k1_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 was not initializaed. + * 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_s2c_opening`. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_s2c_opening_serialize( + const secp256k1_context* ctx, + unsigned char *output33, + const secp256k1_s2c_opening* opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + /** Verify an ECDSA signature. * * Returns: 1: correct signature diff --git a/include/secp256k1_ecdsa_sign_to_contract.h b/include/secp256k1_ecdsa_sign_to_contract.h new file mode 100644 index 0000000000..03d26bbd73 --- /dev/null +++ b/include/secp256k1_ecdsa_sign_to_contract.h @@ -0,0 +1,142 @@ +#ifndef SECP256K1_ECDSA_SIGN_TO_CONTRACT_H +#define SECP256K1_ECDSA_SIGN_TO_CONTRACT_H + +#include "secp256k1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Same as secp256k1_ecdsa_sign, but s2c_data32 is committed to by adding `hash(k*G, s2c_data32)` to + * the signing nonce `k`. + * 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: pointer to an secp256k1_s2c_opening structure which can be + * NULL but is required to be not NULL if this signature creates + * a sign-to-contract commitment (i.e. the `s2c_data` argument + * is not NULL). + * 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 create an optional + * sign-to-contract commitment to if not NULL (can be NULL). + */ +SECP256K1_API int secp256k1_ecdsa_s2c_sign( + const secp256k1_context* ctx, + secp256k1_ecdsa_signature *sig, + secp256k1_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); + +/** Verify a sign-to-contract commitment. + * + * Returns: 1: the signature contains a commitment to data32 + * 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 int secp256k1_ecdsa_s2c_verify_commit( + const secp256k1_context* ctx, + const secp256k1_ecdsa_signature *sig, + const unsigned char *data32, + const secp256k1_s2c_opening *opening +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + +/** Create a randomness commitment on the host as part of the ECDSA Anti Nonce Covert Channel 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_s2c_anti_nonce_covert_channel_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 ECDSA Anti Nonce Covert Channel Protocol. + * + * ECDSA Anti Nonce Covert Channel Protocol: + * + * The anti_nonce_covert_channel_* 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 randomness `k2`, commits to it with sha256 and sends the commitment to the client. + * 2. The client commits to its original nonce `k1` using the host commitment by calling + * `secp256k1_ecdsa_anti_covert_channel_client_commit`. The client sends the resulting commitment + * `R1` to the host. + * 3. The host replies with `k2` generated in step 1. + * 4. The client signs with `secp256k1_ecdsa_s2c_sign`, using the `k2` as `s2c_data` and + * sends the signature and opening to the host. + * 5. The host verifies that `R_x = (R1 + H(R1, k2)*G)_x`, where R_x is the `r` part of the signature by using + * `secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify` with the client's + * commitment from step 2 and the signature and opening received in step 4. If verification does + * not succeed, the protocol failed and can be restarted. + * + * Rationale: + * - The reason for having a host commitment is to allow the client to derive a unique nonce + * for every host randomness. Otherwise the client would reuse the original nonce and thereby + * leaking the secret key to the host. + * - The client does not need to check that the host commitment matches the host's randomness. + * That's because the client derives its nonce using the hosts randomness commitment. If the + * commitment doesn't match then the client will derive a different original nonce and the + * only result will be that the host is not able to verify the sign-to-contract commitment. + * Therefore, the client does not need to maintain state about the progress of the protocol. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: pointer to a context object (cannot be NULL) + * Out: client_commit: pointer to a pubkey where the clients 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_s2c_anti_nonce_covert_channel_client_commit( + const secp256k1_context* ctx, + secp256k1_pubkey *client_commit, + const unsigned char *msg32, + const unsigned char *seckey32, + unsigned char *rand_commitment32 +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Verify that a clients signature contains the hosts randomness as part of the Anti + * Nonce Covert Channel Protocol. Does not verify the signature itself. + * + * Returns 1 on success, 0 on failure. + * Args: ctx: pointer to a context object (cannot be NULL) + * In: sig: pointer to the signature whose randomness should be verified + * (cannot be NULL) + * rand32: pointer to the 32-byte randomness from the host which should + * be included by the signature (cannot be NULL) + * opening: pointer to the opening produced by the client when signing + * with `rand32` as `s2c_data` (cannot be NULL) + * client_commit: pointer to the client's commitment created in + * `secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit` + * (cannot be NULL) + */ +SECP256K1_API int secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify( + secp256k1_context *ctx, + const secp256k1_ecdsa_signature *sig, + const unsigned char *rand32, + const secp256k1_s2c_opening *opening, + const secp256k1_pubkey *client_commit +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +#ifdef __cplusplus +} +#endif + +#endif /* SECP256K1_ECDSA_SIGN_TO_CONTRACT_H */ diff --git a/src/modules/ecdsa_sign_to_contract/Makefile.am.include b/src/modules/ecdsa_sign_to_contract/Makefile.am.include new file mode 100644 index 0000000000..8f8a89c49f --- /dev/null +++ b/src/modules/ecdsa_sign_to_contract/Makefile.am.include @@ -0,0 +1,3 @@ +include_HEADERS += include/secp256k1_ecdsa_sign_to_contract.h +noinst_HEADERS += src/modules/ecdsa_sign_to_contract/main_impl.h +noinst_HEADERS += src/modules/ecdsa_sign_to_contract/tests_impl.h diff --git a/src/modules/ecdsa_sign_to_contract/main_impl.h b/src/modules/ecdsa_sign_to_contract/main_impl.h new file mode 100755 index 0000000000..f203b9c2bd --- /dev/null +++ b/src/modules/ecdsa_sign_to_contract/main_impl.h @@ -0,0 +1,156 @@ +/********************************************************************** + * 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_SIGN_TO_CONTRACT_MAIN_H +#define SECP256K1_MODULE_ECDSA_SIGN_TO_CONTRACT_MAIN_H + +#include "include/secp256k1_ecdsa_sign_to_contract.h" + +int secp256k1_ecdsa_s2c_sign(const secp256k1_context *ctx, secp256k1_ecdsa_signature *signature, secp256k1_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]; + const unsigned char* noncedata = NULL; + ARG_CHECK(signature != NULL); + + if(s2c_data32 != NULL) { + /* Provide s2c_data32 to the the nonce function as additional data to derive the nonce + * from. It's hashed because it should be possible to derive nonces even if only a SHA256 + * commitment to the data is known. This is for example important in the + * anti-nonce-covert-channel protocol. + */ + secp256k1_sha256 sha; + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, s2c_data32, 32); + secp256k1_sha256_finalize(&sha, ndata); + noncedata = ndata; + } + + ret = secp256k1_ecdsa_sign_helper(ctx, &r, &s, s2c_opening, msg32, seckey, s2c_data32, NULL, noncedata, NULL); + 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_s2c_opening *opening) { + secp256k1_pubkey commitment; + secp256k1_ge commitment_ge; + unsigned char x_bytes[32]; + secp256k1_scalar sigr, sigs, x_scalar; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(data32 != NULL); + ARG_CHECK(opening != NULL); + ARG_CHECK(secp256k1_s2c_commit_is_init(opening)); + + if (!secp256k1_ec_commit(ctx, &commitment, &opening->original_pubnonce, 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). + * + * It is sufficient to only compare the x coordinates as it is as difficult to find an client + * commitment to the negation of the point as to any other point. There is a small reduction in + * security as it is easier to find a collision with a point and its negation. + */ + secp256k1_ecdsa_signature_load(ctx, &sigr, &sigs, sig); + + if (!secp256k1_pubkey_load(ctx, &commitment_ge, &commitment)) { + return 0; + } + secp256k1_fe_normalize(&commitment_ge.x); + secp256k1_fe_get_b32(x_bytes, & commitment_ge.x); + secp256k1_scalar_set_b32(&x_scalar, x_bytes, NULL); + return secp256k1_scalar_eq(&sigr, &x_scalar); +} +int secp256k1_ecdsa_s2c_anti_nonce_covert_channel_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_ecdsa_s2c_anti_nonce_covert_channel_client_commit(const secp256k1_context* ctx, secp256k1_pubkey *client_commit, const unsigned char *msg32, const unsigned char *seckey32, unsigned char *rand_commitment32) { + unsigned char nonce32[32]; + secp256k1_scalar k; + secp256k1_gej rj; + secp256k1_ge r; + unsigned int count = 0; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(client_commit != NULL); + ARG_CHECK(msg32 != NULL); + ARG_CHECK(seckey32 != NULL); + ARG_CHECK(rand_commitment32 != NULL); + + while (1) { + int overflow = 0; + if (!secp256k1_nonce_function_default(nonce32, msg32, seckey32, NULL, rand_commitment32, count)) { + /* cannot happen with secp256k1_nonce_function_default */ + memset(nonce32, 0, 32); + secp256k1_scalar_clear(&k); + return 0; + } + + secp256k1_scalar_set_b32(&k, nonce32, &overflow); + if (!overflow && !secp256k1_scalar_is_zero(&k)) { + break; + } + count++; + } + + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rj, &k); + secp256k1_ge_set_gej(&r, &rj); + secp256k1_pubkey_save(client_commit, &r); + memset(nonce32, 0, 32); + secp256k1_scalar_clear(&k); + return 1; +} + +int secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(secp256k1_context *ctx, const secp256k1_ecdsa_signature *sig, const unsigned char *rand32, const secp256k1_s2c_opening *opening, const secp256k1_pubkey *client_commit) { + + secp256k1_ge gcommit; + secp256k1_ge gopening; + secp256k1_gej pj; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(sig != NULL); + ARG_CHECK(rand32 != NULL); + ARG_CHECK(opening != NULL); + ARG_CHECK(secp256k1_s2c_commit_is_init(opening)); + ARG_CHECK(client_commit != NULL); + + /* Check that client_commit == opening->original_pubnonce */ + secp256k1_gej_set_infinity(&pj); + if (!secp256k1_pubkey_load(ctx, &gcommit, client_commit)) { + return 0; + } + secp256k1_ge_neg(&gcommit, &gcommit); + secp256k1_gej_add_ge(&pj, &pj, &gcommit); + if (!secp256k1_pubkey_load(ctx, &gopening, &opening->original_pubnonce)) { + return 0; + } + secp256k1_gej_add_ge(&pj, &pj, &gopening); + if (!secp256k1_gej_is_infinity(&pj)) { + return 0; + } + if (!secp256k1_ecdsa_s2c_verify_commit(ctx, sig, rand32, opening)) { + return 0; + } + return 1; +} + +#endif /* SECP256K1_ECDSA_SIGN_TO_CONTRACT_MAIN_H */ diff --git a/src/modules/ecdsa_sign_to_contract/tests_impl.h b/src/modules/ecdsa_sign_to_contract/tests_impl.h new file mode 100644 index 0000000000..201f065afb --- /dev/null +++ b/src/modules/ecdsa_sign_to_contract/tests_impl.h @@ -0,0 +1,352 @@ +/********************************************************************** + * 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_SIGN_TO_CONTRACT_TESTS_H +#define SECP256K1_MODULE_ECDSA_SIGN_TO_CONTRACT_TESTS_H + +typedef struct { + unsigned char s2c_data[32]; + /* host_commitment = sha256() */ + unsigned char host_commitment[32]; + /* expected_pubnonce = host_commitment*G */ + unsigned char expected_pubnonce[33]; +} ecdsa_s2c_test; + +/* When using sign-to-contract commitments, the nonce function is fixed, so we can use fixtures to 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", + "\xcd\xfe\xb3\xad\x27\x00\x21\x9e\xf7\xe1\xd3\x48\x3e\x31\xe0\xbf\x19\x34\x50\xb3\x77\x41\x58\xaa\x5d\x0f\x95\xb9\xb6\x5b\xaf\xc2", + "\x02\x3d\xce\xb4\xef\x0d\x4f\x59\x98\xf2\xd3\x02\xdb\xfb\x17\x86\x24\xf6\x3e\x17\x5c\xd2\x13\xf5\xf8\x9a\x30\xce\xe4\x50\x17\x4c\x07", + }, + { + "\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", + "\x87\x62\x71\xd6\xfd\xc7\x57\x5a\x44\xb9\x81\x0a\xb2\xea\x8f\x54\xb5\x77\xe3\x35\x86\xb3\x4c\x0d\xc5\xf3\x5f\xf6\xbd\xb8\xeb\x6c", + "\x02\x61\x10\x22\x34\xd2\x03\xe6\x11\xaa\xe7\x1e\x4e\x04\x30\xc2\xf1\x28\x6d\x9c\x2f\x4c\x96\x4f\x54\x0d\x03\x5c\xed\x94\xd7\x42\x6f", + }, +}; + +static void test_ecdsa_s2c_original_pubnonce(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, + }; + secp256k1_ecdsa_signature signature; + secp256k1_s2c_opening s2c_opening; + unsigned char pubnonce[33]; + /* + Check that original pubnonce is derived from s2c_data and ndata. + */ + for (i = 0; i < sizeof(ecdsa_s2c_tests) / sizeof(ecdsa_s2c_tests[0]); i++) { + size_t pubnonce_size = 33; + const ecdsa_s2c_test *test = &ecdsa_s2c_tests[i]; + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, message, privkey, test->s2c_data) == 1); + CHECK(secp256k1_ec_pubkey_serialize(ctx, pubnonce, &pubnonce_size, &s2c_opening.original_pubnonce, SECP256K1_EC_COMPRESSED) == 1); + CHECK(memcmp(test->expected_pubnonce, pubnonce, pubnonce_size) == 0); + } +} + +static void test_ecdsa_s2c_api(void) { + secp256k1_ecdsa_signature signature; + unsigned char privkey[32] = {1}; + unsigned char message[32] = {0}; + unsigned char s2c_data[32] = {0}; + secp256k1_s2c_opening s2c_opening; + + 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); + + int ecount = 0; + 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); + + { + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_sign(none, &signature, NULL, message, privkey, NULL) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_sign(sign, &signature, NULL, message, privkey, NULL) == 1); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_sign(vrfy, &signature, NULL, message, privkey, NULL) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_sign(both, &signature, NULL, message, privkey, NULL) == 1); + CHECK(ecount == 2); + } + { /* message, signature, seckey */ + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_sign(sign, NULL, NULL, message, privkey, NULL) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_sign(sign, &signature, NULL, NULL, privkey, NULL) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_sign(sign, &signature, NULL, message, NULL, NULL) == 0); + CHECK(ecount == 3); + } + { /* either both opening and s2c_data are provided or none */ + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_sign(sign, &signature, NULL, message, privkey, NULL) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_ecdsa_s2c_sign(sign, &signature, &s2c_opening, message, privkey, s2c_data) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_ecdsa_s2c_sign(sign, &signature, NULL, message, privkey, s2c_data) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_sign(sign, &signature, &s2c_opening, message, privkey, NULL) == 0); + CHECK(ecount == 2); + } + { /* verify_commit: ctx */ + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_sign(sign, &signature, &s2c_opening, message, privkey, s2c_data) == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(none, &signature, s2c_data, &s2c_opening) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(sign, &signature, s2c_data, &s2c_opening) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &signature, s2c_data, &s2c_opening) == 1); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_verify_commit(both, &signature, s2c_data, &s2c_opening) == 1); + CHECK(ecount == 2); + } + { /* verify_commit: NULL signature, s2c_data, s2c_opening */ + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_sign(sign, &signature, &s2c_opening, message, privkey, s2c_data) == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, NULL, s2c_data, &s2c_opening) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &signature, NULL, &s2c_opening) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &signature, s2c_data, NULL) == 0); + CHECK(ecount == 3); + } + { /* verify_commit: invalid opening */ + secp256k1_s2c_opening invalid_opening = {0}; + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_verify_commit(vrfy, &signature, s2c_data, &invalid_opening) == 0); + CHECK(ecount == 1); + } + { /* anti_nonce_covert_channel_client_commit: ctx */ + secp256k1_pubkey commitment; + unsigned char rand_commitment[32] = {0}; + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(none, &commitment, message, privkey, rand_commitment) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(sign, &commitment, message, privkey, rand_commitment) == 1); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(vrfy, &commitment, message, privkey, rand_commitment) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(both, &commitment, message, privkey, rand_commitment) == 1); + CHECK(ecount == 2); + } + { /* anti_nonce_covert_channel_client_commit: client_commitment, msg32, seckey32, rand_commitment32 */ + secp256k1_pubkey commitment; + unsigned char rand_commitment[32] = {0}; + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(sign, NULL, message, privkey, rand_commitment) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(sign, &commitment, NULL, privkey, rand_commitment) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(sign, &commitment, message, NULL, rand_commitment) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(sign, &commitment, message, privkey, NULL) == 0); + CHECK(ecount == 4); + } + { /* anti_nonce_covert_channel_host_verify */ + unsigned char host_nonce[32] = {0}; + unsigned char host_commitment[32] = {0}; + secp256k1_pubkey client_commitment = {0}; + secp256k1_s2c_opening invalid_opening = {0}; + secp256k1_pubkey invalid_pubkey = {0}; + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(ctx, &client_commitment, message, privkey, host_commitment) == 1); + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(vrfy, NULL, host_nonce, &s2c_opening, &client_commitment) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(vrfy, &signature, NULL, &s2c_opening, &client_commitment) == 0); + CHECK(ecount == 2); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(vrfy, &signature, host_nonce, NULL, &client_commitment) == 0); + CHECK(ecount == 3); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(vrfy, &signature, host_nonce, &invalid_opening, &client_commitment) == 0); + CHECK(ecount == 4); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(vrfy, &signature, host_nonce, &s2c_opening, NULL) == 0); + CHECK(ecount == 5); + /* invalid client commitment */ + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(vrfy, &signature, host_nonce, &s2c_opening, &invalid_pubkey) == 0); + CHECK(ecount == 6); + /* invalid original pubnonce */ + memset(&s2c_opening.original_pubnonce, 0, sizeof(s2c_opening.original_pubnonce)); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(vrfy, &signature, host_nonce, &s2c_opening, &client_commitment) == 0); + CHECK(ecount == 7); + } + { /* anti_nonce_covert_channel_host_commit */ + unsigned char rand_commitment[32]; + unsigned char rand[32] = {1}; + ecount = 0; + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_commit(none, rand_commitment, rand) == 1); + CHECK(ecount == 0); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_commit(none, NULL, rand) == 0); + CHECK(ecount == 1); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_commit(none, rand_commitment, NULL) == 0); + CHECK(ecount == 2); + } +} + +static void test_ecdsa_s2c_sign_verify(void) { + unsigned char privkey[32]; + 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"; + 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_signature signature2; + secp256k1_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_rand256_test(message); + secp256k1_rand256_test(noncedata); + secp256k1_rand256_test(s2c_data); + secp256k1_rand256_test(s2c_data2); + } + + { /* invalid privkeys */ + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, NULL, message, zero_privkey, NULL) == 0); + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, NULL, message, overflow_privkey, NULL) == 0); + } + /* Check that sign-to-contract without any s2c_data results the same signature as normal sign. */ + { + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, NULL, message, privkey, NULL) == 1); + CHECK(secp256k1_ecdsa_sign(ctx, &signature2, message, privkey, NULL, NULL) == 1); + CHECK(memcmp(&signature, &signature2, sizeof(signature)) == 0); + } + + /* Check that the sign-to-contract signature is valid, without s2c_data */ + { + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, NULL, message, privkey, NULL) == 1); + CHECK(secp256k1_ecdsa_verify(ctx, &signature, message, &pubkey) == 1); + } + /* 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 test_ecdsa_s2c_anti_nonce_covert_channel_client_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, + }; + secp256k1_pubkey client_commit; + unsigned char pubnonce[33]; + /* + Check that original pubnonce is derived from s2c_data and ndata. + */ + for (i = 0; i < sizeof(ecdsa_s2c_tests) / sizeof(ecdsa_s2c_tests[0]); i++) { + size_t pubnonce_size = 33; + const ecdsa_s2c_test *test = &ecdsa_s2c_tests[i]; + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(ctx, &client_commit, message, privkey, (unsigned char*)test->host_commitment) == 1); + CHECK(secp256k1_ec_pubkey_serialize(ctx, pubnonce, &pubnonce_size, &client_commit, SECP256K1_EC_COMPRESSED) == 1); + CHECK(memcmp(test->expected_pubnonce, pubnonce, pubnonce_size) == 0); + } +} + +/* This tests the full ECDSA Anti Nonce Covert Channel Protocol */ +static void test_ecdsa_s2c_anti_nonce_covert_channel(void) { + unsigned char client_privkey[32]; + unsigned char host_msg[32]; + unsigned char host_commitment[32]; + unsigned char host_nonce_contribution[32]; + secp256k1_pubkey client_commitment; + secp256k1_ecdsa_signature signature; + secp256k1_s2c_opening s2c_opening; + + /* Generate a random key, message. */ + { + secp256k1_scalar key; + random_scalar_order_test(&key); + secp256k1_scalar_get_b32(client_privkey, &key); + secp256k1_rand256_test(host_msg); + secp256k1_rand256_test(host_nonce_contribution); + } + + /* Protocol step 1. */ + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_commit(ctx, host_commitment, host_nonce_contribution) == 1); + /* Protocol step 2. */ + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_client_commit(ctx, &client_commitment, host_msg, client_privkey, host_commitment) == 1); + /* Protocol step 3: host_nonce_contribution send to client to be used in step 4. */ + /* Protocol step 4. */ + CHECK(secp256k1_ecdsa_s2c_sign(ctx, &signature, &s2c_opening, host_msg, client_privkey, host_nonce_contribution) == 1); + /* Protocol step 5. */ + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(ctx, &signature, host_nonce_contribution, &s2c_opening, &client_commitment) == 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] = (((int)sigbytes[i]) + 1) % 256; + CHECK(secp256k1_ecdsa_signature_parse_compact(ctx, &signature, sigbytes) == 1); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(ctx, &signature, host_nonce_contribution, &s2c_opening, &client_commitment) == 0); + /* revert */ + sigbytes[i] = (((int)sigbytes[i]) + 255) % 256; + } + } + { /* host_verify: client commitment != opening original pubnonce */ + + unsigned char tweak[32] = {1}; + CHECK(secp256k1_ec_pubkey_tweak_add(ctx, &client_commitment, tweak) == 1); + CHECK(secp256k1_ecdsa_s2c_anti_nonce_covert_channel_host_verify(ctx, &signature, host_nonce_contribution, &s2c_opening, &client_commitment) == 0); + } +} + +static void run_ecdsa_sign_to_contract_tests(void) { + int i; + test_ecdsa_s2c_api(); + test_ecdsa_s2c_original_pubnonce(); + test_ecdsa_s2c_anti_nonce_covert_channel_client_commit(); + for (i = 0; i < count; i++) { + test_ecdsa_s2c_sign_verify(); + test_ecdsa_s2c_anti_nonce_covert_channel(); + } +} + +#endif /* SECP256K1_MODULE_ECDSA_SIGN_TO_CONTRACT_TESTS_H */ diff --git a/src/modules/recovery/main_impl.h b/src/modules/recovery/main_impl.h index ed356e53a5..087cc8e6c8 100644 --- a/src/modules/recovery/main_impl.h +++ b/src/modules/recovery/main_impl.h @@ -122,48 +122,13 @@ static int secp256k1_ecdsa_sig_recover(const secp256k1_ecmult_context *ctx, cons int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature *signature, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) { secp256k1_scalar r, s; - secp256k1_scalar sec, non, msg; + int ret; int recid; - int ret = 0; - int overflow = 0; - 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); - if (noncefp == NULL) { - noncefp = secp256k1_nonce_function_default; - } - - secp256k1_scalar_set_b32(&sec, seckey, &overflow); - /* Fail if the secret key is invalid. */ - if (!overflow && !secp256k1_scalar_is_zero(&sec)) { - unsigned char nonce32[32]; - unsigned int count = 0; - secp256k1_scalar_set_b32(&msg, msg32, NULL); - while (1) { - ret = noncefp(nonce32, msg32, seckey, NULL, (void*)noncedata, count); - if (!ret) { - break; - } - secp256k1_scalar_set_b32(&non, nonce32, &overflow); - if (!overflow && !secp256k1_scalar_is_zero(&non)) { - if (secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, &recid)) { - break; - } - } - count++; - } - memset(nonce32, 0, 32); - secp256k1_scalar_clear(&msg); - secp256k1_scalar_clear(&non); - secp256k1_scalar_clear(&sec); - } - if (ret) { - secp256k1_ecdsa_recoverable_signature_save(signature, &r, &s, recid); - } else { - memset(signature, 0, sizeof(*signature)); - } + ret = secp256k1_ecdsa_sign_helper(ctx, &r, &s, NULL, msg32, seckey, NULL, noncefp, noncedata, &recid); + secp256k1_scalar_cmov(&r, &secp256k1_scalar_zero, !ret); + secp256k1_scalar_cmov(&s, &secp256k1_scalar_zero, !ret); + secp256k1_ecdsa_recoverable_signature_save(signature, &r, &s, recid); return ret; } diff --git a/src/secp256k1.c b/src/secp256k1.c index c10ac4cda5..746fc153ee 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -467,8 +467,10 @@ 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; -int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature *signature, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) { - secp256k1_scalar r, s; +static void secp256k1_s2c_opening_init(secp256k1_s2c_opening *opening); +static int secp256k1_ec_commit_seckey(const secp256k1_context* ctx, unsigned char *seckey, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size); + +static int secp256k1_ecdsa_sign_helper(const secp256k1_context *ctx, secp256k1_scalar *r, secp256k1_scalar *s, secp256k1_s2c_opening *s2c_opening, const unsigned char *msg32, const unsigned char *seckey, const unsigned char* s2c_data32, secp256k1_nonce_function noncefp, const void* noncedata, int *recid) { secp256k1_scalar sec, non, msg; int ret = 0; int overflow = 0; @@ -477,11 +479,20 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature 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(r != NULL && s != NULL); ARG_CHECK(seckey != NULL); 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. */ + VERIFY_CHECK(s2c_data32 == NULL || noncefp == secp256k1_nonce_function_default); + /* s2c_opening and s2c_data32 should be either both non-NULL or both NULL. */ + ARG_CHECK((s2c_opening != NULL) == (s2c_data32 != NULL)); + if (s2c_opening != NULL) { + secp256k1_s2c_opening_init(s2c_opening); + } secp256k1_scalar_set_b32(&sec, seckey, &overflow); /* Fail if the secret key is invalid. */ @@ -499,11 +510,31 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature /* The nonce is still secret here, but it overflowing or being zero is is less likely than 1:2^255. */ secp256k1_declassify(ctx, &koverflow, sizeof(koverflow)); if (!koverflow) { - ret = secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, NULL); - /* The final signature is no longer a secret, nor is the fact that we were successful or not. */ - secp256k1_declassify(ctx, &ret, sizeof(ret)); - if (ret) { - break; + 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); + secp256k1_pubkey_save(&s2c_opening->original_pubnonce, &nonce_p); + + /* Tweak nonce with s2c commitment. */ + ret = secp256k1_ec_commit_seckey(ctx, nonce32, &s2c_opening->original_pubnonce, s2c_data32, 32); + if (!ret) { + break; + } + secp256k1_scalar_set_b32(&non, nonce32, &koverflow); + koverflow |= secp256k1_scalar_is_zero(&non); + } + + if (!koverflow) { + 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)); + if (ret) { + break; + } } } count++; @@ -514,10 +545,20 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature secp256k1_scalar_clear(&sec); secp256k1_scalar_cmov(&r, &secp256k1_scalar_zero, (!ret) | overflow); secp256k1_scalar_cmov(&s, &secp256k1_scalar_zero, (!ret) | overflow); - secp256k1_ecdsa_signature_save(signature, &r, &s); return !!ret & !overflow; } +int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature *signature, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) { + secp256k1_scalar r, s; + int ret; + ARG_CHECK(signature != NULL); + ret = secp256k1_ecdsa_sign_helper(ctx, &r, &s, NULL, msg32, seckey, NULL, noncefp, noncedata, NULL); + 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_ec_seckey_verify(const secp256k1_context* ctx, const unsigned char *seckey) { secp256k1_scalar sec; int ret; @@ -705,6 +746,142 @@ int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey * return 1; } +/* Compute an ec commitment tweak as hash(pubkey, data). */ +static int secp256k1_ec_commit_tweak(const secp256k1_context *ctx, unsigned char *tweak32, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) { + secp256k1_ge p; + unsigned char rbuf[33]; + size_t rbuf_size = sizeof(rbuf); + secp256k1_sha256 sha; + + if (data_size == 0) { + /* That's probably not what the caller wanted */ + return 0; + } + if(!secp256k1_pubkey_load(ctx, &p, pubkey)) { + return 0; + } + secp256k1_eckey_pubkey_serialize(&p, rbuf, &rbuf_size, 1); + + secp256k1_sha256_initialize(&sha); + secp256k1_sha256_write(&sha, rbuf, rbuf_size); + secp256k1_sha256_write(&sha, data, data_size); + secp256k1_sha256_finalize(&sha, tweak32); + return 1; +} + +/* Compute an ec commitment as pubkey + hash(pubkey, data)*G. */ +static int secp256k1_ec_commit(const secp256k1_context* ctx, secp256k1_pubkey *commitment, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) { + unsigned char tweak[32]; + + *commitment = *pubkey; + if (!secp256k1_ec_commit_tweak(ctx, tweak, commitment, data, data_size)) { + return 0; + } + return secp256k1_ec_pubkey_tweak_add(ctx, commitment, tweak); +} + +/* Compute the seckey of an ec commitment from the original secret key of the pubkey as seckey + + * hash(pubkey, data). */ +static int secp256k1_ec_commit_seckey(const secp256k1_context* ctx, unsigned char *seckey, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) { + unsigned char tweak[32]; + secp256k1_pubkey pubkey_tmp; + + if (pubkey == NULL) { + /* Compute pubkey from seckey if not provided */ + int overflow; + secp256k1_scalar x; + secp256k1_gej pj; + secp256k1_ge p; + + secp256k1_scalar_set_b32(&x, seckey, &overflow); + if (overflow != 0) { + return 0; + } + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pj, &x); + secp256k1_ge_set_gej(&p, &pj); + secp256k1_pubkey_save(&pubkey_tmp, &p); + pubkey = &pubkey_tmp; + } + + if (!secp256k1_ec_commit_tweak(ctx, tweak, pubkey, data, data_size)) { + return 0; + } + return secp256k1_ec_privkey_tweak_add(ctx, seckey, tweak); +} + +/* Verify an ec commitment as pubkey + hash(pubkey, data)*G ?= commitment. */ +static int secp256k1_ec_commit_verify(const secp256k1_context* ctx, const secp256k1_pubkey *commitment, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) { + secp256k1_gej pj; + secp256k1_ge p; + secp256k1_pubkey commitment_tmp; + + if (!secp256k1_ec_commit(ctx, &commitment_tmp, pubkey, data, data_size)) { + return 0; + } + + /* Return commitment == commitment_tmp */ + secp256k1_pubkey_load(ctx, &p, &commitment_tmp); + secp256k1_gej_set_ge(&pj, &p); + secp256k1_pubkey_load(ctx, &p, commitment); + secp256k1_ge_neg(&p, &p); + secp256k1_gej_add_ge_var(&pj, &pj, &p, NULL); + return secp256k1_gej_is_infinity(&pj); +} + +static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL; + +static void secp256k1_s2c_opening_init(secp256k1_s2c_opening *opening) { + opening->magic = s2c_opening_magic; + opening->nonce_is_negated = 0; +} + +static int secp256k1_s2c_commit_is_init(const secp256k1_s2c_opening *opening) { + return opening->magic == s2c_opening_magic; +} + +/* s2c_opening is serialized as 33 bytes containing the compressed original pubnonce. In addition to + * holding the EVEN or ODD tag, the first byte has the third bit set to 1 if the nonce was negated. + * The remaining bits in the first byte are 0. */ +int secp256k1_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_s2c_opening* opening, const unsigned char *input33) { + unsigned char pk_ser[33]; + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(opening != NULL); + ARG_CHECK(input33 != NULL); + + secp256k1_s2c_opening_init(opening); + /* Return 0 if unknown bits are set */ + if ((input33[0] & ~0x06) != 0) { + return 0; + } + /* Read nonce_is_negated bit */ + opening->nonce_is_negated = input33[0] & (1 << 2); + memcpy(pk_ser, input33, sizeof(pk_ser)); + /* Unset nonce_is_negated bit to allow parsing the public key */ + pk_ser[0] &= ~(1 << 2); + return secp256k1_ec_pubkey_parse(ctx, &opening->original_pubnonce, &pk_ser[0], 33); +} + +int secp256k1_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output33, const secp256k1_s2c_opening* opening) { + size_t outputlen = 33; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output33 != NULL); + ARG_CHECK(opening != NULL); + ARG_CHECK(secp256k1_s2c_commit_is_init(opening)); + + if (!secp256k1_ec_pubkey_serialize(ctx, &output33[0], &outputlen, &opening->original_pubnonce, SECP256K1_EC_COMPRESSED)) { + return 0; + } + /* Verify that ec_pubkey_serialize only sets the first two bits of the + * first byte, otherwise this function doesn't make any sense */ + VERIFY_CHECK(output33[0] == 0x02 || output33[0] == 0x03); + if (opening->nonce_is_negated) { + /* Set nonce_is_negated bit */ + output33[0] |= (1 << 2); + } + return 1; +} + #ifdef ENABLE_MODULE_ECDH # include "modules/ecdh/main_impl.h" #endif @@ -712,3 +889,7 @@ int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey * #ifdef ENABLE_MODULE_RECOVERY # include "modules/recovery/main_impl.h" #endif + +#ifdef ENABLE_MODULE_ECDSA_SIGN_TO_CONTRACT +# include "modules/ecdsa_sign_to_contract/main_impl.h" +#endif diff --git a/src/tests.c b/src/tests.c index 2f2cb71539..0e07e86cb5 100644 --- a/src/tests.c +++ b/src/tests.c @@ -2365,6 +2365,85 @@ void run_ec_combine(void) { } } +int test_ec_commit_seckey(unsigned char *seckey, secp256k1_pubkey *commitment) { + /* Return if seckey is the discrete log of commitment */ + secp256k1_pubkey pubkey_tmp; + return secp256k1_ec_pubkey_create(ctx, &pubkey_tmp, seckey) == 1 + && memcmp(&pubkey_tmp, commitment, sizeof(pubkey_tmp)) == 0; +} + +void test_ec_commit(void) { + unsigned char seckey[32]; + secp256k1_pubkey pubkey; + secp256k1_pubkey commitment; + unsigned char data[32]; + + /* Create random keypair and data */ + secp256k1_rand256(seckey); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey)); + secp256k1_rand256_test(data); + + /* Commit to data and verify */ + CHECK(secp256k1_ec_commit(ctx, &commitment, &pubkey, data, 32)); + CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 32)); + CHECK(secp256k1_ec_commit_seckey(ctx, seckey, &pubkey, data, 32)); + CHECK(test_ec_commit_seckey(seckey, &commitment) == 1); + + /* Check that verification fails with different data */ + CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 31) == 0); +} + +void test_ec_commit_api(void) { + unsigned char seckey[32]; + secp256k1_pubkey pubkey; + secp256k1_pubkey commitment; + unsigned char data[32]; + + memset(data, 23, sizeof(data)); + + /* Create random keypair */ + secp256k1_rand256(seckey); + CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey)); + + CHECK(secp256k1_ec_commit(ctx, &commitment, &pubkey, data, 1) == 1); + /* The same pubkey can be both input and output of the function */ + { + secp256k1_pubkey pubkey_tmp = pubkey; + CHECK(secp256k1_ec_commit(ctx, &pubkey_tmp, &pubkey_tmp, data, 1) == 1); + CHECK(memcmp(commitment.data, pubkey_tmp.data, sizeof(commitment.data)) == 0); + } + + /* If the pubkey is not provided it will be computed from seckey */ + CHECK(secp256k1_ec_commit_seckey(ctx, seckey, NULL, data, 1) == 1); + CHECK(test_ec_commit_seckey(seckey, &commitment) == 1); + /* pubkey is not provided but seckey overflows */ + { + unsigned char overflowed_seckey[32]; + memset(overflowed_seckey, 0xFF, sizeof(overflowed_seckey)); + CHECK(secp256k1_ec_commit_seckey(ctx, overflowed_seckey, NULL, data, 1) == 0); + } + + CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 1) == 1); + + /* Commitment to 0-len data should fail */ + CHECK(secp256k1_ec_commit(ctx, &commitment, &pubkey, data, 0) == 0); + CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 0) == 0); + CHECK(memcmp(&pubkey.data, &commitment.data, sizeof(pubkey.data)) == 0); + { + unsigned char seckey_tmp[32]; + memcpy(seckey_tmp, seckey, 32); + CHECK(secp256k1_ec_commit_seckey(ctx, seckey_tmp, &pubkey, data, 0) == 0); + } +} + +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; @@ -3076,7 +3155,7 @@ void test_ecmult_multi_batching(void) { data.pt = pt; secp256k1_gej_neg(&r2, &r2); - /* Test with empty scratch space. It should compute the correct result using + /* Test with empty scratch space. It should compute the correct result using * ecmult_mult_simple algorithm which doesn't require a scratch space. */ scratch = secp256k1_scratch_create(&ctx->error_callback, 0); CHECK(secp256k1_ecmult_multi_var(&ctx->error_callback, &ctx->ecmult_ctx, scratch, &r, &scG, ecmult_multi_callback, &data, n_points)); @@ -4104,6 +4183,67 @@ void run_eckey_edge_case_test(void) { secp256k1_context_set_illegal_callback(ctx, NULL, NULL); } +void run_s2c_opening_test(void) { + int i = 0; + unsigned char output[33]; + /* First byte 0x06 means that nonce_is_negated and EVEN tag for the + * following compressed pubkey (which is valid). */ + unsigned char input[33] = { + 0x06, 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_s2c_opening opening; + size_t ecount = 0; + + secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount); + + /* Uninitialized opening can't be serialized. Actually testing that would be + * undefined behavior. Therefore we simulate it by setting the opening to 0. */ + memset(&opening, 0, sizeof(opening)); + CHECK(ecount == 0); + CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 0); + CHECK(ecount == 1); + + /* First parsing, then serializing works */ + CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input) == 1); + CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1); + CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input) == 1); + + { + /* Invalid pubkey makes parsing fail */ + unsigned char input_tmp[33]; + memcpy(input_tmp, input, sizeof(input_tmp)); + /* Pubkey oddness tag is invalid */ + input_tmp[0] = 0; + CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input_tmp) == 0); + /* nonce_is_negated bit is set but pubkey oddness tag is invalid */ + input_tmp[0] = 5; + CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input_tmp) == 0); + /* Unknown bit is set */ + input_tmp[0] = 8; + CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input_tmp) == 0); + } + + /* Try parsing and serializing a bunch of openings */ + do { + /* This is expected to fail in about 50% of iterations because the + * points' x-coordinates are uniformly random */ + if (secp256k1_s2c_opening_parse(ctx, &opening, input) == 1) { + CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1); + CHECK(memcmp(output, input, sizeof(output)) == 0); + } + secp256k1_rand256(&input[1]); + /* Set pubkey oddness tag to first bit of input[1] */ + input[0] = (input[1] & 1) + 2; + /* Set nonce_is_negated bit to input[1]'s 3rd bit */ + input[0] |= (input[1] & (1 << 2)); + i++; + } while(i < count); +} + void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) { secp256k1_scalar nonce; do { @@ -5166,6 +5306,10 @@ void run_ecdsa_openssl(void) { # include "modules/recovery/tests_impl.h" #endif +#ifdef ENABLE_MODULE_ECDSA_SIGN_TO_CONTRACT +# include "modules/ecdsa_sign_to_contract/tests_impl.h" +#endif + void run_memczero_test(void) { unsigned char buf1[6] = {1, 2, 3, 4, 5, 6}; unsigned char buf2[sizeof(buf1)]; @@ -5282,6 +5426,7 @@ int main(int argc, char **argv) { run_ecmult_const_tests(); run_ecmult_multi_tests(); run_ec_combine(); + run_ec_commit(); /* endomorphism tests */ #ifdef USE_ENDOMORPHISM @@ -5294,6 +5439,8 @@ int main(int argc, char **argv) { /* EC key edge cases */ run_eckey_edge_case_test(); + run_s2c_opening_test(); + #ifdef ENABLE_MODULE_ECDH /* ecdh tests */ run_ecdh_tests(); @@ -5314,6 +5461,11 @@ int main(int argc, char **argv) { run_recovery_tests(); #endif +#ifdef ENABLE_MODULE_ECDSA_SIGN_TO_CONTRACT + /* ECDSA sign to contract */ + run_ecdsa_sign_to_contract_tests(); +#endif + /* util tests */ run_memczero_test();