Skip to content

Conversation

@jimjimvalkema
Copy link

@jimjimvalkema jimjimvalkema commented Jul 13, 2025

Description

I changed the internal libraries of IMT, LeanIMT and LazyIMT so the user can provide their own hash function with an address that implements their hash function with the interface that is the same as PoseidonT3(T6 for QuinaryIMT). And also their own default zeros for IMT and LazyIMT.
I made the libraries that use the internal libraries use the poseidon so they behave like usual.

changes made

  1. Remove PoseidonT3, _defaultZero, SNARK_SCALER_LIMT from InternalBinaryIMT.
  2. Call PoseidonT3 through an interface instead like this IHasher(hasher).hash([left,right]) (this also saves gas).
  3. Add hasher, _defaultZero, 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 BinaryIMT: add the hasher address as a constant, add _defaultZero, and set the hasherLimit

here is a snipet:

in InternalBinaryIMT

library InternalBinaryIMT {
...
  function _insert(
      BinaryIMTData storage self,
      uint256 leaf,
      address hasher,
      uint256 hasherLimit,
      function(uint256) pure returns (uint256) _defaultZero,
  ) internal returns (uint256) {
...
}

Then this is used as follows in BinaryIMT

import {SNARK_SCALAR_FIELD} from "./Constants.sol";
library BinaryIMT {
 // This is poseidonT3 from: https://github.com/chancehudson/poseidon-solidity
 address internal constant hasher = 0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93;
 
 uint256 internal constant Z_0 = 0;
 ...
 
function _defaultZero(uint256 index) internal pure returns (uint256) {
      if (index == 0) return Z_0;
      ...
      revert WrongDefaultZeroIndex();
  }

  function insert(BinaryIMTData storage self, uint256 leaf) public returns (uint256) {
      return InternalBinaryIMT._insert(self, leaf, hasher, SNARK_SCALAR_FIELD, _defaultZero);
  }
...
}

test changes

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

Stack too deep error

My changes added a stack too deep error to InternalLeanImt _insertMany. Which i resolved by deleting numberOfNewNodes and calculating it on the spot instead. And also deleting nextLevelNewNodes and using currentLevelNewNodes instead. This reduced the readability but it did save a tiny bit of gas!
You can see it here at line 158-178

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.

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.

internalBinaryIMT can use different hash functions by passing in a hash function as a parameter to
every function that uses the hash function. BinaryIMT does this for poseidonT3.
renamed _hash to hasher for a more consistent naming
renamed _hash to hasher
InternalQuinaryIMT can use different hash functions by passing in a hash function as a parameter to
…er field checks

SNARK_SCALAR_FIELD checks to outside of the internal library to allow non snark based
…se the hasher was pure

fixed non solidity hash functions not being by making the hash function view instead of pure. This
allows use cases where the developer needs to call a contract with .staticcall() directly to do a
hash
initial measuremnt gas so we can see what changes after each commit
…ction

InternalBinaryIMT can now be used with different hash functions given that the hash functions
contract has the expected interface

BREAKING CHANGE:  InternalBinaryIMTs functions now needs additional parameters
quinary IMT can now be used with different hash functions given that the hash functions contract has
the expected interface

BREAKING CHANGE: InternalQuinaryIMTs functions now require additional parameters
Removed poseidon from the quinary and BinaryIMT and made the deploy:imt-test task deploy poseidon
through nicks methond instead of a linked library

BREAKING CHANGE: gas reports no longer include poseidon hashes
…ent hash functions

moved to a new approach using an address to support different hash functions
…terThanHasherLimit

renamed ValueGreaterThanSnarkScalarField to ValueGreaterThanHasherLimit since not every hash
function is snark based

BREAKING CHANGE: breaks error handeling that expects ValueGreaterThanSnarkScalarField
internalLazyImt can use different hash functions by passing in a hash function as a parameter to
every function that uses the hash function. LazyIMT does this for poseidonT3.

BREAKING CHANGE: InternalLazyIMT requires additional inputs.

Errors mention hasherLimit instead of SNARK_SCALAR_FIELD
…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.
library LazyIMT {
using InternalLazyIMT for *;

address internal constant hasher = 0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93;

Check warning

Code scanning / Slither

Conformance to Solidity naming conventions Warning

Constant LazyIMT.hasher is not in UPPER_CASE_WITH_UNDERSCORES
import {SNARK_SCALAR_FIELD} from "./Constants.sol";

library LeanIMT {
address internal constant hasher = 0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93;

Check warning

Code scanning / Slither

Conformance to Solidity naming conventions Warning

Constant LeanIMT.hasher is not in UPPER_CASE_WITH_UNDERSCORES
using InternalBinaryIMT for *;

// This is poseidonT3 from: https://github.com/chancehudson/poseidon-solidity
address internal constant hasher = 0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93;

Check warning

Code scanning / Slither

Conformance to Solidity naming conventions Warning

Constant BinaryIMT.hasher is not in UPPER_CASE_WITH_UNDERSCORES

library QuinaryIMT {
using InternalQuinaryIMT for *;
address internal constant hasher = 0x666333F371685334CdD69bdDdaFBABc87CE7c7Db;

Check warning

Code scanning / Slither

Conformance to Solidity naming conventions Warning

Constant QuinaryIMT.hasher is not in UPPER_CASE_WITH_UNDERSCORES
@jimjimvalkema
Copy link
Author

Sorry didnt catch that slither error! I renamed the constant hasher to HASHER_ADDRESS.

@vplasencia vplasencia removed the request for review from ctrlc03 October 10, 2025 10:58
@jimjimvalkema
Copy link
Author

I have split up the pr per packaged in #58 , #59 , #60

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