From 11da5c78efa8dcd036ff4346bbafb203e206a206 Mon Sep 17 00:00:00 2001 From: Joey Kraut Date: Sat, 1 Mar 2025 15:56:15 -0800 Subject: [PATCH] librarires: merkle: MerkleTree: Use zero values in tree init + insert --- codegen/merkle-zeros/Cargo.toml | 1 - codegen/merkle-zeros/src/main.rs | 28 +++++++++++++++-- src/libraries/merkle/MerkleTree.sol | 17 +++++++---- src/libraries/merkle/MerkleZeros.sol | 45 ++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/codegen/merkle-zeros/Cargo.toml b/codegen/merkle-zeros/Cargo.toml index 9534cab..f35b57b 100644 --- a/codegen/merkle-zeros/Cargo.toml +++ b/codegen/merkle-zeros/Cargo.toml @@ -9,4 +9,3 @@ renegade-constants = { package = "constants", git = "https://github.com/renegade clap = { version = "4.5.1", features = ["derive"] } anyhow = "1.0" tiny-keccak = { version = "2.0", features = ["keccak"] } -hex = "0.4" diff --git a/codegen/merkle-zeros/src/main.rs b/codegen/merkle-zeros/src/main.rs index 876bf5b..75d0ee6 100644 --- a/codegen/merkle-zeros/src/main.rs +++ b/codegen/merkle-zeros/src/main.rs @@ -2,11 +2,8 @@ //! //! This tool generates a Solidity file with predefined Merkle tree zero values. -// ⚠ ️WARNING: This file is auto-generated by `codegen/merkle-zeros`. Do not edit directly. - use anyhow::{anyhow, Result}; use clap::Parser; -use hex; use renegade_constants::{Scalar, MERKLE_HEIGHT}; use renegade_crypto::hash::compute_poseidon_hash; use std::fs::File; @@ -58,6 +55,31 @@ fn generate_solidity_contract() -> Result { root )); + // Add an assembly-based getter function for gas-efficient constant-time access + contract.push_str("\n\t/// @notice Get zero value for a given height\n"); + contract.push_str("\t/// @param height The height in the Merkle tree\n"); + contract.push_str("\t/// @return The zero value for the given height\n"); + contract + .push_str("\tfunction getZeroValue(uint256 height) internal pure returns (uint256) {\n"); + contract.push_str("\t\t// Require height to be within valid range\n"); + contract.push_str("\t\trequire(height <= 31, \"MerkleZeros: height must be <= 31\");\n\n"); + + contract.push_str("\t\tuint256 result;\n"); + contract.push_str("\t\tassembly {\n"); + contract.push_str("\t\t\tswitch height\n"); + + // Generate all the assembly cases with direct constant values + for i in 0..MERKLE_HEIGHT { + contract.push_str(&format!( + "\t\t\tcase {} {{ result := ZERO_VALUE_{} }}\n", + i, i + )); + } + + // Remove the default case for assembly since require will handle invalid values + contract.push_str("\t\t}\n"); + contract.push_str("\t}\n"); + // Close contract contract.push_str("}\n"); Ok(contract) diff --git a/src/libraries/merkle/MerkleTree.sol b/src/libraries/merkle/MerkleTree.sol index 88773f5..e09dbbf 100644 --- a/src/libraries/merkle/MerkleTree.sol +++ b/src/libraries/merkle/MerkleTree.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import { BN254 } from "solidity-bn254/BN254.sol"; import { IHasher } from "../poseidon2/IHasher.sol"; import { DarkpoolConstants } from "../darkpool/Constants.sol"; +import { MerkleZeros } from "./MerkleZeros.sol"; /// @title MerkleTreeLib /// @notice Library for Merkle tree operations @@ -21,17 +22,24 @@ library MerkleTreeLib { mapping(BN254.ScalarField => bool) rootHistory; } + /// @notice Get the zero value for a given height in the Merkle tree + /// @param height The height in the Merkle tree + /// @return The zero value for the given height + function zeroValue(uint256 height) internal pure returns (BN254.ScalarField) { + // Use the assembly-based getter for maximum gas efficiency + return BN254.ScalarField.wrap(MerkleZeros.getZeroValue(height)); + } + /// @notice Initialize the Merkle tree /// @param tree The tree to initialize function initialize(MerkleTree storage tree) internal { tree.nextIndex = 0; tree.root = BN254.ScalarField.wrap(0); - // Initialize the sibling path - BN254.ScalarField zero = BN254.ScalarField.wrap(0); + // Initialize the sibling path array tree.siblingPath = new BN254.ScalarField[](DarkpoolConstants.MERKLE_DEPTH); for (uint256 i = 0; i < DarkpoolConstants.MERKLE_DEPTH; i++) { - tree.siblingPath[i] = zero; + tree.siblingPath[i] = zeroValue(i); } } @@ -78,8 +86,7 @@ library MerkleTreeLib { } else { // Right node, the new sibling is in a new sub-tree, and is the zero value // for this depth in the tree - // TODO: Use depth-dependent zero values - tree.siblingPath[i] = BN254.ScalarField.wrap(0); + tree.siblingPath[i] = zeroValue(i); } } } diff --git a/src/libraries/merkle/MerkleZeros.sol b/src/libraries/merkle/MerkleZeros.sol index 74ba0c9..b744edf 100644 --- a/src/libraries/merkle/MerkleZeros.sol +++ b/src/libraries/merkle/MerkleZeros.sol @@ -38,4 +38,49 @@ library MerkleZeros { uint256 constant public ZERO_VALUE_30 = 7035835480239620343712770214636030506415861196323445446427955599547555378646; uint256 constant public ZERO_VALUE_31 = 3570982782379586050211724779746612745305269241448247085265205218748662232570; uint256 constant public ZERO_VALUE_ROOT = 21822647340628839684360703580761466999432252031123851327655447000077200870350; + + /// @notice Get zero value for a given height + /// @param height The height in the Merkle tree + /// @return The zero value for the given height + function getZeroValue(uint256 height) internal pure returns (uint256) { + // Require height to be within valid range + require(height <= 31, "MerkleZeros: height must be <= 31"); + + uint256 result; + assembly { + switch height + case 0 { result := ZERO_VALUE_0 } + case 1 { result := ZERO_VALUE_1 } + case 2 { result := ZERO_VALUE_2 } + case 3 { result := ZERO_VALUE_3 } + case 4 { result := ZERO_VALUE_4 } + case 5 { result := ZERO_VALUE_5 } + case 6 { result := ZERO_VALUE_6 } + case 7 { result := ZERO_VALUE_7 } + case 8 { result := ZERO_VALUE_8 } + case 9 { result := ZERO_VALUE_9 } + case 10 { result := ZERO_VALUE_10 } + case 11 { result := ZERO_VALUE_11 } + case 12 { result := ZERO_VALUE_12 } + case 13 { result := ZERO_VALUE_13 } + case 14 { result := ZERO_VALUE_14 } + case 15 { result := ZERO_VALUE_15 } + case 16 { result := ZERO_VALUE_16 } + case 17 { result := ZERO_VALUE_17 } + case 18 { result := ZERO_VALUE_18 } + case 19 { result := ZERO_VALUE_19 } + case 20 { result := ZERO_VALUE_20 } + case 21 { result := ZERO_VALUE_21 } + case 22 { result := ZERO_VALUE_22 } + case 23 { result := ZERO_VALUE_23 } + case 24 { result := ZERO_VALUE_24 } + case 25 { result := ZERO_VALUE_25 } + case 26 { result := ZERO_VALUE_26 } + case 27 { result := ZERO_VALUE_27 } + case 28 { result := ZERO_VALUE_28 } + case 29 { result := ZERO_VALUE_29 } + case 30 { result := ZERO_VALUE_30 } + case 31 { result := ZERO_VALUE_31 } + } + } }