How does signature verification work in NFT whitelist contracts using ECDSA?

I’m trying to understand how signature verification works for NFT whitelisting. I found this pattern in a smart contract but the ECDSA part confuses me. Can someone explain how this whitelist system actually works?

function purchase(uint256[] memory _tokenIds, uint256 _expiry, bytes memory _sig) public payable isActive {
    uint256 currentSupply = totalSupply();
    require(_tokenIds.length <= 3, "Exceeds max per transaction");
    require(currentSupply + _tokenIds.length <= MAX_SUPPLY, "Would exceed max supply");
    require(msg.value >= calculatePrice(_tokenIds.length), "Insufficient payment");
    
    address buyer = _msgSender();
    
    address recoveredSigner = verifySignature(buyer, _tokenIds, _expiry, _sig);
    require(recoveredSigner == contractOwner(), "Invalid signature");
    
    require(block.timestamp >= _expiry - 60, "Signature expired");
    
    for(uint8 j = 0; j < _tokenIds.length; j++){
        require(rawOwnerOf(_tokenIds[j]) == address(0) && _tokenIds[j] > 0 && _tokenIds[j] <= MAX_SUPPLY, "Token unavailable");
        _safeMint(buyer, _tokenIds[j]);
    }
}

function verifySignature(address buyer, uint256[] memory _tokenIds, uint256 _expiry, bytes memory _sig) public view returns (address){
    return ECDSA.recover(keccak256(abi.encode(buyer, _tokenIds, _expiry)), _sig);
}

I’m especially confused about these parts:

address recoveredSigner = verifySignature(buyer, _tokenIds, _expiry, _sig);
require(recoveredSigner == contractOwner(), "Invalid signature");

And:

function verifySignature(address buyer, uint256[] memory _tokenIds, uint256 _expiry, bytes memory _sig) public view returns (address){
    return ECDSA.recover(keccak256(abi.encode(buyer, _tokenIds, _expiry)), _sig);
}

How does this prove someone is whitelisted? Thanks for any help!

oh wait, i think i see what’s tripping you up! the whitelist isn’t stored on-chain at all - that’s the clever part. instead of a big list of approved addresses eating up gas, the contract owner becomes a “signature machine” that pre-approves specific transactions.

so imagine you’re whitelisted - the project owner uses their private key to sign a message saying “yes, address 0x123… can buy tokens [1,2,3] before timestamp 1234567890”. they give you that signature through their website.

when you call the purchase function, you’re basically saying “here’s my signature permission slip!” and the contract goes “let me check if this is really from the owner” by doing the math backwards. the keccak256(abi.encode(buyer, _tokenIds, _expiry)) recreates the exact same message that was originally signed, and ECDSA.recover figures out which address signed it.

btw that expiry check looks weird to me - shouldn’t it be block.timestamp <= _expiry instead of >= _expiry - 60? seems like it would accept expired signatures?

anyway, have you seen this pattern used in other projects? curious how they handle the signature generation on the backend!

Here’s how it works: the contract owner acts as the whitelist gatekeeper by pre-signing authorized purchases off-chain. Want to mint? You need the owner’s signature that specifically authorizes your address for those exact token IDs with an expiry time. ECDSA.recover does the math to prove who signed the message - it reconstructs the signer’s address from the signature. The contract hashes your address, token IDs, and expiry, then figures out which private key signed that data. Since only the contract owner has their private key, if the recovered address matches the owner’s address, the signature’s legit. This means the owner has to generate signatures for whitelisted users ahead of time, usually through a backend system. Each signature is unique to that specific buyer and token combo, so you can’t reuse or forge them. The expiry prevents old signatures from working forever.

the owner signs an off-chain msg: “yep, this addr can buy these tokens before expiry.” when you call purchase(), the contract rebuilds that msg from your params and uses ecdsa.recover to check who signed it. if it matches the owner’s addr, you’re gold. it’s like a VIP tick - only the bouncer (owner) can issue valid ones, and they’re made just for you.