Skip to content

Conversation

@Tabaie
Copy link
Contributor

@Tabaie Tabaie commented Oct 27, 2025

A simple utility function that computes the hash of a dynamically sized slice of variables. Used in Linea.


Note

Introduce SumMerkleDamgardDynamicLength and update Poseidon2 circuit test to validate incremental prefix hashes against the new utility.

  • Hash utilities (std/hash/hash.go):
    • Add SumMerkleDamgardDynamicLength(api, f, initialState, length, data) to compute Merkle–Damgård hash of a prefix using lookup-based selection.
    • Improve NewMerkleDamgardHasher docs and import logderivlookup.
  • Tests (std/hash/poseidon2/poseidon2_test.go):
    • Change circuit to expose and assert an array of incremental hashes: Expected[i] = H(Input[:i+1]).
    • For each prefix, assert equality between in-circuit hasher and hash.SumMerkleDamgardDynamicLength with Poseidon2 compressor and IV 0.
    • Update test data generation using gnark-crypto Poseidon2 reference hasher.

Written by Cursor Bugbot for commit c9dd1c3. This will update automatically on new commits. Configure here.

@Tabaie Tabaie requested review from Copilot and ivokub October 27, 2025 17:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces SumMerkleDamgardDynamicLength, a utility function for computing Merkle–Damgård hashes over dynamically sized slices using log-derivative lookup. The implementation is validated through enhanced Poseidon2 circuit tests that verify prefix-hash correctness.

  • Adds hash.SumMerkleDamgardDynamicLength to compute hash at dynamic length using logderivlookup
  • Updates Poseidon2 test to validate prefix hashes against both incremental hasher and the new function
  • Modifies test data generation to use proper field element encoding via fr.Bytes buffer

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
std/hash/hash.go Implements SumMerkleDamgardDynamicLength function using log-derivative lookup for dynamic-length hashing
std/hash/poseidon2/poseidon2_test.go Expands test to validate prefix hashes and compare incremental hasher with SumMerkleDamgardDynamicLength

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Tabaie Tabaie requested a review from Copilot October 27, 2025 18:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Tabaie Tabaie changed the title feat: SumMerkleDamgardDynamicLength with tests feat: SumMerkleDamgardDynamicLength Oct 27, 2025
Copy link
Collaborator

@ivokub ivokub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed - I'm not sure about the interfaces right now. I think adding as a general purpose function is unsafe as allows to easily create collisions. I think leaving it up to user implement correctly will cause issues. To overcome, we should do standard method of adding the input width as a last block, but this doubles the constraint count imo.

Secondly - I'd follow current interfaces pattern. All other hasher types are defined as interfaces, but this is a single function. For me it is a bit confusing.

Or - if only used in Linea, then I'd instead add the method there directly as we can have better control already within business logic which would avoid possible collisons.

// - length: length of the prefix of data to be hashed. The verifier will not accept a value outside the range {0, 1, ..., len(data)}.
// The gnark prover will refuse to attempt to generate such an unsuccessful proof.
// - data: the values a prefix of which is to be hashed.
func SumMerkleDamgardDynamicLength(api frontend.API, f Compressor, initialState frontend.Variable, length frontend.Variable, data []frontend.Variable) frontend.Variable {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this function signature allow for length extension attacks? I.e. lets say:

  • IV = 0
  • f_0 = compress(IV, msg_0)
  • f_1 = compress(f_0, msg_1)

Then, when we do either:
SumMerkleDamgardDynamicLength(api, Compressor, IV, 2, [msg_0, msg_1])
or
SumMerkleDamgardDynamicLength(api, Compressor, f_0, 1, [msg_1, msg_xxx])
we would get the same result? And considering all are user inputs then it could imo lead to possible collisions.

We also provide variable-length mode for binary hashes (see interface BinaryFixedLengthHasher). But there depending on the underlying hash function (sha256, ripemd, sha3) the length is appended to the input, avoid collision problems.

// The gnark prover will refuse to attempt to generate such an unsuccessful proof.
// - data: the values a prefix of which is to be hashed.
func SumMerkleDamgardDynamicLength(api frontend.API, f Compressor, initialState frontend.Variable, length frontend.Variable, data []frontend.Variable) frontend.Variable {
resT := logderivlookup.New(api)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And imo this method diverges from the rest of the APIs we use for hashers - otherwise we have defined FieldHasher for algebraic hash, and BinaryHasher/BinaryFixedLengthHasher for binary hashes (the ..FixedLengthHasher providing the same functionality i.e. allowing to dynamically set the input length to be hash).

I recommend following similar patter for consistency -- create FieldFixedLengthHasher interface

type FieldFixedLengthHasher interface {
    FieldHasher

    FixedLengthSum(length frontend.Variable) frontend.Variable
}

and then make in MiMC and Poseidon2 packages return this instead of FieldHasher.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants