Skip to content

Commit e6e7017

Browse files
chore!: Define single ecdsa constraint generator (#16612)
Audit part 5: Define single ecdsa constraint generator In this PR we merge the two ECDSA constraint generators (secp256k1 and secp256r1) into a single one templated over `Curve` type. We also restructure the code of the constraint generator to be more similar to other parts of the `dsl` package. There are some changes to the circuits. This is due to some `assert_equal` that have been removed, and `normalize` calls that are not needed. We also add documentation and define a `utils.hpp` file in the `dsl` package containing some functions that are used multiple times in different files.
1 parent 16295c3 commit e6e7017

18 files changed

+527
-919
lines changed

barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ cd ..
1111
# - Generate a hash for versioning: sha256sum bb-civc-inputs.tar.gz
1212
# - 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
1313
# Note: In case of the "Test suite failed to run ... Unexpected token 'with' " error, need to run: docker pull aztecprotocol/build:3.0
14-
pinned_short_hash="f22d116f"
14+
pinned_short_hash="e5081516"
1515
pinned_civc_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-civc-inputs-${pinned_short_hash}.tar.gz"
1616

1717
function compress_and_upload {

barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@
1111
#include "barretenberg/common/op_count.hpp"
1212
#include "barretenberg/common/throw_or_abort.hpp"
1313
#include "barretenberg/dsl/acir_format/civc_recursion_constraints.hpp"
14+
#include "barretenberg/dsl/acir_format/ecdsa_constraints.hpp"
1415
#include "barretenberg/dsl/acir_format/honk_recursion_constraint.hpp"
1516
#include "barretenberg/dsl/acir_format/pg_recursion_constraint.hpp"
1617
#include "barretenberg/dsl/acir_format/proof_surgeon.hpp"
1718
#include "barretenberg/flavor/flavor.hpp"
1819
#include "barretenberg/honk/proving_key_inspector.hpp"
1920
#include "barretenberg/stdlib/eccvm_verifier/verifier_commitment_key.hpp"
2021
#include "barretenberg/stdlib/primitives/curves/grumpkin.hpp"
22+
#include "barretenberg/stdlib/primitives/curves/secp256k1.hpp"
23+
#include "barretenberg/stdlib/primitives/curves/secp256r1.hpp"
2124
#include "barretenberg/stdlib/primitives/field/field_conversion.hpp"
2225
#include "barretenberg/stdlib/primitives/pairing_points.hpp"
2326
#include "barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp"
@@ -185,15 +188,15 @@ void build_constraints(Builder& builder, AcirProgram& program, const ProgramMeta
185188
// Add ECDSA k1 constraints
186189
for (size_t i = 0; i < constraint_system.ecdsa_k1_constraints.size(); ++i) {
187190
const auto& constraint = constraint_system.ecdsa_k1_constraints.at(i);
188-
create_ecdsa_k1_verify_constraints(builder, constraint, has_valid_witness_assignments);
191+
create_ecdsa_verify_constraints<stdlib::secp256k1<Builder>>(builder, constraint, has_valid_witness_assignments);
189192
gate_counter.track_diff(constraint_system.gates_per_opcode,
190193
constraint_system.original_opcode_indices.ecdsa_k1_constraints.at(i));
191194
}
192195

193196
// Add ECDSA r1 constraints
194197
for (size_t i = 0; i < constraint_system.ecdsa_r1_constraints.size(); ++i) {
195198
const auto& constraint = constraint_system.ecdsa_r1_constraints.at(i);
196-
create_ecdsa_r1_verify_constraints(builder, constraint, has_valid_witness_assignments);
199+
create_ecdsa_verify_constraints<stdlib::secp256r1<Builder>>(builder, constraint, has_valid_witness_assignments);
197200
gate_counter.track_diff(constraint_system.gates_per_opcode,
198201
constraint_system.original_opcode_indices.ecdsa_r1_constraints.at(i));
199202
}

barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
#include "blake3_constraint.hpp"
2020
#include "block_constraint.hpp"
2121
#include "ec_operations.hpp"
22-
#include "ecdsa_secp256k1.hpp"
23-
#include "ecdsa_secp256r1.hpp"
22+
#include "ecdsa_constraints.hpp"
2423
#include "honk_recursion_constraint.hpp"
2524
#include "keccak_constraint.hpp"
2625
#include "logic_constraint.hpp"
@@ -89,8 +88,8 @@ struct AcirFormat {
8988
std::vector<RangeConstraint> range_constraints;
9089
std::vector<AES128Constraint> aes128_constraints;
9190
std::vector<Sha256Compression> sha256_compression;
92-
std::vector<EcdsaSecp256k1Constraint> ecdsa_k1_constraints;
93-
std::vector<EcdsaSecp256r1Constraint> ecdsa_r1_constraints;
91+
std::vector<EcdsaConstraint> ecdsa_k1_constraints;
92+
std::vector<EcdsaConstraint> ecdsa_r1_constraints;
9493
std::vector<Blake2sConstraint> blake2s_constraints;
9594
std::vector<Blake3Constraint> blake3_constraints;
9695
std::vector<Keccakf1600> keccak_permutations;

barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
#include "barretenberg/op_queue/ecc_op_queue.hpp"
99

1010
#include "barretenberg/serialize/test_helper.hpp"
11-
#include "ecdsa_secp256k1.hpp"
1211

1312
using namespace bb;
1413
using namespace bb::crypto;

barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "barretenberg/common/container.hpp"
1818
#include "barretenberg/common/map.hpp"
1919
#include "barretenberg/common/throw_or_abort.hpp"
20+
#include "barretenberg/dsl/acir_format/ecdsa_constraints.hpp"
2021
#include "barretenberg/dsl/acir_format/recursion_constraint.hpp"
2122
#include "barretenberg/honk/execution_trace/gate_data.hpp"
2223
#include "barretenberg/numeric/uint256/uint256.hpp"
@@ -651,7 +652,7 @@ void handle_blackbox_func_call(Acir::Opcode::BlackBoxFuncCall const& arg, AcirFo
651652
}
652653
af.original_opcode_indices.blake3_constraints.push_back(opcode_index);
653654
} else if constexpr (std::is_same_v<T, Acir::BlackBoxFuncCall::EcdsaSecp256k1>) {
654-
af.ecdsa_k1_constraints.push_back(EcdsaSecp256k1Constraint{
655+
af.ecdsa_k1_constraints.push_back(EcdsaConstraint{
655656
.hashed_message =
656657
transform::map(*arg.hashed_message, [](auto& e) { return get_witness_from_function_input(e); }),
657658
.signature =
@@ -665,16 +666,16 @@ void handle_blackbox_func_call(Acir::Opcode::BlackBoxFuncCall const& arg, AcirFo
665666
af.constrained_witness.insert(af.ecdsa_k1_constraints.back().result);
666667
af.original_opcode_indices.ecdsa_k1_constraints.push_back(opcode_index);
667668
} else if constexpr (std::is_same_v<T, Acir::BlackBoxFuncCall::EcdsaSecp256r1>) {
668-
af.ecdsa_r1_constraints.push_back(EcdsaSecp256r1Constraint{
669+
af.ecdsa_r1_constraints.push_back(EcdsaConstraint{
669670
.hashed_message =
670671
transform::map(*arg.hashed_message, [](auto& e) { return get_witness_from_function_input(e); }),
672+
.signature =
673+
transform::map(*arg.signature, [](auto& e) { return get_witness_from_function_input(e); }),
671674
.pub_x_indices =
672675
transform::map(*arg.public_key_x, [](auto& e) { return get_witness_from_function_input(e); }),
673676
.pub_y_indices =
674677
transform::map(*arg.public_key_y, [](auto& e) { return get_witness_from_function_input(e); }),
675678
.result = arg.output.value,
676-
.signature =
677-
transform::map(*arg.signature, [](auto& e) { return get_witness_from_function_input(e); }),
678679
});
679680
af.constrained_witness.insert(af.ecdsa_r1_constraints.back().result);
680681
af.original_opcode_indices.ecdsa_r1_constraints.push_back(opcode_index);

barretenberg/cpp/src/barretenberg/dsl/acir_format/avm2_recursion_constraint.test.cpp

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "barretenberg/dsl/acir_format/acir_format_mocks.hpp"
66
#include "barretenberg/dsl/acir_format/avm2_recursion_constraint.hpp"
77
#include "barretenberg/dsl/acir_format/proof_surgeon.hpp"
8+
#include "barretenberg/dsl/acir_format/utils.hpp"
89
#include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp"
910
#include "barretenberg/ultra_honk/decider_keys.hpp"
1011
#include "barretenberg/ultra_honk/ultra_prover.hpp"
@@ -80,23 +81,10 @@ class AcirAvm2RecursionConstraint : public ::testing::Test {
8081
const std::vector<fr> proof_witnesses = inner_circuit_data.proof;
8182
const std::vector<fr> public_inputs_witnesses = inner_circuit_data.public_inputs_flat;
8283

83-
// Helper to append some values to the witness vector and return their corresponding indices
84-
auto add_to_witness_and_track_indices =
85-
[&witness](const std::vector<bb::fr>& input) -> std::vector<uint32_t> {
86-
std::vector<uint32_t> indices;
87-
indices.reserve(input.size());
88-
auto witness_idx = static_cast<uint32_t>(witness.size());
89-
for (const auto& value : input) {
90-
witness.push_back(value);
91-
indices.push_back(witness_idx++);
92-
}
93-
return indices;
94-
};
95-
9684
RecursionConstraint avm_recursion_constraint{
97-
.key = add_to_witness_and_track_indices(key_witnesses),
98-
.proof = add_to_witness_and_track_indices(proof_witnesses),
99-
.public_inputs = add_to_witness_and_track_indices(public_inputs_witnesses),
85+
.key = add_to_witness_and_track_indices<bb::fr>(witness, key_witnesses),
86+
.proof = add_to_witness_and_track_indices<bb::fr>(witness, proof_witnesses),
87+
.public_inputs = add_to_witness_and_track_indices<bb::fr>(witness, public_inputs_witnesses),
10088
.key_hash = 0, // not used
10189
.proof_type = AVM,
10290
};
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// === AUDIT STATUS ===
2+
// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3+
// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4+
// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5+
// =====================
6+
7+
#include "barretenberg/dsl/acir_format/ecdsa_constraints.hpp"
8+
#include "barretenberg/dsl/acir_format/utils.hpp"
9+
#include "barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp"
10+
#include "barretenberg/stdlib/primitives/curves/secp256k1.hpp"
11+
#include "barretenberg/stdlib/primitives/curves/secp256r1.hpp"
12+
13+
namespace acir_format {
14+
15+
using namespace bb;
16+
17+
/**
18+
* @brief Create constraints to verify an ECDSA signature
19+
*
20+
* @details Given and ECDSA constraint system, add to the builder constraints that verify the ECDSA signature. We
21+
* perform the following operations:
22+
* 1. Reconstruct byte arrays from builder variables (we enforce that each variable fits in one byte and stack them in
23+
* a vector) and the boolean result from the corresponding builder variable
24+
* 2. Reconstruct the public key from the byte representations (big-endian, 32-byte numbers) of the \f$x\f$ and \f$y\f$
25+
* coordinates.
26+
* 3. Enforce uniqueness of the representation of the public key by asserting \f$x < q\f$ and \f$y < q\f$, where
27+
* \f$q\f$ is the modulus of the base field of the elliptic curve we are working with.
28+
* 4. Verify the signature against the public key and the hash of the message. We return a bool_t bearing witness to
29+
* whether the signature verification was successfull or not.
30+
* 5. Enforce that the result of the signature verification matches the expected result.
31+
*
32+
* @tparam Curve
33+
* @param builder
34+
* @param input
35+
* @param has_valid_witness_assignments
36+
*/
37+
template <typename Curve>
38+
void create_ecdsa_verify_constraints(typename Curve::Builder& builder,
39+
const EcdsaConstraint& input,
40+
bool has_valid_witness_assignments)
41+
{
42+
using Builder = Curve::Builder;
43+
44+
using Fq = Curve::fq_ct;
45+
using Fr = Curve::bigfr_ct;
46+
using G1 = Curve::g1_bigfr_ct;
47+
48+
using field_ct = bb::stdlib::field_t<Builder>;
49+
using bool_ct = bb::stdlib::bool_t<Builder>;
50+
using byte_array_ct = bb::stdlib::byte_array<Builder>;
51+
52+
// Lambda to convert std::vector<field_ct> to byte_array_ct
53+
auto fields_to_bytes = [](Builder& builder, std::vector<field_ct>& fields) -> byte_array_ct {
54+
byte_array_ct result(&builder);
55+
for (auto& field : fields) {
56+
// Construct byte array of length 1 from the field element
57+
// The constructor enforces that `field` fits in one byte
58+
byte_array_ct byte_to_append(field, /*num_bytes=*/1);
59+
// Append the new byte to the result
60+
result.write(byte_to_append);
61+
}
62+
63+
return result;
64+
};
65+
66+
// Define builder variables based on the witness indices
67+
std::vector<field_ct> hashed_message_fields = fields_from_witnesses(builder, input.hashed_message);
68+
std::vector<field_ct> r_fields = fields_from_witnesses(builder, std::span(input.signature.begin(), 32));
69+
std::vector<field_ct> s_fields = fields_from_witnesses(builder, std::span(input.signature.begin() + 32, 32));
70+
std::vector<field_ct> pub_x_fields = fields_from_witnesses(builder, input.pub_x_indices);
71+
std::vector<field_ct> pub_y_fields = fields_from_witnesses(builder, input.pub_y_indices);
72+
field_ct result_field = field_ct::from_witness_index(&builder, input.result);
73+
74+
if (!has_valid_witness_assignments) {
75+
// Fill builder variables in case of empty witness assignment
76+
create_dummy_ecdsa_constraint<Curve>(
77+
builder, hashed_message_fields, r_fields, s_fields, pub_x_fields, pub_y_fields, result_field);
78+
}
79+
80+
// Step 1.
81+
// Construct inputs to signature verification from witness indices
82+
byte_array_ct hashed_message = fields_to_bytes(builder, hashed_message_fields);
83+
byte_array_ct pub_x_bytes = fields_to_bytes(builder, pub_x_fields);
84+
byte_array_ct pub_y_bytes = fields_to_bytes(builder, pub_y_fields);
85+
byte_array_ct r = fields_to_bytes(builder, r_fields);
86+
byte_array_ct s = fields_to_bytes(builder, s_fields);
87+
bool_ct result = static_cast<bool_ct>(result_field); // Constructor enforces result_field = 0 or 1
88+
89+
// Step 2.
90+
// Reconstruct the public key from the byte representations of its coordinates
91+
Fq pub_x(pub_x_bytes);
92+
Fq pub_y(pub_y_bytes);
93+
G1 public_key(pub_x, pub_y);
94+
95+
// Step 3.
96+
// Ensure uniqueness of the public key by asserting each of its coordinates is smaller than the modulus of the base
97+
// field
98+
pub_x.assert_is_in_field();
99+
pub_y.assert_is_in_field();
100+
101+
// Step 4.
102+
bool_ct signature_result =
103+
stdlib::ecdsa_verify_signature<Builder, Curve, Fq, Fr, G1>(hashed_message, public_key, { r, s });
104+
105+
// Step 5.
106+
// Assert that signature verification returned the expected result
107+
signature_result.assert_equal(result);
108+
}
109+
110+
/**
111+
* @brief Generate dummy ECDSA constraints when the builder doesn't have witnesses
112+
*
113+
* @details To avoid firing asserts, the public key must be a point on the curve
114+
*/
115+
template <typename Curve>
116+
void create_dummy_ecdsa_constraint(typename Curve::Builder& builder,
117+
const std::vector<stdlib::field_t<typename Curve::Builder>>& hashed_message_fields,
118+
const std::vector<stdlib::field_t<typename Curve::Builder>>& r_fields,
119+
const std::vector<stdlib::field_t<typename Curve::Builder>>& s_fields,
120+
const std::vector<stdlib::field_t<typename Curve::Builder>>& pub_x_fields,
121+
const std::vector<stdlib::field_t<typename Curve::Builder>>& pub_y_fields,
122+
const stdlib::field_t<typename Curve::Builder>& result_field)
123+
{
124+
using Builder = Curve::Builder;
125+
using FqNative = Curve::fq;
126+
using G1Native = Curve::g1;
127+
using field_ct = stdlib::field_t<Builder>;
128+
129+
// Lambda to populate builder variables from vector of field values
130+
auto populate_fields = [&builder](const std::vector<field_ct>& fields, const std::vector<bb::fr>& values) {
131+
for (auto [field, value] : zip_view(fields, values)) {
132+
builder.set_variable(field.witness_index, value);
133+
}
134+
};
135+
136+
// Vector of 32 copies of bb::fr::zero()
137+
std::vector<bb::fr> mock_zeros(32, bb::fr::zero());
138+
139+
// Hashed message
140+
populate_fields(hashed_message_fields, mock_zeros);
141+
142+
// Signature
143+
populate_fields(r_fields, mock_zeros);
144+
populate_fields(s_fields, mock_zeros);
145+
146+
// Pub key
147+
std::array<uint8_t, 32> buffer_x;
148+
std::array<uint8_t, 32> buffer_y;
149+
std::vector<bb::fr> mock_pub_x;
150+
std::vector<bb::fr> mock_pub_y;
151+
FqNative::serialize_to_buffer(G1Native::one.x, &buffer_x[0]);
152+
FqNative::serialize_to_buffer(G1Native::one.y, &buffer_y[0]);
153+
for (auto [byte_x, byte_y] : zip_view(buffer_x, buffer_y)) {
154+
mock_pub_x.emplace_back(bb::fr(byte_x));
155+
mock_pub_y.emplace_back(bb::fr(byte_y));
156+
}
157+
populate_fields(pub_x_fields, mock_pub_x);
158+
populate_fields(pub_y_fields, mock_pub_y);
159+
160+
// Result
161+
builder.set_variable(result_field.witness_index, bb::fr::one());
162+
}
163+
164+
template void create_ecdsa_verify_constraints<stdlib::secp256k1<UltraCircuitBuilder>>(
165+
UltraCircuitBuilder& builder, const EcdsaConstraint& input, bool has_valid_witness_assignments);
166+
template void create_ecdsa_verify_constraints<stdlib::secp256k1<MegaCircuitBuilder>>(
167+
MegaCircuitBuilder& builder, const EcdsaConstraint& input, bool has_valid_witness_assignments);
168+
template void create_ecdsa_verify_constraints<stdlib::secp256r1<UltraCircuitBuilder>>(
169+
UltraCircuitBuilder& builder, const EcdsaConstraint& input, bool has_valid_witness_assignments);
170+
template void create_ecdsa_verify_constraints<stdlib::secp256r1<MegaCircuitBuilder>>(
171+
MegaCircuitBuilder& builder, const EcdsaConstraint& input, bool has_valid_witness_assignments);
172+
173+
template void create_dummy_ecdsa_constraint<stdlib::secp256k1<UltraCircuitBuilder>>(
174+
UltraCircuitBuilder&,
175+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
176+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
177+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
178+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
179+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
180+
const stdlib::field_t<UltraCircuitBuilder>&);
181+
182+
template void create_dummy_ecdsa_constraint<stdlib::secp256r1<UltraCircuitBuilder>>(
183+
UltraCircuitBuilder&,
184+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
185+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
186+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
187+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
188+
const std::vector<stdlib::field_t<UltraCircuitBuilder>>&,
189+
const stdlib::field_t<UltraCircuitBuilder>&);
190+
191+
} // namespace acir_format

0 commit comments

Comments
 (0)