diff --git a/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh b/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh index 6a19433e874e..6b75b86bfbec 100755 --- a/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh +++ b/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh @@ -11,7 +11,7 @@ cd .. # - Generate a hash for versioning: sha256sum bb-civc-inputs.tar.gz # - Upload the compressed results: aws s3 cp bb-civc-inputs.tar.gz s3://aztec-ci-artifacts/protocol/bb-civc-inputs-[hash(0:8)].tar.gz # Note: In case of the "Test suite failed to run ... Unexpected token 'with' " error, need to run: docker pull aztecprotocol/build:3.0 -pinned_short_hash="f22d116f" +pinned_short_hash="e5081516" pinned_civc_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-civc-inputs-${pinned_short_hash}.tar.gz" function compress_and_upload { diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 403e43aea0ad..a1e10e5db71e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -11,6 +11,7 @@ #include "barretenberg/common/op_count.hpp" #include "barretenberg/common/throw_or_abort.hpp" #include "barretenberg/dsl/acir_format/civc_recursion_constraints.hpp" +#include "barretenberg/dsl/acir_format/ecdsa_constraints.hpp" #include "barretenberg/dsl/acir_format/honk_recursion_constraint.hpp" #include "barretenberg/dsl/acir_format/pg_recursion_constraint.hpp" #include "barretenberg/dsl/acir_format/proof_surgeon.hpp" @@ -18,6 +19,8 @@ #include "barretenberg/honk/proving_key_inspector.hpp" #include "barretenberg/stdlib/eccvm_verifier/verifier_commitment_key.hpp" #include "barretenberg/stdlib/primitives/curves/grumpkin.hpp" +#include "barretenberg/stdlib/primitives/curves/secp256k1.hpp" +#include "barretenberg/stdlib/primitives/curves/secp256r1.hpp" #include "barretenberg/stdlib/primitives/field/field_conversion.hpp" #include "barretenberg/stdlib/primitives/pairing_points.hpp" #include "barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp" @@ -185,7 +188,7 @@ void build_constraints(Builder& builder, AcirProgram& program, const ProgramMeta // Add ECDSA k1 constraints for (size_t i = 0; i < constraint_system.ecdsa_k1_constraints.size(); ++i) { const auto& constraint = constraint_system.ecdsa_k1_constraints.at(i); - create_ecdsa_k1_verify_constraints(builder, constraint, has_valid_witness_assignments); + create_ecdsa_verify_constraints>(builder, constraint, has_valid_witness_assignments); gate_counter.track_diff(constraint_system.gates_per_opcode, constraint_system.original_opcode_indices.ecdsa_k1_constraints.at(i)); } @@ -193,7 +196,7 @@ void build_constraints(Builder& builder, AcirProgram& program, const ProgramMeta // Add ECDSA r1 constraints for (size_t i = 0; i < constraint_system.ecdsa_r1_constraints.size(); ++i) { const auto& constraint = constraint_system.ecdsa_r1_constraints.at(i); - create_ecdsa_r1_verify_constraints(builder, constraint, has_valid_witness_assignments); + create_ecdsa_verify_constraints>(builder, constraint, has_valid_witness_assignments); gate_counter.track_diff(constraint_system.gates_per_opcode, constraint_system.original_opcode_indices.ecdsa_r1_constraints.at(i)); } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index b6bd76f15dd8..906ec9758fee 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -19,8 +19,7 @@ #include "blake3_constraint.hpp" #include "block_constraint.hpp" #include "ec_operations.hpp" -#include "ecdsa_secp256k1.hpp" -#include "ecdsa_secp256r1.hpp" +#include "ecdsa_constraints.hpp" #include "honk_recursion_constraint.hpp" #include "keccak_constraint.hpp" #include "logic_constraint.hpp" @@ -89,8 +88,8 @@ struct AcirFormat { std::vector range_constraints; std::vector aes128_constraints; std::vector sha256_compression; - std::vector ecdsa_k1_constraints; - std::vector ecdsa_r1_constraints; + std::vector ecdsa_k1_constraints; + std::vector ecdsa_r1_constraints; std::vector blake2s_constraints; std::vector blake3_constraints; std::vector keccak_permutations; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index 97c7437fac31..c97cb61ce74e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -8,7 +8,6 @@ #include "barretenberg/op_queue/ecc_op_queue.hpp" #include "barretenberg/serialize/test_helper.hpp" -#include "ecdsa_secp256k1.hpp" using namespace bb; using namespace bb::crypto; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp index 85fb065ca77a..118b548f35e1 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp @@ -17,6 +17,7 @@ #include "barretenberg/common/container.hpp" #include "barretenberg/common/map.hpp" #include "barretenberg/common/throw_or_abort.hpp" +#include "barretenberg/dsl/acir_format/ecdsa_constraints.hpp" #include "barretenberg/dsl/acir_format/recursion_constraint.hpp" #include "barretenberg/honk/execution_trace/gate_data.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" @@ -651,7 +652,7 @@ void handle_blackbox_func_call(Acir::Opcode::BlackBoxFuncCall const& arg, AcirFo } af.original_opcode_indices.blake3_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { - af.ecdsa_k1_constraints.push_back(EcdsaSecp256k1Constraint{ + af.ecdsa_k1_constraints.push_back(EcdsaConstraint{ .hashed_message = transform::map(*arg.hashed_message, [](auto& e) { return get_witness_from_function_input(e); }), .signature = @@ -665,16 +666,16 @@ void handle_blackbox_func_call(Acir::Opcode::BlackBoxFuncCall const& arg, AcirFo af.constrained_witness.insert(af.ecdsa_k1_constraints.back().result); af.original_opcode_indices.ecdsa_k1_constraints.push_back(opcode_index); } else if constexpr (std::is_same_v) { - af.ecdsa_r1_constraints.push_back(EcdsaSecp256r1Constraint{ + af.ecdsa_r1_constraints.push_back(EcdsaConstraint{ .hashed_message = transform::map(*arg.hashed_message, [](auto& e) { return get_witness_from_function_input(e); }), + .signature = + transform::map(*arg.signature, [](auto& e) { return get_witness_from_function_input(e); }), .pub_x_indices = transform::map(*arg.public_key_x, [](auto& e) { return get_witness_from_function_input(e); }), .pub_y_indices = transform::map(*arg.public_key_y, [](auto& e) { return get_witness_from_function_input(e); }), .result = arg.output.value, - .signature = - transform::map(*arg.signature, [](auto& e) { return get_witness_from_function_input(e); }), }); af.constrained_witness.insert(af.ecdsa_r1_constraints.back().result); af.original_opcode_indices.ecdsa_r1_constraints.push_back(opcode_index); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/avm2_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/avm2_recursion_constraint.test.cpp index 3c4777157d76..b1aab0c34d3d 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/avm2_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/avm2_recursion_constraint.test.cpp @@ -5,6 +5,7 @@ #include "barretenberg/dsl/acir_format/acir_format_mocks.hpp" #include "barretenberg/dsl/acir_format/avm2_recursion_constraint.hpp" #include "barretenberg/dsl/acir_format/proof_surgeon.hpp" +#include "barretenberg/dsl/acir_format/utils.hpp" #include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp" #include "barretenberg/ultra_honk/decider_keys.hpp" #include "barretenberg/ultra_honk/ultra_prover.hpp" @@ -80,23 +81,10 @@ class AcirAvm2RecursionConstraint : public ::testing::Test { const std::vector proof_witnesses = inner_circuit_data.proof; const std::vector public_inputs_witnesses = inner_circuit_data.public_inputs_flat; - // Helper to append some values to the witness vector and return their corresponding indices - auto add_to_witness_and_track_indices = - [&witness](const std::vector& input) -> std::vector { - std::vector indices; - indices.reserve(input.size()); - auto witness_idx = static_cast(witness.size()); - for (const auto& value : input) { - witness.push_back(value); - indices.push_back(witness_idx++); - } - return indices; - }; - RecursionConstraint avm_recursion_constraint{ - .key = add_to_witness_and_track_indices(key_witnesses), - .proof = add_to_witness_and_track_indices(proof_witnesses), - .public_inputs = add_to_witness_and_track_indices(public_inputs_witnesses), + .key = add_to_witness_and_track_indices(witness, key_witnesses), + .proof = add_to_witness_and_track_indices(witness, proof_witnesses), + .public_inputs = add_to_witness_and_track_indices(witness, public_inputs_witnesses), .key_hash = 0, // not used .proof_type = AVM, }; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.cpp new file mode 100644 index 000000000000..f8092fc67511 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.cpp @@ -0,0 +1,191 @@ +// === AUDIT STATUS === +// internal: { status: not started, auditors: [], date: YYYY-MM-DD } +// external_1: { status: not started, auditors: [], date: YYYY-MM-DD } +// external_2: { status: not started, auditors: [], date: YYYY-MM-DD } +// ===================== + +#include "barretenberg/dsl/acir_format/ecdsa_constraints.hpp" +#include "barretenberg/dsl/acir_format/utils.hpp" +#include "barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp" +#include "barretenberg/stdlib/primitives/curves/secp256k1.hpp" +#include "barretenberg/stdlib/primitives/curves/secp256r1.hpp" + +namespace acir_format { + +using namespace bb; + +/** + * @brief Create constraints to verify an ECDSA signature + * + * @details Given and ECDSA constraint system, add to the builder constraints that verify the ECDSA signature. We + * perform the following operations: + * 1. Reconstruct byte arrays from builder variables (we enforce that each variable fits in one byte and stack them in + * a vector) and the boolean result from the corresponding builder variable + * 2. Reconstruct the public key from the byte representations (big-endian, 32-byte numbers) of the \f$x\f$ and \f$y\f$ + * coordinates. + * 3. Enforce uniqueness of the representation of the public key by asserting \f$x < q\f$ and \f$y < q\f$, where + * \f$q\f$ is the modulus of the base field of the elliptic curve we are working with. + * 4. Verify the signature against the public key and the hash of the message. We return a bool_t bearing witness to + * whether the signature verification was successfull or not. + * 5. Enforce that the result of the signature verification matches the expected result. + * + * @tparam Curve + * @param builder + * @param input + * @param has_valid_witness_assignments + */ +template +void create_ecdsa_verify_constraints(typename Curve::Builder& builder, + const EcdsaConstraint& input, + bool has_valid_witness_assignments) +{ + using Builder = Curve::Builder; + + using Fq = Curve::fq_ct; + using Fr = Curve::bigfr_ct; + using G1 = Curve::g1_bigfr_ct; + + using field_ct = bb::stdlib::field_t; + using bool_ct = bb::stdlib::bool_t; + using byte_array_ct = bb::stdlib::byte_array; + + // Lambda to convert std::vector to byte_array_ct + auto fields_to_bytes = [](Builder& builder, std::vector& fields) -> byte_array_ct { + byte_array_ct result(&builder); + for (auto& field : fields) { + // Construct byte array of length 1 from the field element + // The constructor enforces that `field` fits in one byte + byte_array_ct byte_to_append(field, /*num_bytes=*/1); + // Append the new byte to the result + result.write(byte_to_append); + } + + return result; + }; + + // Define builder variables based on the witness indices + std::vector hashed_message_fields = fields_from_witnesses(builder, input.hashed_message); + std::vector r_fields = fields_from_witnesses(builder, std::span(input.signature.begin(), 32)); + std::vector s_fields = fields_from_witnesses(builder, std::span(input.signature.begin() + 32, 32)); + std::vector pub_x_fields = fields_from_witnesses(builder, input.pub_x_indices); + std::vector pub_y_fields = fields_from_witnesses(builder, input.pub_y_indices); + field_ct result_field = field_ct::from_witness_index(&builder, input.result); + + if (!has_valid_witness_assignments) { + // Fill builder variables in case of empty witness assignment + create_dummy_ecdsa_constraint( + builder, hashed_message_fields, r_fields, s_fields, pub_x_fields, pub_y_fields, result_field); + } + + // Step 1. + // Construct inputs to signature verification from witness indices + byte_array_ct hashed_message = fields_to_bytes(builder, hashed_message_fields); + byte_array_ct pub_x_bytes = fields_to_bytes(builder, pub_x_fields); + byte_array_ct pub_y_bytes = fields_to_bytes(builder, pub_y_fields); + byte_array_ct r = fields_to_bytes(builder, r_fields); + byte_array_ct s = fields_to_bytes(builder, s_fields); + bool_ct result = static_cast(result_field); // Constructor enforces result_field = 0 or 1 + + // Step 2. + // Reconstruct the public key from the byte representations of its coordinates + Fq pub_x(pub_x_bytes); + Fq pub_y(pub_y_bytes); + G1 public_key(pub_x, pub_y); + + // Step 3. + // Ensure uniqueness of the public key by asserting each of its coordinates is smaller than the modulus of the base + // field + pub_x.assert_is_in_field(); + pub_y.assert_is_in_field(); + + // Step 4. + bool_ct signature_result = + stdlib::ecdsa_verify_signature(hashed_message, public_key, { r, s }); + + // Step 5. + // Assert that signature verification returned the expected result + signature_result.assert_equal(result); +} + +/** + * @brief Generate dummy ECDSA constraints when the builder doesn't have witnesses + * + * @details To avoid firing asserts, the public key must be a point on the curve + */ +template +void create_dummy_ecdsa_constraint(typename Curve::Builder& builder, + const std::vector>& hashed_message_fields, + const std::vector>& r_fields, + const std::vector>& s_fields, + const std::vector>& pub_x_fields, + const std::vector>& pub_y_fields, + const stdlib::field_t& result_field) +{ + using Builder = Curve::Builder; + using FqNative = Curve::fq; + using G1Native = Curve::g1; + using field_ct = stdlib::field_t; + + // Lambda to populate builder variables from vector of field values + auto populate_fields = [&builder](const std::vector& fields, const std::vector& values) { + for (auto [field, value] : zip_view(fields, values)) { + builder.set_variable(field.witness_index, value); + } + }; + + // Vector of 32 copies of bb::fr::zero() + std::vector mock_zeros(32, bb::fr::zero()); + + // Hashed message + populate_fields(hashed_message_fields, mock_zeros); + + // Signature + populate_fields(r_fields, mock_zeros); + populate_fields(s_fields, mock_zeros); + + // Pub key + std::array buffer_x; + std::array buffer_y; + std::vector mock_pub_x; + std::vector mock_pub_y; + FqNative::serialize_to_buffer(G1Native::one.x, &buffer_x[0]); + FqNative::serialize_to_buffer(G1Native::one.y, &buffer_y[0]); + for (auto [byte_x, byte_y] : zip_view(buffer_x, buffer_y)) { + mock_pub_x.emplace_back(bb::fr(byte_x)); + mock_pub_y.emplace_back(bb::fr(byte_y)); + } + populate_fields(pub_x_fields, mock_pub_x); + populate_fields(pub_y_fields, mock_pub_y); + + // Result + builder.set_variable(result_field.witness_index, bb::fr::one()); +} + +template void create_ecdsa_verify_constraints>( + UltraCircuitBuilder& builder, const EcdsaConstraint& input, bool has_valid_witness_assignments); +template void create_ecdsa_verify_constraints>( + MegaCircuitBuilder& builder, const EcdsaConstraint& input, bool has_valid_witness_assignments); +template void create_ecdsa_verify_constraints>( + UltraCircuitBuilder& builder, const EcdsaConstraint& input, bool has_valid_witness_assignments); +template void create_ecdsa_verify_constraints>( + MegaCircuitBuilder& builder, const EcdsaConstraint& input, bool has_valid_witness_assignments); + +template void create_dummy_ecdsa_constraint>( + UltraCircuitBuilder&, + const std::vector>&, + const std::vector>&, + const std::vector>&, + const std::vector>&, + const std::vector>&, + const stdlib::field_t&); + +template void create_dummy_ecdsa_constraint>( + UltraCircuitBuilder&, + const std::vector>&, + const std::vector>&, + const std::vector>&, + const std::vector>&, + const std::vector>&, + const stdlib::field_t&); + +} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.hpp new file mode 100644 index 000000000000..51aba500c691 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.hpp @@ -0,0 +1,67 @@ +// === AUDIT STATUS === +// internal: { status: not started, auditors: [], date: YYYY-MM-DD } +// external_1: { status: not started, auditors: [], date: YYYY-MM-DD } +// external_2: { status: not started, auditors: [], date: YYYY-MM-DD } +// ===================== + +#pragma once +#include "barretenberg/crypto/ecdsa/ecdsa.hpp" +#include "barretenberg/serialize/msgpack.hpp" +#include "barretenberg/stdlib/primitives/byte_array/byte_array.hpp" +#include + +namespace acir_format { + +using namespace bb; + +/** + * @brief ECDSA constraints + * + * @details ECDSA constraints have five components: + * 1. `hashed_message`, an array of length 32 representing the witness indices of the byte representation of the hash + * of the message for which the signature must be verified + * 2. `signature`, an array of length 64 representing the witness indices of the signature \f$(r, s)\f$ which must be + * verified. The components are represented as big-endian, 32-byte numbers. + * 3. `pub_x_indices`, an array of length 32 representing the witness indices of the byte representation the x + * coordinate of the public key against which the signature should be verified. + * 4. `pub_y_indices`, an array of length 32 representing the witness indices of the byte representation the y + * coordinate of the public key against which the signature should be verified. + * 5. `result`, an array of length 1 representing the witness index of the expected result of the signature + * verification. + */ +struct EcdsaConstraint { + // The byte representation of the hashed message. + std::array hashed_message; + + // The signature + std::array signature; + + // The public key against which the signature must be verified. + // Since Fr does not have enough bits to represent the prime field in + // secp256k1 or secp256r1, a byte array is used. + std::array pub_x_indices; + std::array pub_y_indices; + + // Expected result of signature verification + uint32_t result; + + // For serialization, update with any new fields + MSGPACK_FIELDS(hashed_message, signature, pub_x_indices, pub_y_indices, result); + friend bool operator==(EcdsaConstraint const& lhs, EcdsaConstraint const& rhs) = default; +}; + +template +void create_ecdsa_verify_constraints(typename Curve::Builder& builder, + const EcdsaConstraint& input, + bool has_valid_witness_assignments = true); + +template +void create_dummy_ecdsa_constraint(typename Curve::Builder& builder, + const std::vector>& hashed_message_fields, + const std::vector>& r_fields, + const std::vector>& s_fields, + const std::vector>& pub_x_fields, + const std::vector>& pub_y_fields, + const stdlib::field_t& result_field); + +} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.test.cpp new file mode 100644 index 000000000000..3300223c71fa --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_constraints.test.cpp @@ -0,0 +1,159 @@ +#include "barretenberg/dsl/acir_format/ecdsa_constraints.hpp" +#include "acir_format.hpp" +#include "acir_format_mocks.hpp" +#include "barretenberg/crypto/ecdsa/ecdsa.hpp" +#include "barretenberg/dsl/acir_format/utils.hpp" +#include "barretenberg/stdlib/primitives/curves/secp256k1.hpp" +#include "barretenberg/stdlib/primitives/curves/secp256r1.hpp" + +#include +#include +#include + +using namespace bb; +using namespace bb::crypto; +using namespace acir_format; + +template class EcdsaConstraintsTest : public ::testing::Test { + public: + using Builder = Curve::Builder; + using FrNative = Curve::fr; + using FqNative = Curve::fq; + using G1Native = Curve::g1; + using Flavor = std::conditional_t, UltraFlavor, MegaFlavor>; + + // Reproducible test + static constexpr FrNative private_key = + FrNative("0xd67abee717b3fc725adf59e2cc8cd916435c348b277dd814a34e3ceb279436c2"); + + static size_t generate_ecdsa_constraint(EcdsaConstraint& ecdsa_constraint, WitnessVector& witness_values) + { + std::string message_string = "Instructions unclear, ask again later."; + + // Hash the message + std::vector message_buffer(message_string.begin(), message_string.end()); + std::array hashed_message = Sha256Hasher::hash(message_buffer); + + // Generate ECDSA key pair + ecdsa_key_pair account; + account.private_key = private_key; + account.public_key = G1Native::one * account.private_key; + + // Generate signature + ecdsa_signature signature = + ecdsa_construct_signature(message_string, account); + + // Serialize public key coordinates into bytes + std::array buffer_x; + std::array buffer_y; + FqNative::serialize_to_buffer(account.public_key.x, &buffer_x[0]); + FqNative::serialize_to_buffer(account.public_key.y, &buffer_y[0]); + + // Create witness indices and witnesses + size_t num_variables = 0; + + std::array hashed_message_indices = + add_to_witness_and_track_indices(witness_values, std::span(hashed_message)); + num_variables += hashed_message_indices.size(); + + std::array pub_x_indices = + add_to_witness_and_track_indices(witness_values, std::span(buffer_x)); + num_variables += pub_x_indices.size(); + + std::array pub_y_indices = + add_to_witness_and_track_indices(witness_values, std::span(buffer_y)); + num_variables += pub_y_indices.size(); + + std::array r_indices = + add_to_witness_and_track_indices(witness_values, std::span(signature.r)); + num_variables += r_indices.size(); + + std::array s_indices = + add_to_witness_and_track_indices(witness_values, std::span(signature.s)); + num_variables += s_indices.size(); + + uint32_t result_index = static_cast(num_variables); + bb::fr result = bb::fr::one(); + witness_values.emplace_back(result); + num_variables += 1; + + // Restructure vectors into array + std::array signature_indices; + std::ranges::copy(r_indices, signature_indices.begin()); + std::ranges::copy(s_indices, signature_indices.begin() + 32); + + ecdsa_constraint = EcdsaConstraint{ .hashed_message = hashed_message_indices, + .signature = signature_indices, + .pub_x_indices = pub_x_indices, + .pub_y_indices = pub_y_indices, + .result = result_index }; + + return num_variables; + } + + static std::pair generate_constraint_system() + { + EcdsaConstraint ecdsa_constraint; + WitnessVector witness_values; + size_t num_variables = generate_ecdsa_constraint(ecdsa_constraint, witness_values); + AcirFormat constraint_system = { + .varnum = static_cast(num_variables), + .num_acir_opcodes = 1, + .public_inputs = {}, + .original_opcode_indices = create_empty_original_opcode_indices(), + }; + + if constexpr (Curve::type == bb::CurveType::SECP256K1) { + constraint_system.ecdsa_k1_constraints = { ecdsa_constraint }; + } else { + constraint_system.ecdsa_r1_constraints = { ecdsa_constraint }; + } + + mock_opcode_indices(constraint_system); + + return { constraint_system, witness_values }; + } + + protected: + static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); } +}; + +using CurveTypes = testing::Types, + stdlib::secp256r1, + stdlib::secp256k1, + stdlib::secp256r1>; + +TYPED_TEST_SUITE(EcdsaConstraintsTest, CurveTypes); + +TYPED_TEST(EcdsaConstraintsTest, GenerateVKFromConstraints) +{ + using Flavor = TestFixture::Flavor; + using Builder = TestFixture::Builder; + using ProvingKey = DeciderProvingKey_; + using VerificationKey = Flavor::VerificationKey; + + auto [constraint_system, witness_values] = TestFixture::generate_constraint_system(); + + std::shared_ptr vk_from_witness; + { + AcirProgram program{ constraint_system, witness_values }; + auto builder = create_circuit(program); + info("Num gates: ", builder.get_estimated_num_finalized_gates()); + + auto proving_key = std::make_shared(builder); + vk_from_witness = std::make_shared(proving_key->get_precomputed()); + + // Validate the builder + EXPECT_TRUE(CircuitChecker::check(builder)); + } + + std::shared_ptr vk_from_constraint; + { + AcirProgram program{ constraint_system, /*witness=*/{} }; + auto builder = create_circuit(program); + auto proving_key = std::make_shared(builder); + vk_from_constraint = std::make_shared(proving_key->get_precomputed()); + } + + EXPECT_EQ(*vk_from_witness, *vk_from_constraint); +} diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp deleted file mode 100644 index f88a96b6c4ed..000000000000 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp +++ /dev/null @@ -1,154 +0,0 @@ -// === AUDIT STATUS === -// internal: { status: not started, auditors: [], date: YYYY-MM-DD } -// external_1: { status: not started, auditors: [], date: YYYY-MM-DD } -// external_2: { status: not started, auditors: [], date: YYYY-MM-DD } -// ===================== - -#include "ecdsa_secp256k1.hpp" -#include "barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp" - -namespace acir_format { - -using namespace bb; -using secp256k1_ct = bb::stdlib::secp256k1; - -template -secp256k1_ct::g1_ct ecdsa_convert_inputs(Builder* ctx, const bb::secp256k1::g1::affine_element& input) -{ - uint256_t x_u256(input.x); - uint256_t y_u256(input.y); - secp256k1_ct::fq_ct x( - witness_ct(ctx, bb::fr(x_u256.slice(0, secp256k1_ct::fq_ct::NUM_LIMB_BITS * 2))), - witness_ct( - ctx, bb::fr(x_u256.slice(secp256k1_ct::fq_ct::NUM_LIMB_BITS * 2, secp256k1_ct::fq_ct::NUM_LIMB_BITS * 4)))); - secp256k1_ct::fq_ct y( - witness_ct(ctx, bb::fr(y_u256.slice(0, secp256k1_ct::fq_ct::NUM_LIMB_BITS * 2))), - witness_ct( - ctx, bb::fr(y_u256.slice(secp256k1_ct::fq_ct::NUM_LIMB_BITS * 2, secp256k1_ct::fq_ct::NUM_LIMB_BITS * 4)))); - - return { x, y }; -} - -witness_ct ecdsa_index_to_witness(Builder& builder, uint32_t index) -{ - fr value = builder.get_variable(index); - return { &builder, value }; -} - -template -void create_ecdsa_k1_verify_constraints(Builder& builder, - const EcdsaSecp256k1Constraint& input, - bool has_valid_witness_assignments) -{ - using secp256k1_ct = bb::stdlib::secp256k1; - using field_ct = bb::stdlib::field_t; - using bool_ct = bb::stdlib::bool_t; - using byte_array_ct = bb::stdlib::byte_array; - - if (has_valid_witness_assignments == false) { - dummy_ecdsa_constraint(builder, input); - } - - auto new_sig = ecdsa_convert_signature(builder, input.signature); - - byte_array_ct hashed_message = ecdsa_array_of_bytes_to_byte_array(builder, input.hashed_message); - auto pub_key_x_byte_arr = ecdsa_array_of_bytes_to_byte_array(builder, input.pub_x_indices); - auto pub_key_y_byte_arr = ecdsa_array_of_bytes_to_byte_array(builder, input.pub_y_indices); - - auto pub_key_x_fq = typename secp256k1_ct::fq_ct(pub_key_x_byte_arr); - auto pub_key_y_fq = typename secp256k1_ct::fq_ct(pub_key_y_byte_arr); - - std::vector rr(new_sig.r.begin(), new_sig.r.end()); - std::vector ss(new_sig.s.begin(), new_sig.s.end()); - - stdlib::ecdsa_signature sig{ stdlib::byte_array(&builder, rr), - stdlib::byte_array(&builder, ss) }; - - pub_key_x_fq.assert_is_in_field(); - pub_key_y_fq.assert_is_in_field(); - typename secp256k1_ct::g1_bigfr_ct public_key = typename 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(&builder, input.signature[i])); - sig.s[i].assert_equal(field_ct::from_witness_index(&builder, input.signature[i + 32])); - pub_key_x_byte_arr[i].assert_equal(field_ct::from_witness_index(&builder, input.pub_x_indices[i])); - pub_key_y_byte_arr[i].assert_equal(field_ct::from_witness_index(&builder, input.pub_y_indices[i])); - } - for (size_t i = 0; i < input.hashed_message.size(); ++i) { - hashed_message[i].assert_equal(field_ct::from_witness_index(&builder, input.hashed_message[i])); - } - - bool_ct signature_result = - stdlib::ecdsa_verify_signature(hashed_message, public_key, sig); - bool_ct signature_result_normalized = signature_result.normalize(); - builder.assert_equal(signature_result_normalized.witness_index, input.result); -} - -// Add dummy constraints for ECDSA because when the verifier creates the -// constraint system, they usually use zeroes for witness values. -// -// This does not work for ECDSA as the signature, r, s and public key need -// to be valid. -template void dummy_ecdsa_constraint(Builder& builder, EcdsaSecp256k1Constraint const& input) -{ - - std::array pub_x_indices_; - std::array pub_y_indices_; - std::array signature_; - std::array message_indices_; - - // Create a valid signature with a valid public key - crypto::ecdsa_key_pair account; - account.private_key = 10; - account.public_key = secp256k1_ct::g1::one * account.private_key; - uint256_t pub_x_value = account.public_key.x; - uint256_t pub_y_value = account.public_key.y; - std::string message_string = "Instructions unclear, ask again later."; - crypto::ecdsa_signature signature = - crypto::ecdsa_construct_signature( - message_string, account); - - // Create new variables which will reference the valid public key and signature. - // We don't use them in a gate, so when we call assert_equal, they will be - // replaced as if they never existed. - for (size_t i = 0; i < 32; ++i) { - uint32_t m_wit = builder.add_variable(input.hashed_message[i]); - uint32_t x_wit = builder.add_variable(pub_x_value.slice(248 - i * 8, 256 - i * 8)); - uint32_t y_wit = builder.add_variable(pub_y_value.slice(248 - i * 8, 256 - i * 8)); - uint32_t r_wit = builder.add_variable(signature.r[i]); - uint32_t s_wit = builder.add_variable(signature.s[i]); - message_indices_[i] = m_wit; - pub_x_indices_[i] = x_wit; - pub_y_indices_[i] = y_wit; - signature_[i] = r_wit; - signature_[i + 32] = s_wit; - } - - // Call assert_equal(from, to) to replace the value in `to` by the value in `from` - for (size_t i = 0; i < input.hashed_message.size(); ++i) { - builder.assert_equal(message_indices_[i], input.hashed_message[i]); - } - for (size_t i = 0; i < input.pub_x_indices.size(); ++i) { - builder.assert_equal(pub_x_indices_[i], input.pub_x_indices[i]); - } - for (size_t i = 0; i < input.pub_y_indices.size(); ++i) { - builder.assert_equal(pub_y_indices_[i], input.pub_y_indices[i]); - } - for (size_t i = 0; i < input.signature.size(); ++i) { - builder.assert_equal(signature_[i], input.signature[i]); - } -} - -template void create_ecdsa_k1_verify_constraints(UltraCircuitBuilder& builder, - const EcdsaSecp256k1Constraint& input, - bool has_valid_witness_assignments); -template void create_ecdsa_k1_verify_constraints(MegaCircuitBuilder& builder, - const EcdsaSecp256k1Constraint& input, - bool has_valid_witness_assignments); -template void dummy_ecdsa_constraint(UltraCircuitBuilder& builder, - EcdsaSecp256k1Constraint const& input); - -} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp deleted file mode 100644 index 10c63ae2b49f..000000000000 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp +++ /dev/null @@ -1,115 +0,0 @@ -// === AUDIT STATUS === -// internal: { status: not started, auditors: [], date: YYYY-MM-DD } -// external_1: { status: not started, auditors: [], date: YYYY-MM-DD } -// external_2: { status: not started, auditors: [], date: YYYY-MM-DD } -// ===================== - -#pragma once -#include "barretenberg/crypto/ecdsa/ecdsa.hpp" -#include "barretenberg/serialize/msgpack.hpp" -#include "barretenberg/stdlib/primitives/byte_array/byte_array.hpp" -#include "barretenberg/stdlib/primitives/witness/witness.hpp" -#include - -namespace acir_format { - -using Builder = bb::UltraCircuitBuilder; -using witness_ct = bb::stdlib::witness_t; - -struct EcdsaSecp256k1Constraint { - // This is the byte representation of the hashed message. - std::array hashed_message; - - // This is the computed signature - // - std::array signature; - - // This is the supposed public key which signed the - // message, giving rise to the signature. - // Since Fr does not have enough bits to represent - // the prime field in secp256k1, a byte array is used. - // Can also use low and hi where lo=128 bits - std::array pub_x_indices; - std::array pub_y_indices; - - // This is the result of verifying the signature - uint32_t result; - - // for serialization, update with any new fields - MSGPACK_FIELDS(hashed_message, signature, pub_x_indices, pub_y_indices, result); - friend bool operator==(EcdsaSecp256k1Constraint const& lhs, EcdsaSecp256k1Constraint const& rhs) = default; -}; - -template -void create_ecdsa_k1_verify_constraints(Builder& builder, - const EcdsaSecp256k1Constraint& input, - bool has_valid_witness_assignments = true); - -template void dummy_ecdsa_constraint(Builder& builder, EcdsaSecp256k1Constraint const& input); - -witness_ct ecdsa_index_to_witness(Builder& builder, uint32_t index); -template -bb::stdlib::byte_array ecdsa_array_of_bytes_to_byte_array(Builder& builder, - std::array vector_of_bytes) -{ - using byte_array_ct = bb::stdlib::byte_array; - using field_ct = bb::stdlib::field_t; - - byte_array_ct arr(&builder); - - // Get the witness assignment for each witness index - // Write the witness assignment to the byte_array - for (const auto& witness_index : vector_of_bytes) { - - field_ct element = field_ct::from_witness_index(&builder, witness_index); - size_t num_bytes = 1; - - byte_array_ct element_bytes(element, num_bytes); - arr.write(element_bytes); - } - return arr; -} - -// We have the implementation of this template in the header as this method is used -// by other ecdsa constraints over different curves (e.g. secp256r1). -// gcc needs to be able to see the implementation order to generate code for -// all Builder specializations (e.g. bb::Goblin::Builder vs. bb::UltraCircuitBuilder) -template -bb::crypto::ecdsa_signature ecdsa_convert_signature(Builder& builder, std::array signature) -{ - - bb::crypto::ecdsa_signature signature_cr; - - // Get the witness assignment for each witness index - // Write the witness assignment to the byte_array - - for (unsigned int i = 0; i < 32; i++) { - auto witness_index = signature[i]; - - std::vector fr_bytes(sizeof(bb::fr)); - - bb::fr value = builder.get_variable(witness_index); - - bb::fr::serialize_to_buffer(value, &fr_bytes[0]); - - signature_cr.r[i] = fr_bytes.back(); - } - - for (unsigned int i = 32; i < 64; i++) { - auto witness_index = signature[i]; - - std::vector fr_bytes(sizeof(bb::fr)); - - bb::fr value = builder.get_variable(witness_index); - - bb::fr::serialize_to_buffer(value, &fr_bytes[0]); - - signature_cr.s[i - 32] = fr_bytes.back(); - } - - signature_cr.v = 27; - - return signature_cr; -} - -} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp deleted file mode 100644 index f2c331ee57b4..000000000000 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include "ecdsa_secp256k1.hpp" -#include "acir_format.hpp" -#include "acir_format_mocks.hpp" -#include "barretenberg/crypto/ecdsa/ecdsa.hpp" - -#include "barretenberg/stdlib/primitives/curves/secp256k1.hpp" - -#include -#include - -using namespace bb; -using namespace bb::crypto; -using namespace acir_format; -using curve_ct = stdlib::secp256k1; - -class ECDSASecp256k1 : public ::testing::Test { - protected: - static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); } -}; - -size_t generate_ecdsa_constraint(EcdsaSecp256k1Constraint& ecdsa_constraint, WitnessVector& witness_values) -{ - std::string message_string = "Instructions unclear, ask again later."; - - // hash the message since the dsl ecdsa gadget uses the prehashed message - // NOTE: If the hash being used outputs more than 32 bytes, then big-field will panic - std::vector message_buffer; - std::copy(message_string.begin(), message_string.end(), std::back_inserter(message_buffer)); - auto hashed_message = sha256(message_buffer); - - ecdsa_key_pair account; - account.private_key = curve_ct::fr::random_element(); - account.public_key = curve_ct::g1::one * account.private_key; - - ecdsa_signature signature = - 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::array message_in; - std::array pub_x_indices_in; - std::array pub_y_indices_in; - std::array signature_in; - size_t offset = 0; - for (size_t i = 0; i < hashed_message.size(); ++i) { - message_in[i] = static_cast(i + offset); - const auto byte = static_cast(hashed_message[i]); - witness_values.emplace_back(byte); - } - offset += message_in.size(); - - for (size_t i = 0; i < 32; ++i) { - pub_x_indices_in[i] = static_cast(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[i] = static_cast(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[i] = static_cast(i + offset); - witness_values.emplace_back(signature.r[i]); - } - offset += signature.r.size(); - for (size_t i = 0; i < 32; ++i) { - signature_in[i + 32] = static_cast(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 = EcdsaSecp256k1Constraint{ .hashed_message = message_in, - .signature = signature_in, - .pub_x_indices = pub_x_indices_in, - .pub_y_indices = pub_y_indices_in, - .result = result_in }; - return offset; -} - -TEST_F(ECDSASecp256k1, TestECDSAConstraintSucceed) -{ - EcdsaSecp256k1Constraint ecdsa_k1_constraint; - WitnessVector witness_values; - size_t num_variables = generate_ecdsa_constraint(ecdsa_k1_constraint, witness_values); - AcirFormat constraint_system{ - .varnum = static_cast(num_variables), - .num_acir_opcodes = 1, - .public_inputs = {}, - .ecdsa_k1_constraints = { ecdsa_k1_constraint }, - .original_opcode_indices = create_empty_original_opcode_indices(), - }; - mock_opcode_indices(constraint_system); - - AcirProgram program{ constraint_system, witness_values }; - auto builder = create_circuit(program); - - EXPECT_EQ(builder.get_variable(ecdsa_k1_constraint.result), 1); - - EXPECT_TRUE(CircuitChecker::check(builder)); -} - -// Test that the verifier can create an ECDSA circuit. -// The ECDSA circuit requires that certain dummy data is valid -// even though we are just building the circuit. -TEST_F(ECDSASecp256k1, TestECDSACompilesForVerifier) -{ - EcdsaSecp256k1Constraint ecdsa_k1_constraint; - WitnessVector witness_values; - size_t num_variables = generate_ecdsa_constraint(ecdsa_k1_constraint, witness_values); - AcirFormat constraint_system{ - .varnum = static_cast(num_variables), - .num_acir_opcodes = 1, - .public_inputs = {}, - .ecdsa_k1_constraints = { ecdsa_k1_constraint }, - .original_opcode_indices = create_empty_original_opcode_indices(), - }; - mock_opcode_indices(constraint_system); - - AcirProgram program{ constraint_system, /*witness=*/{} }; - auto builder = create_circuit(program); - - EXPECT_TRUE(CircuitChecker::check(builder)); -} - -TEST_F(ECDSASecp256k1, TestECDSAConstraintFail) -{ - EcdsaSecp256k1Constraint ecdsa_k1_constraint; - WitnessVector witness_values; - size_t num_variables = generate_ecdsa_constraint(ecdsa_k1_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; - - AcirFormat constraint_system{ - .varnum = static_cast(num_variables), - .num_acir_opcodes = 1, - .public_inputs = {}, - .ecdsa_k1_constraints = { ecdsa_k1_constraint }, - .original_opcode_indices = create_empty_original_opcode_indices(), - }; - mock_opcode_indices(constraint_system); - - AcirProgram program{ constraint_system, witness_values }; - auto builder = create_circuit(program); - EXPECT_EQ(builder.get_variable(ecdsa_k1_constraint.result), 0); - - EXPECT_TRUE(CircuitChecker::check(builder)); -} diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.cpp deleted file mode 100644 index cefe5bdcd571..000000000000 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.cpp +++ /dev/null @@ -1,160 +0,0 @@ -// === AUDIT STATUS === -// internal: { status: not started, auditors: [], date: YYYY-MM-DD } -// external_1: { status: not started, auditors: [], date: YYYY-MM-DD } -// external_2: { status: not started, auditors: [], date: YYYY-MM-DD } -// ===================== - -#include "ecdsa_secp256r1.hpp" -#include "barretenberg/crypto/ecdsa/ecdsa.hpp" -#include "barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp" -#include "barretenberg/stdlib/primitives/curves/secp256r1.hpp" -#include "ecdsa_secp256k1.hpp" - -using namespace bb; -using namespace bb::crypto; - -namespace acir_format { - -using secp256r1_ct = stdlib::secp256r1; - -secp256r1_ct::g1_ct ecdsa_convert_inputs(Builder* ctx, const secp256r1::g1::affine_element& input) -{ - uint256_t x_u256(input.x); - uint256_t y_u256(input.y); - secp256r1_ct::fq_ct x( - witness_ct(ctx, bb::fr(x_u256.slice(0, secp256r1_ct::fq_ct::NUM_LIMB_BITS * 2))), - witness_ct( - ctx, bb::fr(x_u256.slice(secp256r1_ct::fq_ct::NUM_LIMB_BITS * 2, secp256r1_ct::fq_ct::NUM_LIMB_BITS * 4)))); - secp256r1_ct::fq_ct y( - witness_ct(ctx, bb::fr(y_u256.slice(0, secp256r1_ct::fq_ct::NUM_LIMB_BITS * 2))), - witness_ct( - ctx, bb::fr(y_u256.slice(secp256r1_ct::fq_ct::NUM_LIMB_BITS * 2, secp256r1_ct::fq_ct::NUM_LIMB_BITS * 4)))); - - return { x, y }; -} - -template -void create_ecdsa_r1_verify_constraints(Builder& builder, - const EcdsaSecp256r1Constraint& input, - bool has_valid_witness_assignments) -{ - using secp256r1_ct = bb::stdlib::secp256r1; - using bool_ct = bb::stdlib::bool_t; - using field_ct = bb::stdlib::field_t; - using byte_array_ct = bb::stdlib::byte_array; - - if (has_valid_witness_assignments == false) { - dummy_ecdsa_constraint(builder, input); - } - - auto new_sig = ecdsa_convert_signature(builder, input.signature); - - byte_array_ct hashed_message = ecdsa_array_of_bytes_to_byte_array(builder, input.hashed_message); - auto pub_key_x_byte_arr = ecdsa_array_of_bytes_to_byte_array(builder, input.pub_x_indices); - auto pub_key_y_byte_arr = ecdsa_array_of_bytes_to_byte_array(builder, input.pub_y_indices); - - auto pub_key_x_fq = typename secp256r1_ct::fq_ct(pub_key_x_byte_arr); - auto pub_key_y_fq = typename secp256r1_ct::fq_ct(pub_key_y_byte_arr); - - std::vector rr(new_sig.r.begin(), new_sig.r.end()); - std::vector ss(new_sig.s.begin(), new_sig.s.end()); - - stdlib::ecdsa_signature sig{ stdlib::byte_array(&builder, rr), - stdlib::byte_array(&builder, ss) }; - - pub_key_x_fq.assert_is_in_field(); - pub_key_y_fq.assert_is_in_field(); - typename secp256r1_ct::g1_bigfr_ct public_key = typename secp256r1_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(&builder, input.signature[i])); - sig.s[i].assert_equal(field_ct::from_witness_index(&builder, input.signature[i + 32])); - pub_key_x_byte_arr[i].assert_equal(field_ct::from_witness_index(&builder, input.pub_x_indices[i])); - pub_key_y_byte_arr[i].assert_equal(field_ct::from_witness_index(&builder, input.pub_y_indices[i])); - } - for (size_t i = 0; i < input.hashed_message.size(); ++i) { - hashed_message[i].assert_equal(field_ct::from_witness_index(&builder, input.hashed_message[i])); - } - - bool_ct signature_result = - stdlib::ecdsa_verify_signature(hashed_message, public_key, sig); - bool_ct signature_result_normalized = signature_result.normalize(); - builder.assert_equal(signature_result_normalized.witness_index, input.result); -} - -// Add dummy constraints for ECDSA because when the verifier creates the -// constraint system, they usually use zeroes for witness values. -// -// This does not work for ECDSA as the signature, r, s and public key need -// to be valid. -template void dummy_ecdsa_constraint(Builder& builder, EcdsaSecp256r1Constraint const& input) -{ - - std::array pub_x_indices_; - std::array pub_y_indices_; - std::array signature_; - std::array message_indices_; - - // Create a valid signature with a valid public key - std::string message_string = "Instructions unclear, ask again later."; - - // hash the message since the dsl ecdsa gadget uses the prehashed message - // NOTE: If the hash being used outputs more than 32 bytes, then big-field will panic - std::vector message_buffer; - std::copy(message_string.begin(), message_string.end(), std::back_inserter(message_buffer)); - auto hashed_message = sha256(message_buffer); - - ecdsa_key_pair account; - account.private_key = 10; - account.public_key = secp256r1::g1::one * account.private_key; - - ecdsa_signature signature = - ecdsa_construct_signature(message_string, account); - - uint256_t pub_x_value = account.public_key.x; - uint256_t pub_y_value = account.public_key.y; - - // Create new variables which will reference the valid public key and signature. - // We don't use them in a gate, so when we call assert_equal, they will be - // replaced as if they never existed. - for (size_t i = 0; i < 32; ++i) { - uint32_t m_wit = builder.add_variable(hashed_message[i]); - uint32_t x_wit = builder.add_variable(pub_x_value.slice(248 - i * 8, 256 - i * 8)); - uint32_t y_wit = builder.add_variable(pub_y_value.slice(248 - i * 8, 256 - i * 8)); - uint32_t r_wit = builder.add_variable(signature.r[i]); - uint32_t s_wit = builder.add_variable(signature.s[i]); - message_indices_[i] = m_wit; - pub_x_indices_[i] = x_wit; - pub_y_indices_[i] = y_wit; - signature_[i] = r_wit; - signature_[i + 32] = s_wit; - } - - // Call assert_equal(from, to) to replace the value in `to` by the value in `from` - for (size_t i = 0; i < input.hashed_message.size(); ++i) { - builder.assert_equal(message_indices_[i], input.hashed_message[i]); - } - for (size_t i = 0; i < input.pub_x_indices.size(); ++i) { - builder.assert_equal(pub_x_indices_[i], input.pub_x_indices[i]); - } - for (size_t i = 0; i < input.pub_y_indices.size(); ++i) { - builder.assert_equal(pub_y_indices_[i], input.pub_y_indices[i]); - } - for (size_t i = 0; i < input.signature.size(); ++i) { - builder.assert_equal(signature_[i], input.signature[i]); - } -} - -template void create_ecdsa_r1_verify_constraints(UltraCircuitBuilder& builder, - const EcdsaSecp256r1Constraint& input, - bool has_valid_witness_assignments); -template void create_ecdsa_r1_verify_constraints(MegaCircuitBuilder& builder, - const EcdsaSecp256r1Constraint& input, - bool has_valid_witness_assignments); -template void dummy_ecdsa_constraint(UltraCircuitBuilder& builder, - EcdsaSecp256r1Constraint const& input); - -} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.hpp deleted file mode 100644 index 62c3061d0f23..000000000000 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.hpp +++ /dev/null @@ -1,64 +0,0 @@ -// === AUDIT STATUS === -// internal: { status: not started, auditors: [], date: YYYY-MM-DD } -// external_1: { status: not started, auditors: [], date: YYYY-MM-DD } -// external_2: { status: not started, auditors: [], date: YYYY-MM-DD } -// ===================== - -#pragma once -#include "barretenberg/common/serialize.hpp" -#include -#include -#include - -namespace acir_format { - -struct EcdsaSecp256r1Constraint { - // This is the byte representation of the hashed message. - std::array hashed_message; - - // This is the supposed public key which signed the - // message, giving rise to the signature. - // Since Fr does not have enough bits to represent - // the prime field in secp256r1, a byte array is used. - // Can also use low and hi where lo=128 bits - std::array pub_x_indices; - std::array pub_y_indices; - - // This is the result of verifying the signature - uint32_t result; - - // This is the computed signature - // - std::array signature; - - friend bool operator==(EcdsaSecp256r1Constraint const& lhs, EcdsaSecp256r1Constraint const& rhs) = default; -}; - -template -void create_ecdsa_r1_verify_constraints(Builder& builder, - const EcdsaSecp256r1Constraint& input, - bool has_valid_witness_assignments = true); - -template void dummy_ecdsa_constraint(Builder& builder, EcdsaSecp256r1Constraint const& input); - -template inline void read(B& buf, EcdsaSecp256r1Constraint& constraint) -{ - using serialize::read; - read(buf, constraint.hashed_message); - read(buf, constraint.signature); - read(buf, constraint.pub_x_indices); - read(buf, constraint.pub_y_indices); - read(buf, constraint.result); -} - -template inline void write(B& buf, EcdsaSecp256r1Constraint const& constraint) -{ - using serialize::write; - write(buf, constraint.hashed_message); - write(buf, constraint.signature); - write(buf, constraint.pub_x_indices); - write(buf, constraint.pub_y_indices); - write(buf, constraint.result); -} - -} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp deleted file mode 100644 index 6a1d2a1022dd..000000000000 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256r1.test.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "ecdsa_secp256r1.hpp" -#include "acir_format.hpp" -#include "acir_format_mocks.hpp" -#include "barretenberg/crypto/ecdsa/ecdsa.hpp" - -#include "barretenberg/stdlib/primitives/curves/secp256r1.hpp" - -#include -#include - -using namespace bb; -using namespace bb::crypto; -using namespace acir_format; - -using curve_ct = stdlib::secp256r1; - -// Generate r1 constraints given pre generated pubkey, sig and message values -size_t generate_r1_constraints(EcdsaSecp256r1Constraint& ecdsa_r1_constraint, - WitnessVector& witness_values, - uint256_t pub_x_value, - uint256_t pub_y_value, - std::array hashed_message, - ecdsa_signature signature) -{ - - std::array message_in; - std::array pub_x_indices_in; - std::array pub_y_indices_in; - std::array signature_in; - size_t offset = 0; - for (size_t i = 0; i < hashed_message.size(); ++i) { - message_in[i] = static_cast(i + offset); - const auto byte = static_cast(hashed_message[i]); - witness_values.emplace_back(byte); - } - offset += message_in.size(); - - for (size_t i = 0; i < 32; ++i) { - pub_x_indices_in[i] = static_cast(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[i] = static_cast(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[i] = static_cast(i + offset); - witness_values.emplace_back(signature.r[i]); - } - offset += signature.r.size(); - for (size_t i = 0; i < 32; ++i) { - signature_in[i + 32] = static_cast(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_r1_constraint = EcdsaSecp256r1Constraint{ - .hashed_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; -} - -size_t generate_ecdsa_constraint(EcdsaSecp256r1Constraint& ecdsa_r1_constraint, WitnessVector& witness_values) -{ - - std::string message_string = "Instructions unclear, ask again later."; - - // hash the message since the dsl ecdsa gadget uses the prehashed message - // NOTE: If the hash being used outputs more than 32 bytes, then big-field will panic - std::vector message_buffer; - std::copy(message_string.begin(), message_string.end(), std::back_inserter(message_buffer)); - auto hashed_message = sha256(message_buffer); - - ecdsa_key_pair account; - account.private_key = curve_ct::fr::random_element(); - account.public_key = curve_ct::g1::one * account.private_key; - - ecdsa_signature signature = - ecdsa_construct_signature(message_string, account); - - return generate_r1_constraints( - ecdsa_r1_constraint, witness_values, account.public_key.x, account.public_key.y, hashed_message, signature); -} - -TEST(ECDSASecp256r1, test_hardcoded) -{ - bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); - EcdsaSecp256r1Constraint ecdsa_r1_constraint; - WitnessVector witness_values; - - std::string message = "ECDSA proves knowledge of a secret number in the context of a single message"; - std::array hashed_message = { - 84, 112, 91, 163, 186, 175, 219, 223, 186, 140, 95, 154, 112, 247, 168, 155, - 238, 152, 217, 6, 181, 62, 49, 7, 77, 167, 186, 236, 220, 13, 169, 173, - }; - - uint256_t pub_key_x = uint256_t("550f471003f3df97c3df506ac797f6721fb1a1fb7b8f6f83d224498a65c88e24"); - uint256_t pub_key_y = uint256_t("136093d7012e509a73715cbd0b00a3cc0ff4b5c01b3ffa196ab1fb327036b8e6"); - - // 0x2c70a8d084b62bfc5ce03641caf9f72ad4da8c81bfe6ec9487bb5e1bef62a13218ad9ee29eaf351fdc50f1520c425e9b908a07278b43b0ec7b872778c14e0784 - ecdsa_signature signature = { .r = { 44, 112, 168, 208, 132, 182, 43, 252, 92, 224, 54, 65, 202, 249, 247, 42, - 212, 218, 140, 129, 191, 230, 236, 148, 135, 187, 94, 27, 239, 98, 161, 50 }, - .s = { 24, 173, 158, 226, 158, 175, 53, 31, 220, 80, 241, 82, 12, 66, 94, 155, - 144, 138, 7, 39, 139, 67, 176, 236, 123, 135, 39, 120, 193, 78, 7, 132 }, - .v = 0 }; - - ecdsa_key_pair account; - account.private_key = curve_ct::fr(uint256_t("0202020202020202020202020202020202020202020202020202020202020202")); - - account.public_key = curve_ct::g1::one * account.private_key; - - size_t num_variables = - generate_r1_constraints(ecdsa_r1_constraint, witness_values, pub_key_x, pub_key_y, hashed_message, signature); - - AcirFormat constraint_system{ - .varnum = static_cast(num_variables), - .num_acir_opcodes = 1, - .public_inputs = {}, - .ecdsa_r1_constraints = { ecdsa_r1_constraint }, - .original_opcode_indices = create_empty_original_opcode_indices(), - }; - mock_opcode_indices(constraint_system); - - secp256r1::g1::affine_element pub_key = { pub_key_x, pub_key_y }; - bool we_ballin = - ecdsa_verify_signature(message, pub_key, signature); - EXPECT_EQ(we_ballin, true); - - AcirProgram program{ constraint_system, witness_values }; - auto builder = create_circuit(program); - - EXPECT_EQ(builder.get_variable(ecdsa_r1_constraint.result), 1); - EXPECT_TRUE(CircuitChecker::check(builder)); -} - -TEST(ECDSASecp256r1, TestECDSAConstraintSucceed) -{ - bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); - EcdsaSecp256r1Constraint ecdsa_r1_constraint; - WitnessVector witness_values; - size_t num_variables = generate_ecdsa_constraint(ecdsa_r1_constraint, witness_values); - AcirFormat constraint_system{ - .varnum = static_cast(num_variables), - .num_acir_opcodes = 1, - .public_inputs = {}, - .ecdsa_r1_constraints = { ecdsa_r1_constraint }, - .original_opcode_indices = create_empty_original_opcode_indices(), - }; - mock_opcode_indices(constraint_system); - - AcirProgram program{ constraint_system, witness_values }; - auto builder = create_circuit(program); - - EXPECT_EQ(builder.get_variable(ecdsa_r1_constraint.result), 1); - EXPECT_TRUE(CircuitChecker::check(builder)); -} - -// Test that the verifier can create an ECDSA circuit. -// The ECDSA circuit requires that certain dummy data is valid -// even though we are just building the circuit. -TEST(ECDSASecp256r1, TestECDSACompilesForVerifier) -{ - bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); - EcdsaSecp256r1Constraint ecdsa_r1_constraint; - WitnessVector witness_values; - size_t num_variables = generate_ecdsa_constraint(ecdsa_r1_constraint, witness_values); - AcirFormat constraint_system{ - .varnum = static_cast(num_variables), - .num_acir_opcodes = 1, - .public_inputs = {}, - .ecdsa_r1_constraints = { ecdsa_r1_constraint }, - .original_opcode_indices = create_empty_original_opcode_indices(), - }; - mock_opcode_indices(constraint_system); - - AcirProgram program{ constraint_system, /*witness=*/{} }; - auto builder = create_circuit(program); - - EXPECT_TRUE(CircuitChecker::check(builder)); -} - -TEST(ECDSASecp256r1, TestECDSAConstraintFail) -{ - bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); - EcdsaSecp256r1Constraint ecdsa_r1_constraint; - WitnessVector witness_values; - size_t num_variables = generate_ecdsa_constraint(ecdsa_r1_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; - - AcirFormat constraint_system{ - .varnum = static_cast(num_variables), - .num_acir_opcodes = 1, - .public_inputs = {}, - .ecdsa_r1_constraints = { ecdsa_r1_constraint }, - .original_opcode_indices = create_empty_original_opcode_indices(), - }; - mock_opcode_indices(constraint_system); - - AcirProgram program{ constraint_system, witness_values }; - auto builder = create_circuit(program); - - EXPECT_EQ(builder.get_variable(ecdsa_r1_constraint.result), 0); - - EXPECT_TRUE(CircuitChecker::check(builder)); -} diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/proof_surgeon.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/proof_surgeon.hpp index 9aeaeb491a52..5a9a75ac9a03 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/proof_surgeon.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/proof_surgeon.hpp @@ -7,6 +7,7 @@ #pragma once #include "barretenberg/common/map.hpp" #include "barretenberg/common/serialize.hpp" +#include "barretenberg/dsl/acir_format/utils.hpp" #include "barretenberg/ecc/curves/bn254/fr.hpp" #include "barretenberg/flavor/flavor.hpp" #include "barretenberg/honk/proof_system/types/proof.hpp" @@ -118,24 +119,13 @@ template class ProofSurgeon { std::vector public_input_witnesses = cut_public_inputs_from_proof(proof_witnesses, num_public_inputs_to_extract); - // Helper to append some values to the witness vector and return their corresponding indices - auto add_to_witness_and_track_indices = [](bb::SlabVector& witness, - const std::vector& input) -> std::vector { - std::vector indices; - indices.reserve(input.size()); - auto witness_idx = static_cast(witness.size()); - for (const auto& value : input) { - witness.push_back(value); - indices.push_back(witness_idx++); - } - return indices; - }; - // Append key, proof, and public inputs while storing the associated witness indices - std::vector key_indices = add_to_witness_and_track_indices(witness, key_witnesses); - uint32_t key_hash_index = add_to_witness_and_track_indices(witness, { key_hash_witness })[0]; - std::vector proof_indices = add_to_witness_and_track_indices(witness, proof_witnesses); - std::vector public_input_indices = add_to_witness_and_track_indices(witness, public_input_witnesses); + std::vector key_indices = add_to_witness_and_track_indices(witness, key_witnesses); + uint32_t key_hash_index = static_cast(witness.size()); + witness.emplace_back(key_hash_witness); + std::vector proof_indices = add_to_witness_and_track_indices(witness, proof_witnesses); + std::vector public_input_indices = + add_to_witness_and_track_indices(witness, public_input_witnesses); return { key_indices, key_hash_index, proof_indices, public_input_indices }; } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/utils.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/utils.hpp new file mode 100644 index 000000000000..791bf927f677 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/utils.hpp @@ -0,0 +1,84 @@ +// === AUDIT STATUS === +// internal: { status: not started, auditors: [], date: YYYY-MM-DD } +// external_1: { status: not started, auditors: [], date: YYYY-MM-DD } +// external_2: { status: not started, auditors: [], date: YYYY-MM-DD } +// ===================== + +#pragma once + +#include "barretenberg/dsl/acir_format/acir_format.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" +#include + +namespace acir_format { + +using namespace bb; + +/** + * @brief Generate builder variables from witness indices. This function is useful when receiving the indices of the + * witness from ACIR. + * + * @tparam Builder + * @param builder + * @param witness_indices + * @return std::vector> + */ +template +static std::vector> fields_from_witnesses(Builder& builder, + std::span witness_indices) +{ + std::vector> result; + result.reserve(witness_indices.size()); + for (const auto& idx : witness_indices) { + result.emplace_back(stdlib::field_t::from_witness_index(&builder, idx)); + } + return result; +} + +/** + * @brief Append values to a witness vector and track their indices. + * + * @details This function is useful in mocking situations, when we need to add dummy variables to a builder. + * @tparam T + * @param witness + * @param input + * @return std::vector + */ +template +std::vector add_to_witness_and_track_indices(WitnessVector& witness, std::span input) +{ + std::vector indices; + indices.reserve(input.size()); + auto witness_idx = static_cast(witness.size()); + for (const auto& value : input) { + witness.push_back(bb::fr(value)); + indices.push_back(witness_idx++); + } + return indices; +}; + +/** + * @brief Append values to a witness vector and track their indices. + * + * @details This function is useful in mocking situations, when we need to add dummy variables to a builder. + * + * @tparam T + * @tparam N + * @param witness + * @param input + * @return std::array + */ +template +std::array add_to_witness_and_track_indices(WitnessVector& witness, std::span input) +{ + std::array indices; + auto witness_idx = static_cast(witness.size()); + size_t idx = 0; + for (const auto& value : input) { + witness.push_back(bb::fr(value)); + indices[idx++] = witness_idx++; + } + return indices; +}; + +} // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/witness_constant.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/witness_constant.hpp index ecfcca619f37..f02eacc4437b 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/witness_constant.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/witness_constant.hpp @@ -5,7 +5,6 @@ // ===================== #pragma once -#include "barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp" #include "barretenberg/serialize/msgpack.hpp" #include "barretenberg/stdlib/primitives/field/field.hpp" #include "barretenberg/stdlib/primitives/group/cycle_group.hpp" @@ -45,4 +44,4 @@ bb::stdlib::cycle_group to_grumpkin_point(const WitnessOrConstant& bool has_valid_witness_assignments, Builder& builder); -} // namespace acir_format \ No newline at end of file +} // namespace acir_format