# Merkle Tree criteria can be resolved by wrong tokenIDs

Seaport's Merkle criteria proof verification accepted an intermediate tree hash as a tokenId, letting a fulfiller trade an unrequested NFT and drain the offerer's payment.

- Payout: Contest reward
- Severity: medium
- Program: OpenSea
- Credited: cmichel
- Disclosed: 2022-08-30
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-05-opensea-seaport-findings/issues/168
- Library page: https://bugbountylibrary.com/r/c4%3A2022-05-opensea-seaport%3AM-01
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** Seaport CriteriaResolution.sol Merkle proof verification for ERC721_WITH_CRITERIA orders
**Root cause:** _verifyProof uses the supplied tokenId directly as the leaf hash without hashing it, so any intermediate node hash of the Merkle tree is itself a valid 'leaf' and passes verification.
**Steps:**
1. Offerer builds Merkle tree over tokenIds 1 and 2; root = keccak256(min||max) = 0xe90b...c2e0, stored as identifierOrCriteria for an ERC721_WITH_CRITERIA consideration item.
2. Attacker mints/acquires an NFT whose tokenId equals the intermediate hash (root) value on a contract allowing arbitrary IDs.
3. Attacker calls `fulfillAdvancedOrder(advancedOrder, cr, bytes32(0))` with `CriteriaResolver(0, Side.CONSIDERATION, 0, uint256(merkleRoot), new bytes32[](0))` — empty proof, identifier = root.
4. Verification passes since computedHash starts as leaf (the root) and empty proof leaves it unchanged; trade settles, attacker receives offerer's payment.
**How found:** Code review of CriteriaResolution._verifyProof: noticed the assembly sets `computedHash := leaf` directly, meaning the tokenId is treated as an already-hashed leaf, so intermediate nodes are indistinguishable from leaves; confirmed with a forge PoC.
**Exploitation:** Attacker acquires an NFT with tokenId equal to an intermediate Merkle hash (e.g. the root) on any ERC721 contract permitting arbitrary IDs, then fulfills the criteria order with an empty proof and that identifier, receiving the offerer's ERC20 payment while delivering an NFT the offerer never requested.
**Insight:** When auditing Merkle verification, check whether leaves are hashed before being used as the starting computedHash; if the raw value is used, any intermediate node (including the root with an empty proof) validates as a leaf. Also consider that tokenIds are arbitrary per EIP-721, so hash-shaped IDs are acquirable.
