chore: stdlib::poseidon2 internal audit#16534
chore: stdlib::poseidon2 internal audit#16534iakovenkos merged 37 commits intomerge-train/barretenbergfrom
stdlib::poseidon2 internal audit#16534Conversation
| namespace bb::stdlib { | ||
|
|
||
| template <typename Params, typename Builder> class Poseidon2Permutation { | ||
| template <typename Builder> class Poseidon2Permutation { |
There was a problem hiding this comment.
redundant template param
| const bb::fr four(4); | ||
| // create the 6 gates for the initial matrix multiplication | ||
| // gate 1: Compute tmp1 = state[0] + state[1] + 2 * state[3] | ||
| field_t<Builder> tmp1 = |
There was a problem hiding this comment.
seems more robust to re-use add_two from field_t, as we don't allow hashing constants, the result of add_two is always a new witness, which is normalized. hence, it's safe to use get_witness_index() above.
| template <size_t rate, size_t capacity, size_t t, typename Permutation, typename Builder> class FieldSponge { | ||
| template <size_t rate, size_t capacity, size_t t, typename Builder> class FieldSponge { | ||
| public: | ||
| /** |
There was a problem hiding this comment.
Not needed in our use-cases.
There was a problem hiding this comment.
are we opting to fix the capacity etc?
| } | ||
|
|
||
| std::array<field_t, rate> perform_duplex() | ||
| void perform_duplex() |
There was a problem hiding this comment.
this method used to mutate state and return the output equal to the updated state.
| * @param input | ||
| * @return std::array<field_t, out_len> | ||
| */ | ||
| template <size_t out_len> |
There was a problem hiding this comment.
the function is only used with out_len == 1
| ASSERT(!data.empty()); | ||
| ASSERT(data[0].get_context() != nullptr); | ||
|
|
||
| Builder* builder = data[0].get_context(); |
There was a problem hiding this comment.
this is done in the hash method
...src/barretenberg/boomerang_value_detection/graph_description_poseidon2s_permutation.test.cpp
Show resolved
Hide resolved
barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.cpp
Show resolved
Hide resolved
barretenberg/cpp/src/barretenberg/dsl/acir_format/poseidon2_constraint.cpp
Show resolved
Hide resolved
barretenberg/cpp/src/barretenberg/dsl/acir_format/witness_constant.hpp
Outdated
Show resolved
Hide resolved
barretenberg/cpp/src/barretenberg/relations/poseidon2_external_relation.hpp
Show resolved
Hide resolved
barretenberg/cpp/src/barretenberg/relations/poseidon2_external_relation.hpp
Show resolved
Hide resolved
barretenberg/cpp/src/barretenberg/relations/poseidon2_internal_relation.hpp
Show resolved
Hide resolved
| // Add ĉ₀⁽ⁱ⁾ stored in the selector and convert to Lagrange basis | ||
| auto s1 = Accumulator(w_1 + c_0_int); | ||
|
|
||
| // Apply S-box. Note that the multiplication is performed point-wise |
There was a problem hiding this comment.
hmm, should we add an S-BOX method at this point instead of having the macro and use it across both internal and external relation?
There was a problem hiding this comment.
I don't knoow, feels a bit redundant tbh, cause it's only done once in the internal one
barretenberg/cpp/src/barretenberg/relations/poseidon2_internal_relation.hpp
Show resolved
Hide resolved
kashbrti
left a comment
There was a problem hiding this comment.
other than few nitpicks lgtm. haven't looked too much into the README and the tests.
| template <size_t rate, size_t capacity, size_t t, typename Permutation, typename Builder> class FieldSponge { | ||
| template <size_t rate, size_t capacity, size_t t, typename Builder> class FieldSponge { | ||
| public: | ||
| /** |
There was a problem hiding this comment.
are we opting to fix the capacity etc?
| }; | ||
| template <typename Builder> class FieldSponge { | ||
| private: | ||
| using Permutation = Poseidon2Permutation<Builder>; |
There was a problem hiding this comment.
could you add a reference where we have gotten the sponge spec and the parameters from?
There was a problem hiding this comment.
it's in the readme
| state = Permutation::permutation(builder, state); | ||
| // return `rate` number of field elements from the sponge state. | ||
| std::array<field_t, rate> output; | ||
| for (size_t i = 0; i < rate; ++i) { |
There was a problem hiding this comment.
were we just copying the state and outputting it here?
| static field_t hash_internal(std::span<const field_t> input) | ||
| { | ||
| size_t in_len = input.size(); | ||
| const uint256_t iv = (static_cast<uint256_t>(in_len) << 64) + out_len - 1; |
There was a problem hiding this comment.
just moved to the constructor. still have mixed feelings about the amount of flexibility we're removing from the sponge. I guess internally it doesn't matter. but thinking whether we want it to be a standalone primitive that other devs can play around with.
There was a problem hiding this comment.
there was no flexibility tbh, cause create_poseidon2_***_gate supports only the case where rate + capacity = num_wires (=4). templating those circuit builder methods on these params and storing several batches of round constants sounds painful. so before my changes we couldn't just instantiate the permutation with different params
| // The final state consists of 4 elements, we only use the first element, which means that the remaining | ||
| // 3 witnesses are only used in a single gate. | ||
| for (const auto& elem : sponge.state) { | ||
| builder->update_used_witnesses(elem.witness_index); |
There was a problem hiding this comment.
this used to be skipped for MegaCircuitBuilder was there a reason for this?
There was a problem hiding this comment.
Daniel's boomerang test is only instantiated with UltraCircuitBuilder but in both cases the "tail" of the final state is un-used
...src/barretenberg/boomerang_value_detection/graph_description_poseidon2s_permutation.test.cpp
Show resolved
Hide resolved
| static void matrix_multiplication_external(Builder* builder, State& state); | ||
| static void record_current_state_into_next_row(Builder* builder, const State& state, auto& block) | ||
| { | ||
| builder->create_dummy_gate(block, |
There was a problem hiding this comment.
create_dummy_gate just constructs a gate that is never active? I agree then that the naming is funky
| template <typename Params, typename Builder> | ||
| typename Poseidon2Permutation<Params, Builder>::State Poseidon2Permutation<Params, Builder>::permutation( | ||
| Builder* builder, const typename Poseidon2Permutation<Params, Builder>::State& input) | ||
| template <typename Builder> |
There was a problem hiding this comment.
just fixing the params to BN254 it seems.
barretenberg/cpp/src/barretenberg/stdlib/hash/poseidon2/poseidon2_permutation.cpp
Show resolved
Hide resolved
| * @param block Either `poseidon2_external` or `poseidon2_internal` block of the Execution Trace | ||
| */ | ||
| static void matrix_multiplication_external(Builder* builder, State& state); | ||
| static void record_current_state_into_next_row(Builder* builder, const State& state, auto& block) |
There was a problem hiding this comment.
nitpick, but maybe store or write is a better name? (write might be confused by a memory write)
There was a problem hiding this comment.
changed to propagate_current_state_to_next_row()
| // add the cache into sponge state | ||
| // Add the cache into sponge state | ||
| for (size_t i = 0; i < rate; ++i) { | ||
| state[i] += cache[i]; |
There was a problem hiding this comment.
@kashbrti the padding is happening here. when squeeze calls perform_duplex(), only first num_inputs % 3 are not const 0
There was a problem hiding this comment.
hmm, so I see that we're keeping a cache of size 3, whenever it fills up we're calling the perform_duplex. don't see what we do when for the last block (of length 3) we don't have more elements to fill the block. I guess in this case, we're just calling squeeze on it?
There was a problem hiding this comment.
squeeze will again call perform_duplex. but the cache will not have an element in index 2 anymore, am I missing something? because it seems each call of perform_duplex just empties the cache.
There was a problem hiding this comment.
ah cache is a std::array<field_t,3> and not a std::vector<field_t> so it'll have a 0 field_t there.
but shouldn't we have a bit of 1 before starting to pad with 0s? like now we will have a collision between a genuine input that ends with 0 and one that needs padding by 1 element it seems.
There was a problem hiding this comment.
Well, we don't support fixed length mode at all. So, IV prevents this. I added a test checking that there are no collisions between {x}, {x,0}, and {x,0,0}
There was a problem hiding this comment.
I guess the length used in the IV would mitigate that, but still might be better to take a standard approach.
There was a problem hiding this comment.
ah seems like I forgot to press comment XD. yea I guess the IV is preventing the collisions
|
also please add a PR description. |
stdlib::poseidon2 internal audit
- Most of the `Poseidon2`-related methods and classes were unnecessarily templated on a variety of parameters, such as `rate` and `capacity` of the permutation, while at the same time our Circuit Builders would only support the case `rate = 3`, `capacity = 1`. To avoid confusion and improve the readability, I minimized the number of templates. - The `sponge` class was only invoked with a sequence of `absorb` calls that would lead to a single `squezze` call. It allowed to simplify the class logic. - Simplified the `Poseidon2Permutation` class by re-using some of the `field_t` methods. Improved the docs. - Added a detailed **readme** that contains links to the sources, including the padding choices in `sponge` and the explanation of the custom Poseidon2 gates - Audited relations, expanded corresponding docs - Added a bunch of tests - collision-resistance sanity check, test against the hash outputs from https://github.com/zemse/poseidon2-evm, several basic failure tests
### 🧾`stdlib::poseidon2` internal audit - Most of the `Poseidon2`-related methods and classes were unnecessarily templated on a variety of parameters, such as `rate` and `capacity` of the permutation, while at the same time our Circuit Builders would only support the case `rate = 3`, `capacity = 1`. To avoid confusion and improve the readability, I minimized the number of templates. - The `sponge` class was only invoked with a sequence of `absorb` calls that would lead to a single `squezze` call. It allowed to simplify the class logic. - Simplified the `Poseidon2Permutation` class by re-using some of the `field_t` methods. Improved the docs. - Added a detailed **readme** that contains links to the sources, including the padding choices in `sponge` and the explanation of the custom Poseidon2 gates - Audited relations, expanded corresponding docs - Added a bunch of tests - collision-resistance sanity check, test against the hash outputs from https://github.com/zemse/poseidon2-evm, several basic failure tests
🧾
stdlib::poseidon2internal auditPoseidon2-related methods and classes were unnecessarily templated on a variety of parameters, such asrateandcapacityof the permutation, while at the same time our Circuit Builders would only support the caserate = 3,capacity = 1. To avoid confusion and improve the readability, I minimized the number of templates.spongeclass was only invoked with a sequence ofabsorbcalls that would lead to a singlesquezzecall. It allowed to simplify the class logic.Poseidon2Permutationclass by re-using some of thefield_tmethods. Improved the docs.spongeand the explanation of the custom Poseidon2 gates