How does ECDSA signature verification work in NFT whitelist implementation?

I’m trying to understand how ECDSA signature verification works for NFT whitelisting. I found this pattern in a smart contract but I’m confused about the mechanics.

function purchase(uint256[] memory _tokenIds, uint256 _expiry, bytes memory _sig) public payable onlyActive {
    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 = validateSignature(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(!_exists(_tokenIds[j]) && _tokenIds[j] > 0 && _tokenIds[j] <= MAX_SUPPLY, "Invalid token ID");
        _safeMint(buyer, _tokenIds[j]);
    }
}

function validateSignature(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 don’t get how this part works:

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

Can someone explain the theory behind this whitelist mechanism? How does the signature verification actually prove authorization?

Oh interesting! I’ve been diving into this lately too and it’s pretty clever.

It’s basically a digital voucher system. The contract owner uses their private key to “sign” a message saying “this address can mint these token IDs before this expiry time.” The signature proves the owner authorized it.

When you call the purchase function, the contract hashes all that data (your address, token ids, expiry) with keccak256. Then ECDSA.recover works backwards - takes that hash and signature to figure out which address signed it. If it matches the contract owner’s address, you’re authorized!

I’m curious though - how does the owner generate these signatures off-chain? Do they run some backend service that creates them when people join the whitelist? And how do users get their signatures?

Also @LeapingFox have you thought about security here? What if someone intercepts a signature - could they replay it or is there protection beyond just the expiry timestamp?

Signature verification serves as a cryptographic method that confirms an individual’s authorization to mint. This process works as follows: to mint from the whitelist, you must possess a signature created by the contract owner’s private key. The owner generates these signatures in advance by signing a hash that contains your address, the token IDs, and the expiration time. When you invoke the purchase function, the contract utilizes ECDSA.recover to identify the address associated with that signature. Since only the owner’s private key can produce signatures that correlate back to their address, any attempt with a fake or invalid signature will result in a mismatch, causing the transaction to revert. This method is efficient as it eliminates the necessity to store whitelist addresses on-chain, saving gas fees. You can think of the signature as your authorization to mint specific tokens within the given timeframe.

Exactly! You build trust without bloating the chain with huge lists. The owner signs proof off-chain, then when you submit it, the contract just checks if it matches their key. Way more efficient and blocks fake mints.