Sitelet https://github.com/ithacaxyz/account/pull/435/commits/2ecce316b8bad62c40f31a5ceeb500726d926d1c
Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2ecce31
feat: add merkle sigs natively into the account
legion2002 Sep 24, 2025
5577747
fix: tests
legion2002 Sep 24, 2025
995a3e1
test: add more sophisticated fuzz test
legion2002 Sep 24, 2025
4918a12
chore: bump contract versions due to bytecode changes - Contracts upd…
actions-user Sep 24, 2025
68f4e3a
Add .circleci/config.yml (#1)
Dargon789 Nov 19, 2025
2f484ce
Update ci.yaml
Dargon789 Nov 19, 2025
cef256e
Update ci.yaml (#2)
Dargon789 Nov 19, 2025
bcd8527
Update ci.yaml (#4)
Dargon789 Nov 19, 2025
01aafc5
Update ci.yaml (#5)
Dargon789 Nov 20, 2025
21aa942
Update ci.yaml
Dargon789 Nov 20, 2025
a317ddb
Create CNAME
Dargon789 Dec 20, 2025
6c02fbf
Merge branch 'master'
Dargon789 Dec 21, 2025
d41447e
Merge branch 'master' (#8)
Dargon789 Dec 21, 2025
28ff37f
Revert "Merge branch 'master'"
Dargon789 Dec 21, 2025
30a2096
Merge branch 'ithacaxyz:main' into main
Dargon789 Jan 5, 2026
8a9cd81
Update ci.yaml (#10)
Dargon789 Jan 5, 2026
1fe73a5
Revert "fix vm block accoount (#11)" (#13)
Dargon789 Jan 22, 2026
9627204
Merge branch 'master' of https://github.com/Dargon789/account
Dargon789 Jan 28, 2026
e730f41
pre-commit
googleworkspace-bot Mar 13, 2026
4241666
deploy execute_config.sh
googleworkspace-bot Mar 13, 2026
aaee542
Merge branch 'main' into porto.sh
googleworkspace-bot Mar 13, 2026
cbd478b
Update forge-std
googleworkspace-bot Mar 13, 2026
ab9e883
Normalize file modes and update forge-std submodule
googleworkspace-bot Apr 10, 2026
cd369ab
Merge branch 'main' into porto.sh
googleworkspace-bot Apr 10, 2026
513d2c8
Update LayerZero-v2 submodule and permissions
googleworkspace-bot Apr 11, 2026
36249c7
Update LayerZero-v2
googleworkspace-bot Apr 11, 2026
d54a37b
Update LayerZero-v2
googleworkspace-bot Apr 11, 2026
93329f5
Merge branch 'porto.sh'
googleworkspace-bot Apr 11, 2026
379fa7b
Dargon789 legion rouge (#61)
Dargon789 Apr 11, 2026
0cb7a77
Ithaca (#58)
Dargon789 Apr 11, 2026
7155697
Delete .circleci directory (#64)
Dargon789 Apr 11, 2026
e713ca4
feat: add merkle sigs natively into the account legion rouge (#70) (…
Dargon789 Apr 16, 2026
6f0ba98
Merge branch 'main'
googleworkspace-bot Apr 20, 2026
90b1293
clear && forge fmt && forge snapshot
googleworkspace-bot Apr 20, 2026
8380522
Merge branch 'main'
googleworkspace-bot Apr 20, 2026
932411c
Delete CNAME
Dargon789 Apr 30, 2026
8cd01bb
Main account (#97)
Dargon789 Apr 30, 2026
dea6ed5
Delete .idea directory (#98)
Dargon789 Apr 30, 2026
0c06532
feat: add merkle sigs natively into the account (#99)
Dargon789 Apr 30, 2026
7917955
Revert 58 ithaca (#100)
Dargon789 May 6, 2026
fe94e7b
Normalize file modes and update forge-std submodule (#103)
Dargon789 May 6, 2026
f9c6898
Update ci.yaml
Dargon789 May 6, 2026
0154f0a
Merge branch 'main' into master
Dargon789 May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: add merkle sigs natively into the account
  • Loading branch information
legion2002 committed Sep 24, 2025
commit 2ecce316b8bad62c40f31a5ceeb500726d926d1c
30 changes: 15 additions & 15 deletions snapshots/BenchmarkTest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
"testERC20Transfer_ERC4337MinimalAccount": "171509",
"testERC20Transfer_ERC4337MinimalAccount_AppSponsor": "168500",
"testERC20Transfer_ERC4337MinimalAccount_ERC20SelfPay": "199831",
"testERC20Transfer_IthacaAccount": "128059",
"testERC20Transfer_IthacaAccountWithSpendLimits": "193602",
"testERC20Transfer_IthacaAccount_AppSponsor": "138595",
"testERC20Transfer_IthacaAccount_AppSponsor_ERC20": "143884",
"testERC20Transfer_IthacaAccount_ERC20SelfPay": "128548",
"testERC20Transfer_IthacaAccount": "128100",
"testERC20Transfer_IthacaAccountWithSpendLimits": "193589",
"testERC20Transfer_IthacaAccount_AppSponsor": "138636",
"testERC20Transfer_IthacaAccount_AppSponsor_ERC20": "143937",
"testERC20Transfer_IthacaAccount_ERC20SelfPay": "128589",
"testERC20Transfer_Safe4337": "197561",
"testERC20Transfer_Safe4337_AppSponsor": "191725",
"testERC20Transfer_Safe4337_ERC20SelfPay": "221464",
Expand All @@ -29,25 +29,25 @@
"testERC20Transfer_ZerodevKernel_ERC20SelfPay": "235489",
"testERC20Transfer_batch100_AlchemyModularAccount": "10109066",
"testERC20Transfer_batch100_AlchemyModularAccount_ERC20SelfPay": "11609298",
"testERC20Transfer_batch100_IthacaAccount": "7531912",
"testERC20Transfer_batch100_IthacaAccount_AppSponsor": "8140216",
"testERC20Transfer_batch100_IthacaAccount_AppSponsor_ERC20": "7966120",
"testERC20Transfer_batch100_IthacaAccount_ERC20SelfPay": "7353064",
"testERC20Transfer_batch100_IthacaAccount": "7535892",
"testERC20Transfer_batch100_IthacaAccount_AppSponsor": "8144196",
"testERC20Transfer_batch100_IthacaAccount_AppSponsor_ERC20": "7970208",
"testERC20Transfer_batch100_IthacaAccount_ERC20SelfPay": "7357152",
"testERC20Transfer_batch100_ZerodevKernel": "12631318",
"testERC20Transfer_batch100_ZerodevKernel_ERC20SelfPay": "14149937",
"testNativeTransfer_AlchemyModularAccount": "180829",
"testNativeTransfer_CoinbaseSmartWallet": "178916",
"testNativeTransfer_IthacaAccount": "129415",
"testNativeTransfer_IthacaAccount_AppSponsor": "139982",
"testNativeTransfer_IthacaAccount_ERC20SelfPay": "137204",
"testNativeTransfer_IthacaAccount": "129456",
"testNativeTransfer_IthacaAccount_AppSponsor": "140023",
"testNativeTransfer_IthacaAccount_ERC20SelfPay": "137245",
"testNativeTransfer_Safe4337": "198595",
"testNativeTransfer_ZerodevKernel": "208635",
"testUniswapV2Swap_AlchemyModularAccount": "238647",
"testUniswapV2Swap_CoinbaseSmartWallet": "237451",
"testUniswapV2Swap_ERC4337MinimalAccount": "230691",
"testUniswapV2Swap_IthacaAccount": "187179",
"testUniswapV2Swap_IthacaAccount_AppSponsor": "197691",
"testUniswapV2Swap_IthacaAccount_ERC20SelfPay": "192480",
"testUniswapV2Swap_IthacaAccount": "187244",
"testUniswapV2Swap_IthacaAccount_AppSponsor": "197744",
"testUniswapV2Swap_IthacaAccount_ERC20SelfPay": "192533",
"testUniswapV2Swap_Safe4337": "257333",
"testUniswapV2Swap_ZerodevKernel": "266367"
}
59 changes: 57 additions & 2 deletions src/IthacaAccount.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {LibNonce} from "./libraries/LibNonce.sol";
import {TokenTransferLib} from "./libraries/TokenTransferLib.sol";
import {LibTStack} from "./libraries/LibTStack.sol";
import {IIthacaAccount} from "./interfaces/IIthacaAccount.sol";
import {MerkleProofLib} from "solady/utils/MerkleProofLib.sol";

/// @title Account
/// @notice A account contract for EOAs with EIP7702.
Expand Down Expand Up @@ -490,9 +491,52 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
return isMultichain ? _hashTypedDataSansChainId(structHash) : _hashTypedData(structHash);
}

/// @dev Verifies the merkle sig for the multi chain intents.
/// - Note: Each leaf of the merkle tree should be a standard intent digest, computed with chainId.
/// - Leaf intents do NOT need to have the multichain nonce prefix.
/// - The signature for multi chain intents using merkle verification is encoded as:
/// - bytes signature = abi.encode(bytes32[] proof, bytes32 root, bytes rootSig)
function _verifyMerkleSig(bytes32 digest, bytes calldata signature)
internal
view
returns (bool isValid, bytes32 keyHash)
{
bytes32[] calldata proof;
bytes32 root;
bytes calldata rootSig;

assembly ("memory-safe") {
let proofOffset := add(signature.offset, calldataload(signature.offset))
proof.length := calldataload(proofOffset)
proof.offset := add(proofOffset, 0x20)

root := calldataload(add(signature.offset, 0x20))

let rootSigOffset := add(signature.offset, calldataload(add(signature.offset, 0x40)))
rootSig.length := calldataload(rootSigOffset)
rootSig.offset := add(rootSigOffset, 0x20)
}

console.logBytes32(root);

console.log("Proof length: ", proof.length);
console.logBytes32(proof[0]);

if (MerkleProofLib.verifyCalldata(proof, root, digest)) {
console.log("Entered here");
(isValid, keyHash) = unwrapAndValidateSignature(root, rootSig);

console.logBytes32(keyHash);
console.log(isValid);
return (isValid, keyHash);
}

return (false, bytes32(0));
}

/// @dev Returns if the signature is valid, along with its `keyHash`.
/// The `signature` is a wrapped signature, given by
/// `abi.encodePacked(bytes(innerSignature), bytes32(keyHash), bool(prehash))`.
/// `abi.encode(bytes(innerSignature), bytes32(keyHash), bool(prehash), bool(merkle))`.
function unwrapAndValidateSignature(bytes32 digest, bytes calldata signature)
public
view
Expand All @@ -507,14 +551,25 @@ contract IthacaAccount is IIthacaAccount, EIP712, GuardedExecutor {
return (ECDSA.recoverCalldata(digest, signature) == address(this), 0);
}

bool merkle;
unchecked {
uint256 n = signature.length - 0x21;
uint256 n = signature.length - 0x22;
keyHash = LibBytes.loadCalldata(signature, n);
signature = LibBytes.truncatedCalldata(signature, n);
// Do the prehash if last byte is non-zero.
if (uint256(LibBytes.loadCalldata(signature, n + 1)) & 0xff != 0) {
console.log("prehash");
digest = EfficientHashLib.sha2(digest); // `sha256(abi.encode(digest))`.
}
merkle = uint256(LibBytes.loadCalldata(signature, n + 2)) & 0xff != 0;
}

console.logBytes(signature);

console.log("Reached here", merkle);
if (merkle) {
console.log("merkle");
return _verifyMerkleSig(digest, signature);
}

Key memory key = getKey(keyHash);
Expand Down