Complete Orderbook.sol executeWithdrawal Verification
Description
Implement full merkle proof verification in the executeWithdrawal function. The user submits both a ZK proof (proving the state root) and a merkle proof (proving the burn exists in that state) in a single transaction. State roots are never stored on-chain.
Tasks
Technical Details
File to Modify
Constants to Add
uint256 constant TREE_HEIGHT = 255;
executeWithdrawal Flow
- User submits withdrawal data + merkle siblings + ZK proof (imageId, journal, seal)
- Contract verifies ZK proof using IRiscZeroVerifier
- Contract extracts state root from journal (first 32 bytes)
- Contract verifies merkle proof against extracted state root
- If both pass, transfer tokens
Note: State roots are NOT stored on-chain. Both proofs are verified in the same transaction.
verifyBurnProof Function
Verifies a merkle proof for a burned withdrawal:
- Computes leaf data matching Rust
encode_burned_value format
- Computes burned key index using owner and nonce
- Walks up the tree using siblings
- Returns true if computed root matches expected root
executeWithdrawal Updates
- Requires state root to be in
verifiedStateRoots mapping
- Verifies merkle proof against the verified state root
- Existing nonce and chainId checks remain
Data Format Matching
Burned key index computation must match Rust:
bytes memory key = abi.encodePacked(
uint8(0x05), // BURNED prefix
uint8(0x00), // Ethereum address type
owner, // 20 bytes
bytes12(0), // Padding to 32 bytes
nonce // 8 bytes
);
return keccak256(key);
Leaf data must match Rust encode_burned_value:
bytes memory leafData = abi.encodePacked(
amount, // 32 bytes
uint8(0x00), // Ethereum address type
token, // 20 bytes
bytes12(0), // Padding
chainId // 32 bytes
);
Updated executeWithdrawal Signature
function executeWithdrawal(
Withdrawal calldata withdrawal,
bytes32 imageId,
bytes calldata journal,
bytes calldata seal
) external {
require(withdrawal.burn.user == msg.sender, "Invalid user");
require(withdrawal.burn.chainId == block.chainid, "Invalid chain ID");
require(!usedNonces[withdrawal.burn.user][withdrawal.burn.nonce], "Nonce already used");
// 1. Verify ZK proof
require(address(verifier) != address(0), "Verifier not set");
bytes32 journalDigest = sha256(journal);
bytes memory sealWithSelector = abi.encodePacked(verifierSelector, seal);
verifier.verify(sealWithSelector, imageId, journalDigest);
// 2. Extract state root from journal (first 32 bytes)
require(journal.length >= 32, "Invalid journal");
bytes32 stateRoot;
assembly {
stateRoot := calldataload(journal.offset)
}
// 3. Verify merkle proof against ZK-proven state root
require(verifyBurnProof(
withdrawal.burn.user,
withdrawal.burn.nonce,
withdrawal.burn.token,
withdrawal.burn.amount,
withdrawal.burn.chainId,
withdrawal.siblings,
stateRoot
), "Invalid merkle proof");
// 4. Mark nonce as used and transfer
usedNonces[withdrawal.burn.user][withdrawal.burn.nonce] = true;
IERC20(withdrawal.burn.token).safeTransfer(msg.sender, withdrawal.burn.amount);
}
Security Considerations
- ZK proof verified in same transaction as merkle proof (no stored state roots)
- Nonce tracking prevents double-withdrawals
- chainId check prevents cross-chain replay attacks
- Only the burn owner can execute their withdrawal
- imageId should be validated against expected guest program
Complete Orderbook.sol executeWithdrawal Verification
Description
Implement full merkle proof verification in the
executeWithdrawalfunction. The user submits both a ZK proof (proving the state root) and a merkle proof (proving the burn exists in that state) in a single transaction. State roots are never stored on-chain.Tasks
hashLeaf()andhashPair()helper functionscomputeBurnedKeyIndex()functionverifyBurnProof()function for merkle verificationexecuteWithdrawal()to accept both ZK proof and merkle proofTechnical Details
File to Modify
contracts/Orderbook.solConstants to Add
executeWithdrawal Flow
Note: State roots are NOT stored on-chain. Both proofs are verified in the same transaction.
verifyBurnProof Function
Verifies a merkle proof for a burned withdrawal:
encode_burned_valueformatexecuteWithdrawal Updates
verifiedStateRootsmappingData Format Matching
Burned key index computation must match Rust:
Leaf data must match Rust
encode_burned_value:Updated executeWithdrawal Signature
Security Considerations