No description
  • Solidity 93.6%
  • JavaScript 6.4%
Find a file
beeinger d14943232c Add Poseidon2 hasher and update MMR library for pluggable hashing functionality
- Introduced Poseidon2Hasher for ZK-friendly hashing.
- Updated StatelessMmr to accept custom hasher functions.
- Enhanced README with usage examples for both Keccak and Poseidon2.
- Added foundry.lock and package-lock.json for dependency management.
- Updated .gitmodules to include poseidon2-evm submodule.
2026-04-01 13:28:40 -03:00
.github/workflows chore: forge init 2023-04-11 14:49:08 +04:00
helpers Update @herodotus_dev packages to @accumulators 2023-10-02 14:23:31 +02:00
lib Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00
src Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00
test Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00
.gitignore 🌋MMR lib implementation w/ tests 2023-04-11 19:41:57 +04:00
.gitmodules Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00
banner.png Added banner from Anna 2023-11-14 20:30:59 +00:00
foundry.lock Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00
foundry.toml Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00
LICENSE Update LICENSE 2023-10-31 21:48:20 +01:00
package-lock.json Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00
package.json Update @herodotus_dev packages to @accumulators 2023-10-02 14:23:31 +02:00
pnpm-lock.yaml Update @herodotus_dev packages to @accumulators 2023-10-02 14:23:31 +02:00
README.md Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00
remappings.txt Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00
yarn.lock Add Poseidon2 hasher and update MMR library for pluggable hashing functionality 2026-04-01 13:28:40 -03:00

solidity-mmr

  _____       _ _     _ _ _           __  __ __  __ _____
 / ____|     | (_)   | (_) |         |  \/  |  \/  |  __ \
