Skip to content

FEATURE: Allow custom hash functions to Internal library of LeanIMT#59

Open
jimjimvalkema wants to merge 12 commits intozk-kit:mainfrom
jimjimvalkema:lean-imt
Open

FEATURE: Allow custom hash functions to Internal library of LeanIMT#59
jimjimvalkema wants to merge 12 commits intozk-kit:mainfrom
jimjimvalkema:lean-imt

Conversation

@jimjimvalkema
Copy link

@jimjimvalkema jimjimvalkema commented Oct 16, 2025

Description

I changed the internal library of LeanIMT so the user can provide their own hash function as a parameter.
The LeanIMT.sol library is setup so it uses the poseidon hash function so everything behaves as usual.

I used this to make a poseidon2 IMT (the new poseidon who is faster to prove) here: https://github.com/jimjimvalkema/EIP7503-ERC20/blob/55732aedac7b82e11769f1de7869373736a08734/contracts/leanIMTPoseidon2.sol#L13-L22

I also made some bench marks with hash functions like SHA256, poseidon2, etc:
https://github.com/jimjimvalkema/zkkit-hashfunc-bench-marks/tree/main?tab=readme-ov-file#proof-time-16-depth-tree

changes made

  1. Remove PoseidonT3 and SNARK_SCALER_LIMT from InternalLeanIMT.
  2. Call PoseidonT3 through an interface instead like this IHasher(hasher).hash([left,right]) (this also saves gas).
  3. Add hasher, SNARK_SCALER_LIMT as added arguments to functions that need them.
  4. Rename SNARK_SCALER_LIMT to hasherLimit since it not every hash function is snark based.
  5. In LeanIMT: add the hasher address as a constant and set the hasherLimit

here is a snipet:

in InternalLeanIMT.sol

struct Hasher {
    function(uint256[2] memory) view returns (uint256) func;
    uint256 limit;
}

library InternalLeanIMT {
...
    function _insert(
        LeanIMTData storage self, 
        uint256 leaf, 
        Hasher memory hasher
    ) internal returns (uint256) {
...
}

Then this is used as follows in LeanIMT.sol

library LeanIMT {
    address internal constant HASHER_ADDRESS = 0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93;
    
     // The function used for hashing. Passed as a function parameter in functions from InternalLazyIMT
    function _hasher(uint256[2] memory leaves) internal view returns (uint256) {
        return IHasherT3(HASHER_ADDRESS).hash([leaves[0], leaves[1]]);
    }

    function insert(LeanIMTData storage self, uint256 leaf) public returns (uint256) {
        return InternalLeanIMT._insert(self, leaf, Hasher(_hasher, SNARK_SCALAR_FIELD));
    }
}

insertMany changes

I ran into a stack too deep error and had to make some minor changes to get around it.

  1. i removed numberOfNewNodes and instead calculated it on the spot
  2. remove leftNode rightNode and just used an array named hasherInput

test changes

I changed the deploy-imt-test task to deploy poseidon with the deterministic proxy so it always deploys to the same address. See in the poseidon-solidity readme

Breaking changes

I changed error that mention SNARK_SCALAR_LIMIT to instead say hasherLimit, since the hash function is now generic. This might break error handling for users that expect the old error.
LeanIMT now uses the create2 address of poseidonT3 directly, so users using a chain where it isn't deployed yet (or hardhat tests), will need to deploy it them selfs now.

If an user used the Internal version of the libraries for some reason. Then they do have now pass these new parameters like _hasher, _defaultZeros and hasherLimit.

If an user used the Internal version of the libraries for some reason. Then they do have now pass these new parameters like hasher, hasherLimit and _defaultZeros.

Related Issue(s)

Closes #56

Other information

Calling poseidon with a interface saves from 10k - 100k gas for most functions. But making the libraries generic does add 0~100 gas on average.
In total expect a gas saving of 10k gas on average!
image

Checklist

  • I have read and understand the contributor guidelines and code of conduct.
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • I have run yarn style without getting any errors
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Important

We do not accept pull requests for minor grammatical fixes (e.g., correcting typos, rewording sentences) or for fixing broken links, unless they significantly improve clarity or functionality. These contributions, while appreciated, are not a priority for merging. If you notice any of these issues, please create a GitHub Issue to report them so they can be properly tracked and addressed.

…brary

calling poseidon through an interface instead of a library to save gas
InternalLeanIMT can now use different hash functions by passing in a hash function as a parameter
toevery function that uses the hash function. LeanIMT does this for poseidonT3.

BREAKING CHANGE: InternalLeanIMTs functions requires additional inputs.

Errors mention hasherLimit instead of SNARK_SCALAR_FIELD.
…instead of an address

Switched to passing the hash function as a function instead of an address and fixed some of the
readability of _insertMany.

Also added a regular insert to the insertMany test in order to catch issues with self.sideNodes
being off.

BREAKING CHANGE: switched to passing the hash function as a function

instead of an address
@jimjimvalkema jimjimvalkema marked this pull request as ready for review January 4, 2026 22:31
@jimjimvalkema
Copy link
Author

I managed to fix the readability of insertMany by packing hasherLimit + hasher() into a struct.
Only things i changed to stay below the stack too deep limit is

  1. i removed numberOfNewNodes and instead calculated it on the spot
  2. remove leftNode rightNode and just used an array named hasherInput

I also feel confident now this pr can be reviewed!

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.

Support different hash functions

1 participant