From bb08dfa2134a00fbc91b38c8cdb0be868a523c1c Mon Sep 17 00:00:00 2001 From: kevaundray Date: Tue, 9 May 2023 14:36:47 +0100 Subject: [PATCH] Add ECDSA test for ACIR and fix (#435) --------- Co-authored-by: zac-williamson --- .../dsl/acir_format/acir_format.test.cpp | 1 + .../dsl/acir_format/ecdsa_secp256k1.cpp | 25 ++- .../dsl/acir_format/ecdsa_secp256k1.test.cpp | 144 ++++++++++++++++++ .../stdlib/encryption/ecdsa/ecdsa.hpp | 5 + .../stdlib/encryption/ecdsa/ecdsa.test.cpp | 94 ++++++++++++ .../stdlib/encryption/ecdsa/ecdsa_impl.hpp | 92 ++++++++++- 6 files changed, 352 insertions(+), 9 deletions(-) create mode 100644 cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index 07305d0cbd9..48debb4db98 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -3,6 +3,7 @@ #include #include #include "barretenberg/common/streams.hpp" +#include "ecdsa_secp256k1.hpp" TEST(acir_format, test_logic_gate_from_noir_circuit) { diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp index 21abc4e8e24..d4ec3e235d6 100644 --- a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp @@ -38,6 +38,8 @@ crypto::ecdsa::signature ecdsa_convert_signature(Composer& composer, std::vector signature_cr.s[i - 32] = fr_bytes.back(); } + signature_cr.v = 27; + return signature_cr; } @@ -104,17 +106,24 @@ void create_ecdsa_verify_constraints(Composer& composer, const EcdsaSecp256k1Con pub_key_x_fq.assert_is_in_field(); pub_key_y_fq.assert_is_in_field(); - secp256k1_ct::g1_bigfr_ct public_key = secp256k1_ct::g1_bigfr_ct(pub_key_x_fq, pub_key_y_fq); + for (size_t i = 0; i < 32; ++i) { + sig.r[i].assert_equal(field_ct::from_witness_index(&composer, input.signature[i])); + sig.s[i].assert_equal(field_ct::from_witness_index(&composer, input.signature[i + 32])); + pub_key_x_byte_arr[i].assert_equal(field_ct::from_witness_index(&composer, input.pub_x_indices[i])); + pub_key_y_byte_arr[i].assert_equal(field_ct::from_witness_index(&composer, input.pub_y_indices[i])); + } + for (size_t i = 0; i < input.message.size(); ++i) { + message[i].assert_equal(field_ct::from_witness_index(&composer, input.message[i])); + } - bool_ct signature_result = stdlib::ecdsa::verify_signature(message, public_key, sig); - + bool_ct signature_result = + stdlib::ecdsa::verify_signature_noassert(message, public_key, sig); bool_ct signature_result_normalized = signature_result.normalize(); - composer.assert_equal(signature_result_normalized.witness_index, input.result); } diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp new file mode 100644 index 00000000000..0724b15f911 --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -0,0 +1,144 @@ +#include "acir_format.hpp" +#include "ecdsa_secp256k1.hpp" +#include "barretenberg/plonk/proof_system/types/proof.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" +#include "barretenberg/crypto/ecdsa/ecdsa.hpp" + +#include +#include + +using curve = proof_system::plonk::stdlib::secp256k1; + +size_t generate_ecdsa_constraint(acir_format::EcdsaSecp256k1Constraint& ecdsa_constraint, + std::vector& witness_values) +{ + std::string message_string = "Instructions unclear, ask again later."; + + crypto::ecdsa::key_pair account; + account.private_key = curve::fr::random_element(); + account.public_key = curve::g1::one * account.private_key; + + crypto::ecdsa::signature signature = + crypto::ecdsa::construct_signature(message_string, account); + + uint256_t pub_x_value = account.public_key.x; + uint256_t pub_y_value = account.public_key.y; + + std::vector message_in; + std::vector pub_x_indices_in; + std::vector pub_y_indices_in; + std::vector signature_in; + size_t offset = 1; + for (size_t i = 0; i < message_string.size(); ++i) { + message_in.emplace_back(i + offset); + const auto byte = static_cast(message_string[i]); + witness_values.emplace_back(byte); + } + offset += message_in.size(); + + for (size_t i = 0; i < 32; ++i) { + pub_x_indices_in.emplace_back(i + offset); + witness_values.emplace_back(pub_x_value.slice(248 - i * 8, 256 - i * 8)); + } + offset += pub_x_indices_in.size(); + for (size_t i = 0; i < 32; ++i) { + pub_y_indices_in.emplace_back(i + offset); + witness_values.emplace_back(pub_y_value.slice(248 - i * 8, 256 - i * 8)); + } + offset += pub_y_indices_in.size(); + for (size_t i = 0; i < 32; ++i) { + signature_in.emplace_back(i + offset); + witness_values.emplace_back(signature.r[i]); + } + offset += signature.r.size(); + for (size_t i = 0; i < 32; ++i) { + signature_in.emplace_back(i + offset); + witness_values.emplace_back(signature.s[i]); + } + offset += signature.s.size(); + + witness_values.emplace_back(1); + const auto result_in = static_cast(offset); + offset += 1; + witness_values.emplace_back(1); + + ecdsa_constraint = acir_format::EcdsaSecp256k1Constraint{ + .message = message_in, + .pub_x_indices = pub_x_indices_in, + .pub_y_indices = pub_y_indices_in, + .result = result_in, + .signature = signature_in, + }; + return offset; +} + +TEST(ECDSASecp256k1, TestECDSAConstraintSucceed) +{ + acir_format::EcdsaSecp256k1Constraint ecdsa_constraint; + std::vector witness_values; + size_t num_variables = generate_ecdsa_constraint(ecdsa_constraint, witness_values); + acir_format::acir_format constraint_system{ + .varnum = static_cast(num_variables), + .public_inputs = {}, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = { ecdsa_constraint }, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .compute_merkle_root_constraints = {}, + .constraints = {}, + }; + + auto composer = acir_format::create_circuit_with_witness(constraint_system, witness_values); + + EXPECT_EQ(composer.get_variable(ecdsa_constraint.result), 1); + auto prover = composer.create_prover(); + + auto proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + EXPECT_EQ(verifier.verify_proof(proof), true); +} + +TEST(ECDSASecp256k1, TestECDSAConstraintFail) +{ + acir_format::EcdsaSecp256k1Constraint ecdsa_constraint; + std::vector witness_values; + size_t num_variables = generate_ecdsa_constraint(ecdsa_constraint, witness_values); + + // set result value to be false + witness_values[witness_values.size() - 1] = 0; + + // tamper with signature + witness_values[witness_values.size() - 20] += 1; + + acir_format::acir_format constraint_system{ + .varnum = static_cast(num_variables), + .public_inputs = {}, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = { ecdsa_constraint }, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .compute_merkle_root_constraints = {}, + .constraints = {}, + }; + + auto composer = acir_format::create_circuit_with_witness(constraint_system, witness_values); + + EXPECT_EQ(composer.get_variable(ecdsa_constraint.result), 0); + auto prover = composer.create_prover(); + + auto proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + EXPECT_EQ(verifier.verify_proof(proof), true); +} diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp index ea5f890fd5b..59b97b6bbee 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp @@ -20,6 +20,11 @@ bool_t verify_signature(const stdlib::byte_array& message, const G1& public_key, const signature& sig); +template +bool_t verify_signature_noassert(const stdlib::byte_array& message, + const G1& public_key, + const signature& sig); + template static signature from_witness(Composer* ctx, const crypto::ecdsa::signature& input) { diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp index 405d28f33c8..722e0e16a40 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp @@ -57,4 +57,98 @@ TEST(stdlib_ecdsa, verify_signature) bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); } + +TEST(stdlib_ecdsa, verify_signature_noassert_succeed) +{ + Composer composer = Composer(); + + // whaaablaghaaglerijgeriij + std::string message_string = "Instructions unclear, ask again later."; + + crypto::ecdsa::key_pair account; + account.private_key = curve::fr::random_element(); + account.public_key = curve::g1::one * account.private_key; + + crypto::ecdsa::signature signature = + crypto::ecdsa::construct_signature(message_string, account); + + bool first_result = crypto::ecdsa::verify_signature( + message_string, account.public_key, signature); + EXPECT_EQ(first_result, true); + + curve::g1_bigfr_ct public_key = curve::g1_bigfr_ct::from_witness(&composer, account.public_key); + + std::vector rr(signature.r.begin(), signature.r.end()); + std::vector ss(signature.s.begin(), signature.s.end()); + uint8_t vv = signature.v; + + stdlib::ecdsa::signature sig{ + curve::byte_array_ct(&composer, rr), + curve::byte_array_ct(&composer, ss), + stdlib::uint8(&composer, vv), + }; + + curve::byte_array_ct message(&composer, message_string); + + curve::bool_ct signature_result = + stdlib::ecdsa::verify_signature_noassert( + message, public_key, sig); + + EXPECT_EQ(signature_result.get_value(), true); + + std::cerr << "composer gates = " << composer.get_num_gates() << std::endl; + benchmark_info("UltraComposer", "ECDSA", "Signature Verification Test", "Gate Count", composer.get_num_gates()); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} + +TEST(stdlib_ecdsa, verify_signature_noassert_fail) +{ + Composer composer = Composer(); + + // whaaablaghaaglerijgeriij + std::string message_string = "Instructions unclear, ask again later."; + + crypto::ecdsa::key_pair account; + account.private_key = curve::fr::random_element(); + account.public_key = curve::g1::one * account.private_key; + + crypto::ecdsa::signature signature = + crypto::ecdsa::construct_signature(message_string, account); + + // tamper w. signature to make fail + signature.r[0] += 1; + + bool first_result = crypto::ecdsa::verify_signature( + message_string, account.public_key, signature); + EXPECT_EQ(first_result, false); + + curve::g1_bigfr_ct public_key = curve::g1_bigfr_ct::from_witness(&composer, account.public_key); + + std::vector rr(signature.r.begin(), signature.r.end()); + std::vector ss(signature.s.begin(), signature.s.end()); + + stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), + curve::byte_array_ct(&composer, ss), + 27 }; + + curve::byte_array_ct message(&composer, message_string); + + curve::bool_ct signature_result = + stdlib::ecdsa::verify_signature_noassert( + message, public_key, sig); + + EXPECT_EQ(signature_result.get_value(), false); + + std::cerr << "composer gates = " << composer.get_num_gates() << std::endl; + benchmark_info("UltraComposer", "ECDSA", "Signature Verification Test", "Gate Count", composer.get_num_gates()); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} } // namespace test_stdlib_ecdsa diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp index 4c23d3d66c9..76eab940c19 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp @@ -6,6 +6,19 @@ namespace proof_system::plonk { namespace stdlib { namespace ecdsa { +/** + * @brief Verify ECDSA signature. Produces unsatisfiable constraints if signature fails + * + * @tparam Composer + * @tparam Curve + * @tparam Fq + * @tparam Fr + * @tparam G1 + * @param message + * @param public_key + * @param sig + * @return bool_t + */ template bool_t verify_signature(const stdlib::byte_array& message, const G1& public_key, @@ -40,6 +53,7 @@ bool_t verify_signature(const stdlib::byte_array& message, * [2] EIP-155: https://eips.ethereum.org/EIPS/eip-155 * */ + // Note: This check is also present in the _noassert variation of this method. field_t(sig.v).assert_is_in_set({ field_t(27), field_t(28) }, "signature is non-standard"); @@ -92,10 +106,86 @@ bool_t verify_signature(const stdlib::byte_array& message, result_mod_r.binary_basis_limbs[2].element.assert_equal(r.binary_basis_limbs[2].element); result_mod_r.binary_basis_limbs[3].element.assert_equal(r.binary_basis_limbs[3].element); result_mod_r.prime_basis_limb.assert_equal(r.prime_basis_limb); - return bool_t(ctx, true); } +/** + * @brief Verify ECDSA signature. Returns 0 if signature fails (i.e. does not produce unsatisfiable constraints) + * + * @tparam Composer + * @tparam Curve + * @tparam Fq + * @tparam Fr + * @tparam G1 + * @param message + * @param public_key + * @param sig + * @return bool_t + */ +template +bool_t verify_signature_noassert(const stdlib::byte_array& message, + const G1& public_key, + const signature& sig) +{ + Composer* ctx = message.get_context() ? message.get_context() : public_key.x.context; + + stdlib::byte_array hashed_message = + static_cast>(stdlib::sha256(message)); + + Fr z(hashed_message); + z.assert_is_in_field(); + + Fr r(sig.r); + // force r to be < secp256k1 group modulus, so we can compare with `result_mod_r` below + r.assert_is_in_field(); + + Fr s(sig.s); + + // r and s should not be zero + r.assert_is_not_equal(Fr::zero()); + s.assert_is_not_equal(Fr::zero()); + + Fr u1 = z / s; + Fr u2 = r / s; + + G1 result; + if constexpr (Composer::type == ComposerType::PLOOKUP) { + ASSERT(Curve::type == proof_system::CurveType::SECP256K1); + public_key.validate_on_curve(); + result = G1::secp256k1_ecdsa_mul(public_key, u1, u2); + } else { + result = G1::batch_mul({ G1::one(ctx), public_key }, { u1, u2 }); + } + result.x.self_reduce(); + + // transfer Fq value x to an Fr element and reduce mod r + Fr result_mod_r(ctx, 0); + result_mod_r.binary_basis_limbs[0].element = result.x.binary_basis_limbs[0].element; + result_mod_r.binary_basis_limbs[1].element = result.x.binary_basis_limbs[1].element; + result_mod_r.binary_basis_limbs[2].element = result.x.binary_basis_limbs[2].element; + result_mod_r.binary_basis_limbs[3].element = result.x.binary_basis_limbs[3].element; + result_mod_r.binary_basis_limbs[0].maximum_value = result.x.binary_basis_limbs[0].maximum_value; + result_mod_r.binary_basis_limbs[1].maximum_value = result.x.binary_basis_limbs[1].maximum_value; + result_mod_r.binary_basis_limbs[2].maximum_value = result.x.binary_basis_limbs[2].maximum_value; + result_mod_r.binary_basis_limbs[3].maximum_value = result.x.binary_basis_limbs[3].maximum_value; + + result_mod_r.prime_basis_limb = result.x.prime_basis_limb; + + result_mod_r.assert_is_in_field(); + + bool_t output(ctx, true); + output &= result_mod_r.binary_basis_limbs[0].element == (r.binary_basis_limbs[0].element); + output &= result_mod_r.binary_basis_limbs[1].element == (r.binary_basis_limbs[1].element); + output &= result_mod_r.binary_basis_limbs[2].element == (r.binary_basis_limbs[2].element); + output &= result_mod_r.binary_basis_limbs[3].element == (r.binary_basis_limbs[3].element); + output &= result_mod_r.prime_basis_limb == (r.prime_basis_limb); + + field_t(sig.v).assert_is_in_set({ field_t(27), field_t(28) }, + "signature is non-standard"); + + return output; +} + } // namespace ecdsa } // namespace stdlib } // namespace proof_system::plonk \ No newline at end of file