Skip to content

Conversation

@re1ro
Copy link
Collaborator

@re1ro re1ro commented Oct 31, 2025

Summary

Implement tree-based cross-chain permit system with UI transparency and gas-efficient on-chain verification using TreeNodeLib.

Users sign a complete PermitNode tree structure (full transparency), while the contract receives a compact proof (gas efficient).

Dependencies

⚠️ Depends on PR #53 (feat/tree-lib) - merge that first!

Core Changes

New Structures

  • PermitNode - UI-readable tree structure for signing
  • PermitTree - Compact on-chain structure (proofStructure + proof)
  • Signature - Standardized signature data bundle

New Typehashes

  • PERMIT3_TYPEHASH - Single-chain permit
  • MULTICHAIN_PERMIT3_TYPEHASH - Multi-chain tree permit
  • PERMIT_NODE_TYPEHASH - Internal tree node hashing

Updated Files

  • src/Permit3.sol - Tree reconstruction using TreeNodeLib
  • src/PermitBase.sol - Updated for new structures
  • src/interfaces/IPermit3.sol - New structs and signatures
  • src/MultiTokenPermit.sol - API adjustments
  • src/modules/ERC7579ApproverModule.sol - Integration updates

Test Coverage

New Tests:

  • test/PermitNodeReconstruction.t.sol (540 lines) - Hash reconstruction validation
  • test/PermitTreeIntegration.t.sol (296 lines) - End-to-end integration
  • test/NestedStructure.t.sol (137 lines) - EIP-712 nested struct tests

Updated Tests:

  • test/Permit3.t.sol, test/Permit3Edge.t.sol, test/Permit3Witness.t.sol
  • test/MultiTokenPermit.t.sol and related tests

Test Results:

✅ All 138 permit tests pass
✅ Build successful

JavaScript Utilities

Complete toolkit for tree construction:

  • utils/permitNodeHelpers.js (1,192 lines) - Tree construction & proof generation
  • utils/test-permitNode.js (288 lines) - JS test suite
  • utils/merkle-helpers.js (636 lines) - Legacy Merkle utilities
  • Comprehensive README documentation

Gas Performance

  • 2 chains: ~85,000 gas
  • 4 chains: ~90,000 gas
  • 8 chains: ~95,000 gas
  • Each proof element adds ~3,000-5,000 gas

Key Benefits

UI Transparency: Users see all chains/permits in wallet before signing
Gas Efficient: O(log n) proof size with linear gas scaling
Deterministic: Three clear combination rules prevent ambiguity
Secure: Maximum depth validation, unused bit checks

API Example

Before (single-chain):

permit(owner, salt, deadline, timestamp, permits, signature);

After (single-chain):

Signature memory sig = Signature({owner, salt, deadline, timestamp, signature});
permit(permits, sig);

After (multi-chain):

PermitTree memory tree = PermitTree({
    currentChainPermits: chainPermits,
    proofStructure: proofStructure,
    proof: proof
});
permit(tree, sig);

Documentation

Updated README with:

  • Tree-Based Cross-Chain Permits section
  • JavaScript usage examples
  • Gas cost benchmarks
  • Security model explanation

🤖 Generated with Claude Code

Implement tree-based cross-chain permit system with UI transparency
and gas-efficient on-chain verification using TreeNodeLib.

Core changes:
- Add PermitNode tree structure for UI-readable permits
- Add PermitTree compact on-chain structure
- Add Signature struct for cleaner API
- New typehashes: PERMIT3_TYPEHASH, MULTICHAIN_PERMIT3_TYPEHASH, PERMIT_NODE_TYPEHASH
- Refactor Permit3.sol to use tree reconstruction
- Update PermitBase.sol for new structures
- Update all permit-related interfaces

Test coverage:
- PermitNodeReconstruction.t.sol (540 lines) - Hash reconstruction tests
- PermitTreeIntegration.t.sol (296 lines) - End-to-end integration
- NestedStructure.t.sol (137 lines) - EIP-712 nested struct tests
- Updated Permit3.t.sol, Permit3Edge.t.sol, Permit3Witness.t.sol
- Updated MultiTokenPermit.t.sol and related tests

JavaScript utilities:
- permitNodeHelpers.js (1,192 lines) - Complete tree construction toolkit
- test-permitNode.js (288 lines) - JS test suite
- merkle-helpers.js (636 lines) - Legacy Merkle utilities
- Comprehensive README documentation

Key benefits:
- Users sign complete tree structure (full transparency)
- Contract receives compact proof (gas efficient)
- O(log n) proof size with linear gas scaling
- Three deterministic combination rules

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings October 31, 2025 19:18
Copy link

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 pull request introduces comprehensive JavaScript utilities for Permit3's tree-based cross-chain permit system. The changes add implementation of EIP-712 PermitNode tree construction, proof generation, and Solidity test infrastructure to support the new tree-based permit architecture.

Key Changes:

  • Implementation of permitNodeHelpers.js with tree encoding/proof generation algorithms matching on-chain Solidity reconstruction
  • Addition of test-permitNode.js comprehensive test suite with 6 test scenarios
  • New Solidity test helpers (TreeNodeLibTester, PermitNodeLibTester) for testing generic tree reconstruction
  • Extensive integration tests for tree-based permit execution across various topologies

