From d202a6bf4966121ef69f91846ce6fbf5346c6df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Vejpustek?= Date: Mon, 3 Mar 2025 20:54:18 +0100 Subject: [PATCH] feat(crypto): add wrappers for anti-exfil functions --- crypto/Makefile | 3 +- crypto/tests/test_check.c | 74 ++++++++++++++++++++ crypto/zkp_ecdsa.c | 141 +++++++++++++++++++++++++++++++++++--- crypto/zkp_ecdsa.h | 10 +++ 4 files changed, 217 insertions(+), 11 deletions(-) diff --git a/crypto/Makefile b/crypto/Makefile index 34bab1175d..a16d865df6 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -70,7 +70,8 @@ ZKP_CFLAGS = \ -DENABLE_MODULE_RECOVERY \ -DENABLE_MODULE_SCHNORRSIG \ -DENABLE_MODULE_EXTRAKEYS \ - -DENABLE_MODULE_ECDH + -DENABLE_MODULE_ECDH \ + -DENABLE_MODULE_ECDSA_S2C ZKP_PATH = ../vendor/secp256k1-zkp # this is specific for 64-bit builds CFLAGS += -DSECP256K1_CONTEXT_SIZE=208 diff --git a/crypto/tests/test_check.c b/crypto/tests/test_check.c index 3c6226277d..6f94940bfe 100644 --- a/crypto/tests/test_check.c +++ b/crypto/tests/test_check.c @@ -4172,6 +4172,78 @@ START_TEST(test_zkp_ecdsa_sign_digest_recoverable_deterministic) { } END_TEST +START_TEST(test_zkp_ecdsa_anti_exfil_commit_nonce) { + static const struct { + const char *priv_key; + const char *digest; + const char *entropy; + const char *expected_nonce_commitment; + } tests[] = { + {"8521fa4b1f08d39b91e2fcc6a342b2395fbada5146c494be485b6d5ff8002da0", + "0d841da7d1d6c5edb90864118f52c865f2ac56deb13a41229ef7cbd2951593fd", + "88e381faa8f2297cd6295b19e8a3563291e4c86a873f9a758b0cd6629a102eac", + "032b756f0b1937eef6d269b35781c7a1a5435e00e3c06245ef2162d51c15b2992c"}}; + + const ecdsa_curve *curve = &secp256k1; + uint8_t priv_key[32] = {0}; + uint8_t digest[32] = {0}; + uint8_t entropy[32] = {0}; + uint8_t nonce_commitment[64] = {0}; + uint8_t expected_nonce_commitment[64] = {0}; + int res = 0; + + for (size_t i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + memcpy(priv_key, fromhex(tests[i].priv_key), 32); + memcpy(digest, fromhex(tests[i].digest), 32); + memcpy(entropy, fromhex(tests[i].entropy), 32); + memcpy(expected_nonce_commitment, + fromhex(tests[i].expected_nonce_commitment), 33); + + res = zkp_ecdsa_anti_exfil_commit_nonce(curve, priv_key, digest, entropy, + nonce_commitment); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq(expected_nonce_commitment, nonce_commitment, 33); + } +} +END_TEST + +START_TEST(test_zkp_ecdsa_anti_exfil_sign_digest) { + static const struct { + const char *priv_key; + const char *digest; + const char *entropy_commitment; + const char *expected_sig; + } tests[] = { + {"8521fa4b1f08d39b91e2fcc6a342b2395fbada5146c494be485b6d5ff8002da0", + "0d841da7d1d6c5edb90864118f52c865f2ac56deb13a41229ef7cbd2951593fd", + // entropy: + // 88e381faa8f2297cd6295b19e8a3563291e4c86a873f9a758b0cd6629a102eac + "bc07f96c351484fecb4c1b1820070c446f4f5398530e85ed2955041bdef54abf", + "e4450fb09d21528a3b75e34e1a7d0872f8f02f243087f69a472029c6622ed9790b1855b" + "91149ea55d5336b0967d25c73890b126220dd4ad885b69b65028178da"}}; + + const ecdsa_curve *curve = &secp256k1; + uint8_t priv_key[32] = {0}; + uint8_t digest[32] = {0}; + uint8_t entropy_commitment[32] = {0}; + uint8_t expected_sig[64] = {0}; + uint8_t computed_sig[64] = {0}; + int res = 0; + + for (size_t i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + memcpy(priv_key, fromhex(tests[i].priv_key), 32); + memcpy(digest, fromhex(tests[i].digest), 32); + memcpy(entropy_commitment, fromhex(tests[i].entropy_commitment), 32); + memcpy(expected_sig, fromhex(tests[i].expected_sig), 64); + + res = zkp_ecdsa_anti_exfil_sign_digest(curve, priv_key, digest, + entropy_commitment, computed_sig); + ck_assert_int_eq(res, 0); + ck_assert_mem_eq(expected_sig, computed_sig, 64); + } +} +END_TEST + // test vectors from // http://www.inconteam.com/software-development/41-encryption/55-aes-test-vectors START_TEST(test_aes) { @@ -11524,6 +11596,8 @@ Suite *test_suite(void) { tcase_add_test(tc, test_tc_ecdsa_sign_digest_recoverable_deterministic); tcase_add_test(tc, test_zkp_ecdsa_sign_digest_recoverable_deterministic); #endif + tcase_add_test(tc, test_zkp_ecdsa_anti_exfil_commit_nonce); + tcase_add_test(tc, test_zkp_ecdsa_anti_exfil_sign_digest); suite_add_tcase(s, tc); tc = tcase_create("rfc6979"); diff --git a/crypto/zkp_ecdsa.c b/crypto/zkp_ecdsa.c index 083c202f4c..4e148f8da0 100644 --- a/crypto/zkp_ecdsa.c +++ b/crypto/zkp_ecdsa.c @@ -28,9 +28,11 @@ #include "memzero.h" #include "secp256k1.h" #include "zkp_context.h" +#include "zkp_ecdsa.h" #include "vendor/secp256k1-zkp/include/secp256k1.h" #include "vendor/secp256k1-zkp/include/secp256k1_ecdh.h" +#include "vendor/secp256k1-zkp/include/secp256k1_ecdsa_s2c.h" #include "vendor/secp256k1-zkp/include/secp256k1_recovery.h" #include "zkp_ecdsa.h" @@ -260,6 +262,125 @@ int zkp_ecdsa_sign_digest_recoverable( return result; } +// anti-exfil ECDSA signing +// curve has to be &secp256k1 +// private_key_bytes has 32 bytes +// digest has 32 bytes +// entropy_commitment has 32 bytes +// signature_bytes has 64 bytes +// returns 0 on success +int zkp_ecdsa_anti_exfil_sign_digest(const ecdsa_curve *curve, + const uint8_t *private_key_bytes, + const uint8_t *digest, + const uint8_t *entropy, + uint8_t *signature_bytes) { + int result = 0; + secp256k1_context *context_writable = NULL; + + if (curve != &secp256k1) { + result = 1; + goto cleanup; + } + + if (is_zero_digest(digest)) { + // The probability of the digest being all-zero by chance is + // infinitesimal, so this is most likely an indication of a bug. + // Furthermore, the signature has no value, because in this case it can be + // easily forged for any public key, see zkp_ecdsa_verify_digest(). + result = 1; + goto cleanup; + } + + context_writable = zkp_context_acquire_writable(); + if (context_writable == NULL) { + result = 1; + goto cleanup; + } + + if (secp256k1_context_writable_randomize(context_writable) != 0) { + result = 1; + goto cleanup; + } + + secp256k1_ecdsa_signature signature = {0}; + if (secp256k1_anti_exfil_sign(context_writable, &signature, digest, + private_key_bytes, entropy) != 1) { + result = 1; + goto cleanup; + } + + const secp256k1_context *context_read_only = zkp_context_get_read_only(); + if (secp256k1_ecdsa_signature_serialize_compact( + context_read_only, signature_bytes, &signature) != 1) { + result = 1; + goto cleanup; + } + +cleanup: + if (context_writable) { + zkp_context_release_writable(); + context_writable = NULL; + } + + return result; +} + +// anti-exfil ECDSA commit +// curve has to be &secp256k1 +// private_key_bytes has 32 bytes +// digest has 32 bytes +// entropy_commitment has 32 bytes +// public_nonce_bytes has 33 bytes +// returns 0 on success +int zkp_ecdsa_anti_exfil_commit_nonce(const ecdsa_curve *curve, + const uint8_t *private_key_bytes, + const uint8_t *digest, + const uint8_t *entropy_commitment, + uint8_t *nonce_commitment) { + int result = 0; + secp256k1_context *context_writable = NULL; + + assert(curve == &secp256k1); + if (curve != &secp256k1) { + result = 1; + goto cleanup; + } + + context_writable = zkp_context_acquire_writable(); + if (context_writable == NULL) { + result = 1; + goto cleanup; + } + + if (secp256k1_context_writable_randomize(context_writable) != 0) { + result = 1; + goto cleanup; + } + + secp256k1_ecdsa_s2c_opening s2c_opening = {0}; + if (secp256k1_ecdsa_anti_exfil_signer_commit(context_writable, &s2c_opening, + digest, private_key_bytes, + entropy_commitment) != 1) { + result = 1; + goto cleanup; + } + + const secp256k1_context *context_read_only = zkp_context_get_read_only(); + if (secp256k1_ecdsa_s2c_opening_serialize(context_read_only, nonce_commitment, + &s2c_opening) != 1) { + result = 1; + goto cleanup; + } + +cleanup: + if (context_writable) { + zkp_context_release_writable(); + context_writable = NULL; + } + + return result; +} + // ECDSA public key recovery // public_key_bytes has 65 bytes // signature_bytes has 64 bytes @@ -338,12 +459,12 @@ int zkp_ecdsa_verify_digest(const ecdsa_curve *curve, if (result == 0) { if (is_zero_digest(digest)) { - // The digest was all-zero. The probability of this happening by chance is - // infinitesimal, but it could be induced by a fault injection. In this - // case the signature (r,s) can be forged by taking r := (t * Q).x mod n - // and s := r * t^-1 mod n for any t in [1, n-1]. We fail verification, - // because there is no guarantee that the signature was created by the - // owner of the private key. + // The digest was all-zero. The probability of this happening by + // chance is infinitesimal, but it could be induced by a fault + // injection. In this case the signature (r,s) can be forged by taking + // r := (t * Q).x mod n and s := r * t^-1 mod n for any t in [1, n-1]. + // We fail verification, because there is no guarantee that the + // signature was created by the owner of the private key. result = 3; } } @@ -370,8 +491,8 @@ int zkp_ecdsa_verify_digest(const ecdsa_curve *curve, if (result == 0) { (void)secp256k1_ecdsa_signature_normalize( context_read_only, &signature, - &signature); // The return value inidicates whether the signature was - // already normalized + &signature); // The return value inidicates whether the signature + // was already normalized if (secp256k1_ecdsa_verify(context_read_only, &signature, digest, &public_key) != 1) { @@ -515,8 +636,8 @@ ecdsa_tweak_pubkey_result zkp_ecdsa_tweak_pubkey( if (secp256k1_ec_pubkey_tweak_add(context_writable, &public_key, tweak_bytes) != 1) { - // The tweak is not less than the group order, or the resulting public key - // is the point at infinity. + // The tweak is not less than the group order, or the resulting public + // key is the point at infinity. result = ECDSA_TWEAK_PUBKEY_INVALID_TWEAK_OR_RESULT_ERR; goto end; } diff --git a/crypto/zkp_ecdsa.h b/crypto/zkp_ecdsa.h index e6e203a74f..941d5df8ca 100644 --- a/crypto/zkp_ecdsa.h +++ b/crypto/zkp_ecdsa.h @@ -32,4 +32,14 @@ int zkp_ecdh_multiply(const ecdsa_curve *curve, const uint8_t *priv_key, ecdsa_tweak_pubkey_result zkp_ecdsa_tweak_pubkey( const ecdsa_curve *curve, const uint8_t *public_key_bytes, const uint8_t *tweak_bytes, uint8_t *tweaked_public_key_bytes); +int zkp_ecdsa_anti_exfil_sign_digest(const ecdsa_curve *curve, + const uint8_t *private_key_bytes, + const uint8_t *digest, + const uint8_t *entropy, + uint8_t *signature_bytes); +int zkp_ecdsa_anti_exfil_commit_nonce(const ecdsa_curve *curve, + const uint8_t *private_key_bytes, + const uint8_t *digest, + const uint8_t *entropy_commitment, + uint8_t *nonce_commitment); #endif