I need help implementing a system that validates NFT ownership using EIP-712 signatures. My goal is to create a deposit function where users sign a message with MetaMask, and the contract checks if they actually own the NFT they’re trying to deposit.
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
using Counters for Counters.Counter;
using ECDSA for bytes32;
Counters.Counter private tokenCounter;
mapping(address => uint256) private deposits;
bytes32 private constant TOKEN_TYPEHASH =
keccak256("Token(address contractAddr,uint256 id)");
constructor() EIP712("TokenValidator", "1.0") {}
function submitToken(Token calldata token, bytes calldata sig) external returns (bool isValidOwner) {
require(validateSignature(token, sig));
uint256 currentId = tokenCounter.current();
deposits[msg.sender] = currentId;
tokenCounter.increment();
return isValidOwner;
}
function validateSignature(Token calldata token, bytes calldata sig) public view returns (bool) {
address recoveredSigner = _hashTypedDataV4(
keccak256(abi.encode(TOKEN_TYPEHASH, token.contractAddr, token.id))
).recover(sig);
return true;
}
The concept is that when someone calls submitToken, MetaMask creates a signature that proves ownership of the specified NFT. How can I properly implement the ownership verification part?
Hey! Love the EIP-712 signature approach - I’ve been working on something similar.
But how are you handling NFT ownership verification? Your validateSignature function just returns true without checking if the signer actually owns the NFT at token.contractAddr with id token.id.
Don’t you need to call the NFT contract? Something like IERC721(token.contractAddr).ownerOf(token.id) == recoveredSigner?
Security-wise, what stops someone from signing a message for an NFT they don’t own? The signature proves they signed it, but not that they own the token.
Also thinking about timing attacks - what if someone owns an NFT, signs the message, transfers it, then submits the old signature later?
Have you tested with MetaMask? Curious how you’re structuring the signing message - just contract address and token ID, or adding timestamps/nonces?
Sorry for the question dump! This is a cool use case. Building it for a specific dapp or just experimenting?
Your foundation’s solid, but you’re missing the actual ownership check. The validateSignature function recovers the signer address but never verifies they own the NFT. I’ve hit this same issue before. Here’s what you need:
function validateSignature(Token calldata token, bytes calldata sig) public view returns (bool) {
address recoveredSigner = _hashTypedDataV4(
keccak256(abi.encode(TOKEN_TYPEHASH, token.contractAddr, token.id))
).recover(sig);
return IERC721(token.contractAddr).ownerOf(token.id) == recoveredSigner;
}
Big problem though - you’re wide open to replay attacks. Someone signs while they own the NFT, sells it, then reuses that signature later. Add a nonce or timestamp to your Token struct and track used signatures. Also, your submitToken function declares isValidOwner as a return variable but never actually sets it. The ownership check happens in validateSignature, so you’ll want to clean up that logic flow.