Reviewed Changes

Copilot reviewed 29 out of 30 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
utils/permitNodeHelpers.js Core implementation of PermitNode tree hashing, proof generation, and encoding matching Solidity reconstruction algorithm
utils/test-permitNode.js Comprehensive test script validating tree construction and proof generation for 2-5 chain scenarios
utils/merkle-helpers.js Legacy Merkle tree utilities with backward compatibility aliases for permit node structures
utils/README.md Documentation covering tree concepts, encoding format, usage examples, and migration guide
utils/package.json Package configuration with ethers v5, merkletreejs, and keccak256 dependencies
test/utils/TreeNodeLibTester.sol Generic tree node library test wrapper exposing internal combination functions
test/utils/PermitNodeLibTester.sol Permit-specific wrapper around TreeNodeLib maintaining backward compatibility
test/utils/TestBase.sol Enhanced base test with _hashPermitNode() helper and sorting utilities
test/TreeNodeLib.t.sol Comprehensive tests (65 tests) for generic TreeNodeLib with both permit and nonce typehashes
test/PermitTreeIntegration.t.sol End-to-end integration tests for tree-based permit execution with various topologies
test/PermitNodeReconstruction.t.sol Tests for EIP-712 PermitNode hash reconstruction across 15 different tree structures
test/Permit3Witness.t.sol Updated witness tests migrated from merkle proofs to tree structures
test/Permit3.t.sol Updated core permit tests using new tree-based signature structure
Files not reviewed (1)
  • utils/package-lock.json: Language not supported

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

re1ro and others added 6 commits October 31, 2025 15:26
# Conflicts:
#	test/ZeroAddressValidation.t.sol
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@re1ro re1ro changed the title feat: implement tree-based cross-chain permits feat(#2): implement tree-based cross-chain permits Oct 31, 2025
@re1ro re1ro changed the base branch from main to feat/tree-lib October 31, 2025 19:37
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these supposed to be committed? If so, let's do all the JS things separately.

* @dev Used in EIP-712 signatures to provide transparency to users about what they're signing
* @dev Can represent either leaf nodes (ChainPermits) or internal tree nodes (nested levels)
* @dev Both arrays should be ordered by hash value as merkle tree construction requires
* @param levels Child tree nodes for internal nodes (ordered by hash value)
Copy link
Contributor

Choose a reason for hiding this comment

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

No levels

Comment on lines 97 to 98
* @param proofStructure Compact tree encoding
* @param currentChainPermits Permit operations for the current chain
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @param proofStructure Compact tree encoding
* @param currentChainPermits Permit operations for the current chain
* @param currentChainPermits Permit operations for the current chain
* @param proofStructure Compact tree encoding

* @param permits Array of permit operations to execute
* @param signature EIP-712 signature authorizing all permits in the batch
*/
function permit(
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really need this function? Why can't the single-chain permit be treated as a single-node tree?

// processProof performs validation internally and provides granular error messages
params.merkleRoot = MerkleProof.processProof(proof, params.currentChainHash);
// Reconstruct the PermitNode hash from the proof and tree structure
bytes32 permitNodeHash =
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
bytes32 permitNodeHash =
bytes32 treeHash =

Comment on lines +181 to 183
if (block.timestamp > sig.deadline) {
revert SignatureExpired(sig.deadline, uint48(block.timestamp));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm see this being repeated a lot. Consider moving it to a modifier or _verifySignature

Comment on lines +306 to +319
function _validateWitnessTypeString(
string calldata witnessTypeString
) internal pure {
// Validate minimum length
if (bytes(witnessTypeString).length == 0) {
revert InvalidWitnessTypeString(witnessTypeString);
}

// Validate proper ending with closing parenthesis
uint256 witnessTypeStringLength = bytes(witnessTypeString).length;
if (bytes(witnessTypeString)[witnessTypeStringLength - 1] != ")") {
revert InvalidWitnessTypeString(witnessTypeString);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Honestly, I don't think this function is needed. It doesn't do much for us. There's much more that can go wrong than empty or the last char being not ")".

* @param signature EIP-712 signature authorizing all permits with witness
* @param witness Witness data containing witness hash and type string
*/
function permitWitness(
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand how this works, but what's the usecase. Who would need it?

IPermit3.Signature calldata sig,
IPermit3.PermitTree calldata tree,
IPermit3.Witness calldata witness
) internal view returns (TreeWitnessContext memory ctx) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
) internal view returns (TreeWitnessContext memory ctx) {
) internal pure returns (TreeWitnessContext memory ctx) {

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, don't see why we need this struct TreeWitnessContext. Only .signedHash is ever used but the other two fields are never used.

claude and others added 3 commits November 21, 2025 22:27
Address PR feedback:
- Fix @param name from 'levels' to 'nodes' in PermitNode struct
- Reorder @param documentation to match struct field order in PermitTree

Changes:
- Corrected parameter documentation to match actual struct fields
- Improved documentation clarity and consistency
…me7mv5nn2QUpN6XQ

docs: fix documentation formatting in IPermit3
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.

4 participants