| (___   ___ | |_  __| |_| |_ _   _  | \  / | \  / | |__) |
 \___ \ / _ \| | |/ _` | | __| | | | | |\/| | |\/| |  _  /
 ____) | (_) | | | (_| | | |_| |_| | | |  | | |  | | | \ \
|_____/ \___/|_|_|\__,_|_|\__|\__, | |_|  |_|_|  |_|_|  \_\
                               __/ |

Pre-requisites:

  • yarn
  • Node.js
  • Solidity compiler (solc)
  • Foundry

Note: this library can be directly inlined in your contracts and doesn't need to be deployed separately as all functions visibility are internal pure.

Quick Start

    yarn install   # required for the keccak off-chain interoperability tests
    forge build
    forge test                          # default: 10 fuzz runs (fast)
    FOUNDRY_PROFILE=full forge test     # 256 fuzz runs (thorough)

Pluggable Hash Functions

All StatelessMmr functions accept a hasher parameter:

function(bytes32, bytes32) internal pure returns (bytes32)

This lets you choose the hashing algorithm without forking the library. Two ready-made implementations are provided in src/lib/hashers/:

File Algorithm Use-case
KeccakHasher.sol keccak256(abi.encode(a, b)) General-purpose EVM hashing (default)
Poseidon2Hasher.sol Poseidon2 over BN254 (via poseidon2-evm) ZK-SNARK / ZK-STARK circuits

Both expose a single function hash(bytes32 a, bytes32 b) internal pure returns (bytes32) that can be passed directly to any StatelessMmr call.

Poseidon2 note: The Poseidon2 implementation operates over the BN254 scalar field. Input values are interpreted as field elements without range checking. For full ZK compatibility, leaf values should be within the BN254 scalar field (< 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001). Internal MMR nodes are always valid field elements because Poseidon2 output is reduced modulo the prime.

Interface API

interface MMRTree {
    function append(bytes32 element) external;

    function multiAppend(bytes32[] memory elements) external;

    function getRootHash() external view returns (bytes32);

    function getElementsCount() external view returns (uint);

    function verifyProof(
        uint index,
        bytes32 value,
        bytes32[] memory proof,
        bytes32[] memory peaks,
        uint elementsCount,
        bytes32 root
    ) external view;
}

Usage examples

Keccak256 MMR (default)

import {StatelessMmr} from "solidity-mmr/src/lib/StatelessMmr.sol";
import {KeccakHasher} from "solidity-mmr/src/lib/hashers/KeccakHasher.sol";

contract MyKeccakMmr {
    bytes32[] peaks;
    uint    elementsCount;
    bytes32 root;

    function append(bytes32 element) external {
        (elementsCount, root, peaks) = StatelessMmr.appendWithPeaksRetrieval(
            element, peaks, elementsCount, root, KeccakHasher.hash
        );
    }

    function verifyProof(
        uint index, bytes32 value,
        bytes32[] calldata proof, bytes32[] calldata _peaks,
        uint _elementsCount, bytes32 _root
    ) external pure {
        StatelessMmr.verifyProof(
            index, value, proof, _peaks, _elementsCount, _root,
            KeccakHasher.hash
        );
    }
}

Poseidon2 MMR (ZK-friendly)

import {StatelessMmr} from "solidity-mmr/src/lib/StatelessMmr.sol";
import {Poseidon2Hasher} from "solidity-mmr/src/lib/hashers/Poseidon2Hasher.sol";

contract MyPoseidon2Mmr {
    bytes32[] peaks;
    uint    elementsCount;
    bytes32 root;

    function append(bytes32 element) external {
        (elementsCount, root, peaks) = StatelessMmr.appendWithPeaksRetrieval(
            element, peaks, elementsCount, root, Poseidon2Hasher.hash
        );
    }

    function verifyProof(
        uint index, bytes32 value,
        bytes32[] calldata proof, bytes32[] calldata _peaks,
        uint _elementsCount, bytes32 _root
    ) external pure {
        StatelessMmr.verifyProof(
            index, value, proof, _peaks, _elementsCount, _root,
            Poseidon2Hasher.hash
        );
    }
}

Custom hasher

You can pass any function with the signature function(bytes32, bytes32) internal pure returns (bytes32):

function myHasher(bytes32 a, bytes32 b) internal pure returns (bytes32) {
    return sha256(abi.encode(a, b));
}

// Then pass it directly:
StatelessMmr.append(element, peaks, count, root, myHasher);

Generate a proof (keccak)

In order to generate a proof, the easiest way is to keep track of the MMR state off-chain and generate a proof when needed.

The following example shows how to generate a compatible proof in TypeScript:

const { utils, BigNumber } = require("ethers"); // Use ethers@5.2.7
const { default: CoreMMR } = require("@herodotus_dev/mmr-core");
const { KeccakHasher } = require("@herodotus_dev/mmr-hashes");
const { default: MMRInMemoryStore } = require("@herodotus_dev/mmr-memory"); // @herodotus_dev/mmr-rocksdb also available

async function main() {
  const store = new MMRInMemoryStore();
  const hasher = new KeccakHasher();
  const encoder = new utils.AbiCoder();
  const mmr = new CoreMMR(store, hasher);

  await mmr.append("1");
  await mmr.append("2");
  const { leafIndex } = await mmr.append("3");
  const peaks = await mmr.getPeaks();
  await mmr.append("4");

  // Generate an inclusion proof of the third element
  const proof = await mmr.getProof(leafIndex);
  const solidityVerifyProof = {
    index: leafIndex.toString(),
    value: numberStringToBytes32("3").toString(),
    proof: proof.siblingsHashes,
    peaks,
    pos: result.elementsCount.toString(),
    rootHash: result.rootHash,
  };
  console.log(solidityVerifyProof);

  // Encode `solidityVerifyProof` as calldata to then call verifyProof function in the contract.
  // Verifying a proof does _not_ cost gas (view function), so it can also be done off-chain.
  // You can take a look at `./helpers/off-chain-mmr.js` for a full example.
}

const numberStringToBytes32 = (numberAsString) =>
  utils.hexZeroPad(BigNumber.from(numberAsString).toHexString(), 32);

Herodotus Dev Ltd - 2023