-
Notifications
You must be signed in to change notification settings - Fork 598
chore: field_conversion clean-up/int audit
#16898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9206325
7d1760a
abe1b42
0c8f5e6
d2e6bc8
c377bdd
acdaa51
a63559d
af8117b
5bfb6f6
0ca0024
f21bb97
3e792a5
74fcd3d
3d4efb4
883d484
f7e2154
3c37128
a0dfb28
95c68fd
6e9b48b
0ed5343
e0b240c
27c18bb
5436aca
a84c115
599e0fc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -139,7 +139,7 @@ class ECCVMRecursiveTests : public ::testing::Test { | |
| } | ||
|
|
||
| // Check that the size of the recursive verifier is consistent with historical expectation | ||
| uint32_t NUM_GATES_EXPECTED = 213923; | ||
| uint32_t NUM_GATES_EXPECTED = 213775; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cheaper point at infinity checks + cheaper fr -> bigfield conversion |
||
| ASSERT_EQ(static_cast<uint32_t>(outer_circuit.get_num_finalized_gates()), NUM_GATES_EXPECTED) | ||
| << "Ultra-arithmetized ECCVM Recursive verifier gate count changed! Update this value if you are sure this " | ||
| "is expected."; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,16 +25,55 @@ class GoblinRecursiveVerifierTests : public testing::Test { | |
| using RecursiveCommitment = GoblinRecursiveVerifier::MergeVerifier::Commitment; | ||
| using MergeCommitments = MergeVerifier::InputCommitments; | ||
| using RecursiveMergeCommitments = GoblinRecursiveVerifier::MergeVerifier::InputCommitments; | ||
|
|
||
| using FF = TranslatorFlavor::FF; | ||
| using BF = TranslatorFlavor::BF; | ||
| static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); } | ||
|
|
||
| // Compute the size of a Translator commitment (in bb::fr's) | ||
| static constexpr size_t comm_frs = bb::field_conversion::calc_num_bn254_frs<Commitment>(); // 4 | ||
| static constexpr size_t eval_frs = bb::field_conversion::calc_num_bn254_frs<FF>(); // 1 | ||
|
|
||
| // The `op` wire commitment is currently the second element of the proof, following the | ||
| // `accumulated_result` which is a BN254 BaseField element. | ||
| static constexpr size_t offset = bb::field_conversion::calc_num_bn254_frs<BF>(); | ||
|
|
||
| struct ProverOutput { | ||
| GoblinProof proof; | ||
| Goblin::VerificationKey verifier_input; | ||
| MergeCommitments merge_commitments; | ||
| RecursiveMergeCommitments recursive_merge_commitments; | ||
| }; | ||
| // TODO(https://github.com/AztecProtocol/barretenberg/issues/1298): | ||
| // Better recursion testing - create more flexible proof tampering tests. | ||
| // Modify the `op` commitment which a part of the Merge protocol. | ||
| static void tamper_with_op_commitment(HonkProof& translator_proof) | ||
| { | ||
|
|
||
| // Extract `op` fields and convert them to a Commitment object | ||
| auto element_frs = std::span{ translator_proof }.subspan(offset, comm_frs); | ||
| auto op_commitment = NativeTranscriptParams::template deserialize<Commitment>(element_frs); | ||
| // Modify the commitment | ||
| op_commitment = op_commitment * FF(2); | ||
| // Serialize the tampered commitment into the proof (overwriting the valid one). | ||
| auto op_commitment_reserialized = bb::NativeTranscriptParams::serialize(op_commitment); | ||
| std::copy(op_commitment_reserialized.begin(), | ||
| op_commitment_reserialized.end(), | ||
| translator_proof.begin() + static_cast<std::ptrdiff_t>(offset)); | ||
| }; | ||
|
|
||
| // Translator proof ends with [..., Libra:quotient_eval, Shplonk:Q, KZG:W]. We invalidate the proof by multiplying | ||
| // the eval by 2 (it leads to a Libra consistency check failure). | ||
| static void tamper_with_libra_eval(HonkProof& translator_proof) | ||
| { | ||
| // Proof tail size | ||
| static constexpr size_t tail_size = 2 * comm_frs + eval_frs; // 2*4 + 1 = 9 | ||
|
|
||
| // Index of the target field (one fr) from the beginning | ||
| const size_t idx = translator_proof.size() - tail_size; | ||
|
|
||
| // Tamper: multiply by 2 (or tweak however you like) | ||
| translator_proof[idx] = translator_proof[idx] + translator_proof[idx]; | ||
| }; | ||
| /** | ||
| * @brief Create a goblin proof and the VM verification keys needed by the goblin recursive verifier | ||
| * | ||
|
|
@@ -208,13 +247,7 @@ TEST_F(GoblinRecursiveVerifierTests, TranslatorFailure) | |
| // Tamper with the Translator proof preamble | ||
| { | ||
| GoblinProof tampered_proof = proof; | ||
| for (auto& val : tampered_proof.translator_proof) { | ||
| if (val > 0) { // tamper by finding the first non-zero value and incrementing it by 1 | ||
| val += 1; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| tamper_with_op_commitment(tampered_proof.translator_proof); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the original code would simply trigger a non-circuit ASSERT, since a limb of a comm is modified --> "point" is not on bn254, using serde to mutiply the comm by 2 |
||
| Builder builder; | ||
|
|
||
| RecursiveMergeCommitments recursive_merge_commitments; | ||
|
|
@@ -232,18 +265,10 @@ TEST_F(GoblinRecursiveVerifierTests, TranslatorFailure) | |
| verifier.verify(tampered_proof, recursive_merge_commitments, MergeSettings::APPEND); | ||
| EXPECT_FALSE(CircuitChecker::check(builder)); | ||
| } | ||
| // Tamper with the Translator proof non-preamble values | ||
| // Tamper with the Translator proof non - preamble values | ||
| { | ||
| auto tampered_proof = proof; | ||
| int seek = 10; | ||
| for (auto& val : tampered_proof.translator_proof) { | ||
| if (val > 0) { // tamper by finding the tenth non-zero value and incrementing it by 1 | ||
| if (--seek == 0) { | ||
| val += 1; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| tamper_with_libra_eval(tampered_proof.translator_proof); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the same here, the tampering before would simply invalidate |
||
|
|
||
| Builder builder; | ||
|
|
||
|
|
@@ -296,9 +321,6 @@ TEST_F(GoblinRecursiveVerifierTests, TranslatorMergeConsistencyFailure) | |
| { | ||
|
|
||
| { | ||
| using Commitment = TranslatorFlavor::Commitment; | ||
| using FF = TranslatorFlavor::FF; | ||
| using BF = TranslatorFlavor::BF; | ||
|
|
||
| Builder builder; | ||
|
|
||
|
|
@@ -310,27 +332,6 @@ TEST_F(GoblinRecursiveVerifierTests, TranslatorMergeConsistencyFailure) | |
| // Check natively that the proof is correct. | ||
| EXPECT_TRUE(Goblin::verify(proof, merge_commitments, verifier_transcript, MergeSettings::APPEND)); | ||
|
|
||
| // TODO(https://github.com/AztecProtocol/barretenberg/issues/1298): | ||
| // Better recursion testing - create more flexible proof tampering tests. | ||
| // Modify the `op` commitment which a part of the Merge protocol. | ||
| auto tamper_with_op_commitment = [](HonkProof& translator_proof) { | ||
| // Compute the size of a Translator commitment (in bb::fr's) | ||
| static constexpr size_t num_frs_comm = bb::field_conversion::calc_num_bn254_frs<Commitment>(); | ||
| // The `op` wire commitment is currently the second element of the proof, following the | ||
| // `accumulated_result` which is a BN254 BaseField element. | ||
| static constexpr size_t offset = bb::field_conversion::calc_num_bn254_frs<BF>(); | ||
| // Extract `op` fields and convert them to a Commitment object | ||
| auto element_frs = std::span{ translator_proof }.subspan(offset, num_frs_comm); | ||
| auto op_commitment = NativeTranscriptParams::template deserialize<Commitment>(element_frs); | ||
| // Modify the commitment | ||
| op_commitment = op_commitment * FF(2); | ||
| // Serialize the tampered commitment into the proof (overwriting the valid one). | ||
| auto op_commitment_reserialized = bb::NativeTranscriptParams::serialize(op_commitment); | ||
| std::copy(op_commitment_reserialized.begin(), | ||
| op_commitment_reserialized.end(), | ||
| translator_proof.begin() + static_cast<std::ptrdiff_t>(offset)); | ||
| }; | ||
|
|
||
| tamper_with_op_commitment(proof.translator_proof); | ||
| // Construct and check the Goblin Recursive Verifier circuit | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -293,7 +293,7 @@ template <typename RecursiveFlavor> class RecursiveVerifierTest : public testing | |
| } | ||
| // Check the size of the recursive verifier | ||
| if constexpr (std::same_as<RecursiveFlavor, MegaZKRecursiveFlavor_<UltraCircuitBuilder>>) { | ||
| uint32_t NUM_GATES_EXPECTED = 796978; | ||
| uint32_t NUM_GATES_EXPECTED = 801953; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as a result of enabled |
||
| ASSERT_EQ(static_cast<uint32_t>(outer_circuit.get_num_finalized_gates()), NUM_GATES_EXPECTED) | ||
| << "MegaZKHonk Recursive verifier changed in Ultra gate count! Update this value if you " | ||
| "are sure this is expected."; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,7 @@ template <class Builder_, class Fq, class Fr, class NativeGroup> class element { | |
| element(); | ||
| element(const typename NativeGroup::affine_element& input); | ||
| element(const Fq& x, const Fq& y); | ||
| element(const Fq& x, const Fq& y, const bool_ct& is_infinity); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. feels like a very natural constructor, here and in other groups |
||
|
|
||
| element(const element& other); | ||
| element(element&& other) noexcept; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,46 +5,43 @@ | |
| // ===================== | ||
|
|
||
| #include "barretenberg/stdlib/primitives/field/field_conversion.hpp" | ||
| #include "barretenberg/common/assert.hpp" | ||
|
|
||
| namespace bb::stdlib::field_conversion { | ||
|
|
||
| /** | ||
| * @brief Converts a challenge to a fq<Builder> | ||
| * @details We sometimes need challenges that are a bb::fq element, so we need to convert the bb::fr challenge to a | ||
| * bb::fq type. We do this by in a similar fashion to the convert_from_bn254_frs function that converts to a | ||
| * fq<Builder>. In fact, we do call that function that the end, but we first have to split the fr<Builder> into two | ||
| * pieces, one that is the 136 lower bits and one that is the 118 higher bits. Then, we can split these two pieces into | ||
| * their bigfield limbs through convert_from_bn254_frs, which is actually just a bigfield constructor that takes in two | ||
| * two-limb frs. | ||
| * @brief Converts an in-circuit `fr`element to an `fq`, i.e. `field_t` --> `bigfield`. | ||
| * | ||
| * TODO(https://github.com/AztecProtocol/barretenberg/issues/850): audit this function more carefully | ||
| * @details Our circuit builders are `fr`-native, which results in challenges being `field_t` elements. However, | ||
| * ECCVMRecursiveVerifier and IPA Recursive Verification need challenges that are `bigfield` elements. We do this in | ||
| * a similar fashion to the `convert_from_bn254_frs` function that converts to a `bigfield`. We split the `field_t` | ||
| * into two pieces, one that is the 136 lower bits and one that is the 118 higher bits, assert the correctness of the | ||
| * decomposition, and invoke the `bigfield` constructor. | ||
| * @tparam Builder | ||
| */ | ||
| template <typename Builder> fq<Builder> convert_to_grumpkin_fr(Builder& builder, const fr<Builder>& f) | ||
| template <typename Builder> fq<Builder> convert_to_grumpkin_fr(Builder& builder, const fr<Builder>& fr_element) | ||
| { | ||
| constexpr uint64_t NUM_BITS_IN_TWO_LIMBS = 2 * NUM_LIMB_BITS; // 136 | ||
| constexpr uint64_t UPPER_TWO_LIMB_BITS = TOTAL_BITS - NUM_BITS_IN_TWO_LIMBS; // 118 | ||
| ASSERT(!fr_element.is_constant()); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ledwards2225 after our discussions, i think this should use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually merged your changes from |
||
| static constexpr uint64_t NUM_LIMB_BITS = fq<Builder>::NUM_LIMB_BITS; | ||
|
|
||
| constexpr uint64_t NUM_BITS_IN_TWO_LIMBS = 2 * NUM_LIMB_BITS; // 136 | ||
|
|
||
| constexpr uint256_t shift = (uint256_t(1) << NUM_BITS_IN_TWO_LIMBS); | ||
| // split f into low_bits_in and high_bits_in | ||
| constexpr uint256_t LIMB_MASK = shift - 1; // mask for upper 128 bits | ||
| const uint256_t value = f.get_value(); | ||
| const uint256_t value = fr_element.get_value(); | ||
| const uint256_t low_val = static_cast<uint256_t>(value & LIMB_MASK); | ||
| const uint256_t hi_val = static_cast<uint256_t>(value >> NUM_BITS_IN_TWO_LIMBS); | ||
|
|
||
| fr<Builder> low{ witness_t<Builder>(&builder, low_val) }; | ||
| fr<Builder> hi{ witness_t<Builder>(&builder, hi_val) }; | ||
| // range constrain low to 136 bits and hi to 118 bits | ||
| builder.create_range_constraint(low.witness_index, NUM_BITS_IN_TWO_LIMBS, "create_range_constraint"); | ||
| builder.create_range_constraint(hi.witness_index, UPPER_TWO_LIMB_BITS, "create_range_constraint"); | ||
|
|
||
| BB_ASSERT_EQ(static_cast<uint256_t>(low_val) + (static_cast<uint256_t>(hi_val) << NUM_BITS_IN_TWO_LIMBS), value); | ||
| // checks this decomposition low + hi * 2^64 = value with an assert_equal | ||
| auto sum = low + hi * shift; | ||
| builder.assert_equal(f.witness_index, sum.witness_index, "assert_equal"); | ||
| BB_ASSERT_EQ(static_cast<uint256_t>(low_val) + (static_cast<uint256_t>(hi_val) << NUM_BITS_IN_TWO_LIMBS), | ||
| value, | ||
| "field_conversion: limb decomposition"); | ||
| // check the decomposition low + hi * 2^136 = value in circuit | ||
| const fr<Builder> zero = fr<Builder>::from_witness_index(&builder, builder.zero_idx); | ||
| fr<Builder>::evaluate_linear_identity(hi * shift, low, -fr_element, zero); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minus 1 gate thanks to combining the addition with constraining to be 0
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice! |
||
|
|
||
| std::vector<fr<Builder>> fr_vec{ low, hi }; | ||
| return convert_from_bn254_frs<Builder, fq<Builder>>(builder, fr_vec); | ||
| return fq<Builder>(low, hi); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not calling |
||
| } | ||
|
|
||
| template fq<UltraCircuitBuilder> convert_to_grumpkin_fr<UltraCircuitBuilder>(UltraCircuitBuilder& builder, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and in several places below,
builderis redundant asfield_conversioncan extract from the fields being passed while ensuring that those are in-circuit.