Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
poseidon2.quad_internal_soundness.test.cpp
Go to the documentation of this file.
1// Regression tests for the Mega Poseidon2 quad-internal layout and boundary soundness.
2//
3// The Mega Poseidon2 permutation uses a compressed internal block with an entry transition
4// row (standard -> compressed) and a terminal row (compressed -> standard). The transition
5// rows are tied to the surrounding standard-encoded states via copy constraints and shifted
6// wires:
7//
8// - Entry (q_poseidon2_transition_entry):
9// w_r_shift - D_1 (w_l + q_l)^5 - w_r - w_o - w_4 = 0
10// ties the first compressed row's `w_r` (= v_0 = state[0] one round ahead) to the
11// standard `s_1` at round `rounds_f_begin`.
12//
13// - Terminal (q_poseidon2_quad_internal_terminal):
14// out_k - w_{k,shift} = 0 for k in {0, 1, 2, 3}
15// ties the compressed chain's computed state at round `p_end` to the selector-unconstrained
16// standard bridge row consumed by the final external rounds via shared witness indices.
17//
18// CircuitChecker iterates row-major-then-relation-major and short-circuits on the first
19// failing relation. This means a corruption that would in principle break multiple relations
20// is reported as breaking the first one the checker reaches; the tests below note the
21// expected first-detector where it matters.
22
29
30#include <gtest/gtest.h>
31
32using namespace bb;
33
34namespace {
35
36class Poseidon2QuadInternalSoundnessTests : public ::testing::Test {
37 public:
39 using FF = MegaFlavor::FF;
40 // Quad-internal block layout produced by stdlib::Poseidon2Permutation on Mega:
41 // row 0 : entry transition (standard encoding)
42 // rows 1 .. 13 : interior compressed rows
43 // row 14 : terminal compressed row
44 // row 15 : standard transition row (selector-unconstrained, copy-constrained to final external rounds)
45 static constexpr size_t quad_entry_row = 0;
46 static constexpr size_t quad_first_interior_row = 1;
47
48 // Build an honest Poseidon2 circuit: hashes a single fixed field element through the
49 // `Poseidon2Permutation::permutation` call used by the stdlib.
50 static std::unique_ptr<Builder> build_honest_permutation(const FF& input_value)
51 {
52 auto builder = std::make_unique<Builder>(std::make_shared<ECCOpQueue>(), /*is_write_vk_mode=*/true);
54 State input{
59 };
61 return builder;
62 }
63};
64
65TEST_F(Poseidon2QuadInternalSoundnessTests, DoesNotMaterializeUnusedNonTerminalStateLimbs)
66{
67 auto builder = std::make_unique<Builder>(std::make_shared<ECCOpQueue>(), /*is_write_vk_mode=*/true);
68 const size_t initial_num_variables = builder->get_num_variables();
69
71 State input{
76 };
78
79 // Initial input witnesses + initial-linear-layer output + first-half external-round outputs +
80 // compressed internal witnesses + final-half external-round outputs.
81 //
82 // The non-terminal compressed rows only need state[0] at the next row; state[1..3] are derived by
83 // the relation and are not materialized until the terminal row bridges back to standard encoding.
84 constexpr size_t input_witnesses = 4;
85 constexpr size_t initial_external_output_witnesses = 4;
86 constexpr size_t external_output_witnesses = 8 * 4;
87 constexpr size_t compressed_intermediate_witnesses = 14 * 3;
88 constexpr size_t compressed_next_state_zero_witnesses = 14;
89 constexpr size_t compressed_terminal_standard_limbs = 3;
90 constexpr size_t expected_num_variables = input_witnesses + initial_external_output_witnesses +
91 external_output_witnesses + compressed_intermediate_witnesses +
92 compressed_next_state_zero_witnesses + compressed_terminal_standard_limbs;
93
94 EXPECT_EQ(builder->get_num_variables() - initial_num_variables, expected_num_variables);
95 EXPECT_TRUE(CircuitChecker::check(*builder));
96}
97
98// Entry boundary: tampering the first compressed row's `w_r` (= intermediate_s0) breaks the
99// entry-transition relation, which enforces
100// w_r_shift = D_1 (s_0 + c)^5 + s_1 + s_2 + s_3
101// on the entry row (w_r_shift lives in the first compressed row and is the tampered witness).
102TEST_F(Poseidon2QuadInternalSoundnessTests, EntryBoundaryRejectsTamperedIntermediateS0)
103{
104 auto builder = build_honest_permutation(FF(uint256_t(0x1234ULL)));
105 ASSERT_TRUE(CircuitChecker::check(*builder));
106
107 auto& quad = builder->blocks.poseidon2_quad_internal;
108 // Shift the first interior compressed row's w_r (= intermediate_s0) by a nonzero delta.
109 const uint32_t w_r_idx = quad.w_r()[quad_first_interior_row];
110 builder->set_variable(w_r_idx, builder->get_variable(w_r_idx) + FF(1));
111
112 EXPECT_FALSE(CircuitChecker::check(*builder));
113}
114
115// Entry boundary: tampering the entry row's `w_r` (= standard s_1 at round rounds_f_begin)
116// breaks the entry-transition relation as well. `w_r` of the entry row is copy-constrained
117// to the external block's propagate row; modifying it invalidates both the external chain
118// and the entry relation.
119TEST_F(Poseidon2QuadInternalSoundnessTests, EntryBoundaryRejectsTamperedStateOne)
120{
121 auto builder = build_honest_permutation(FF(uint256_t(0xabcdULL)));
122 ASSERT_TRUE(CircuitChecker::check(*builder));
123
124 auto& quad = builder->blocks.poseidon2_quad_internal;
125 const uint32_t w_r_idx = quad.w_r()[quad_entry_row];
126 builder->set_variable(w_r_idx, builder->get_variable(w_r_idx) + FF(7));
127
128 EXPECT_FALSE(CircuitChecker::check(*builder));
129}
130
131// Exit boundary: the standard bridge row (last row of poseidon2_quad_internal) holds `state[1]`
132// at round p_end in its `w_r`. Shifting that witness breaks the terminal relation, which enforces
133// out_1 (computed by the last compressed row) == w_r_shift (the bridge row's w_r).
134TEST_F(Poseidon2QuadInternalSoundnessTests, ExitBoundaryRejectsTamperedStateOne)
135{
136 auto builder = build_honest_permutation(FF(uint256_t(0xcafebabeULL)));
137 ASSERT_TRUE(CircuitChecker::check(*builder));
138
139 auto& quad = builder->blocks.poseidon2_quad_internal;
140 // Last row of the quad-internal block is the standard transition row holding
141 // (s_0, s_1, s_2, s_3) at round p_end in standard encoding.
142 const size_t quad_std_transition_row = quad.size() - 1;
143 const uint32_t state1_idx = quad.w_r()[quad_std_transition_row];
144 builder->set_variable(state1_idx, builder->get_variable(state1_idx) + FF(1));
145
146 EXPECT_FALSE(CircuitChecker::check(*builder));
147}
148
149// Interior chain: corrupting any wire on an interior compressed row breaks the chain's
150// quad-internal relation locally.
151TEST_F(Poseidon2QuadInternalSoundnessTests, InteriorRelationRejectsTamperedWire)
152{
153 auto builder = build_honest_permutation(FF(uint256_t(0xfeedf00dULL)));
154 ASSERT_TRUE(CircuitChecker::check(*builder));
155
156 auto& quad = builder->blocks.poseidon2_quad_internal;
157 // Pick some middle interior row.
158 const size_t interior_row = quad_first_interior_row + 5;
159 const uint32_t w_o_idx = quad.w_o()[interior_row];
160 builder->set_variable(w_o_idx, builder->get_variable(w_o_idx) + FF(1));
161
162 EXPECT_FALSE(CircuitChecker::check(*builder));
163}
164
165// Cross-row encoding test: the interior subrelations A_1, A_2, A_3 compare row i's predicted
166// (out_1, out_2, out_3) against row i+1's reconstructed Vandermonde encoding (b_1', b_2',
167// b_3'), where b_k' is built from row i+1's lane-0 chain. Tampering an interior row's wire
168// perturbs that reconstruction at the *previous* row without touching the previous row's
169// own committed wires — exercising the bijectivity-of-V mechanism that lets the relation
170// compare uncommitted hidden lanes. The tampered wire is also row i+1's own committed wire,
171// so the tamper would also break row i+1's own relation; CircuitChecker may report either
172// site, but both are exercising the same Vandermonde-encoding equality.
173TEST_F(Poseidon2QuadInternalSoundnessTests, CrossRowVandermondeEncodingMismatchRejected)
174{
175 auto builder = build_honest_permutation(FF(uint256_t(0xCAFE1234ULL)));
176 ASSERT_TRUE(CircuitChecker::check(*builder));
177
178 auto& quad = builder->blocks.poseidon2_quad_internal;
179 const size_t row_i_plus_1 = quad_first_interior_row + 6;
180 const uint32_t idx = quad.w_o()[row_i_plus_1];
181 builder->set_variable(idx, builder->get_variable(idx) + FF(1));
182
183 EXPECT_FALSE(CircuitChecker::check(*builder));
184}
185
186} // namespace
Curve::ScalarField FF
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
Circuit form of Poseidon2 permutation from https://eprint.iacr.org/2023/323.
std::array< field_t< Builder >, t > State
AluTraceBuilder builder
Definition alu.test.cpp:124
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:155
MegaCircuitBuilder_< field< Bn254FrParams > > MegaCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static constexpr field zero()