Appearance
Abstract
While Externally Owned Accounts can validate signed messages with ecrecover()
and smart contracts can validate signatures using specifications outlined in ERC-1271, currently there is no standard method to create or validate signatures made by NFTs. We propose a standard way for anyone to validate whether a signature made by an NFT is valid. This is possible via a modified signature validation function originally found in ERC-1271: isValidSignature(tokenId, hash, data)
.
Motivation
With billions of ETH in trading volume, the Non-Fungible Token standard has exploded into tremendous popularity in recent years. Despite the far-reaching implications of having unique tokenized items on-chain, NFTs have mainly been used to represent artwork in the form of avatars or profile pictures. While this is certainly not a trivial use case for the ERC-721 & ERC-1155 token standards, we reckon more can be done to aid the community in discovering alternative uses for NFTs.
One of the alternative use cases for NFTs is using them to represent offices in an organization. In this case, tying signatures to transferrable NFTs instead of EOAs or smart contracts becomes crucial. Suppose there exists a DAO that utilizes NFTs as badges that represent certain administrative offices (i.e., CEO, COO, CFO, etc.) with a quarterly democratic election that potentially replaces those who currently occupy said offices. If the sitting COO has previously signed agreements or authorized certain actions, their past signatures would stay with the EOA who used to be the COO instead of the COO's office itself once they are replaced with another EOA as the new COO-elect. Although a multisig wallet for the entire DAO is one way to mitigate this problem, often it is helpful to generate signatures on a more intricate level so detailed separation of responsibilities are established and maintained. It is also feasible to appoint a smart contract instead of an EOA as the COO, but the complexities this solution brings are unnecessary. If a DAO uses ENS to establish their organizational hierarchy, this proposal would allow wrapped ENS subdomains (which are NFTs) to generate signatures.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
pragma solidity ^0.8.0;
interface IERC6066 {
/**
* @dev MUST return if the signature provided is valid for the provided tokenId and hash
* @param tokenId Token ID of the signing NFT
* @param hash Hash of the data to be signed
* @param data OPTIONAL arbitrary data that may aid verification
*
* MUST return the bytes4 magic value 0x12edb34f when function passes.
* MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
* MUST allow external calls
*
*/
function isValidSignature(
uint256 tokenId,
bytes32 hash,
bytes calldata data
) external view returns (bytes4 magicValue);
}
isValidSignature
can call arbitrary methods to validate a given signature.
This function MAY be implemented by ERC-721 or ERC-1155 compliant contracts that desire to enable its token holders to sign messages using their NFTs. Compliant callers wanting to support contract signatures MUST call this method if the signer is the holder of an NFT (ERC-721 or ERC-1155).
Rationale
We have purposefully decided to not include a signature generation standard in this proposal as it would restrict flexibility of such mechanism, just as ERC-1271 does not enforce a signing standard for smart contracts. We also decided to reference Gnosis Safe's contract signing approach as it is both simplistic and proven to be adequate. The bytes calldata data
parameter is considered optional if extra data is needed for signature verification, also conforming this EIP to ERC-5750 for future-proofing purposes.
Backwards Compatibility
This EIP is incompatible with previous work on signature validation as it does not validate any cryptographically generated signatures. Instead, signature is merely a boolean flag indicating consent. This is consistent with Gnosis Safe's contract signature implementation.
Reference Implementation
Example implementation of an ERC-721 compliant contract that conforms to ERC-6066 with a custom signing function:
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./interfaces/IERC6066.sol";
contract ERC6066Reference is ERC721, IERC6066 {
// type(IERC6066).interfaceId
bytes4 public constant MAGICVALUE = 0x12edb34f;
bytes4 public constant BADVALUE = 0xffffffff;
mapping(uint256 => mapping(bytes32 => bool)) internal _signatures;
error ENotTokenOwner();
/**
* @dev Checks if the sender owns NFT with ID tokenId
* @param tokenId Token ID of the signing NFT
*/
modifier onlyTokenOwner(uint256 tokenId) {
if (ownerOf(tokenId) != _msgSender()) revert ENotTokenOwner();
_;
}
constructor(string memory name_, string memory symbol_)
ERC721(name_, symbol_)
{}
/**
* @dev SHOULD sign the provided hash with NFT of tokenId given sender owns said NFT
* @param tokenId Token ID of the signing NFT
* @param hash Hash of the data to be signed
*/
function sign(uint256 tokenId, bytes32 hash)
external
onlyTokenOwner(tokenId)
{
_signatures[tokenId][hash] = true;
}
/**
* @dev MUST return if the signature provided is valid for the provided tokenId, hash, and optionally data
*/
function isValidSignature(uint256 tokenId, bytes32 hash, bytes calldata data)
external
view
override
returns (bytes4 magicValue)
{
// The data parameter is unused in this example
return _signatures[tokenId][hash] ? MAGICVALUE : BADVALUE;
}
/**
* @dev ERC-165 support
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
interfaceId == type(IERC6066).interfaceId ||
super.supportsInterface(interfaceId);
}
}
Security Considerations
The revokable nature of contract-based signatures carries over to this EIP. Developers and users alike should take it into consideration.
Copyright
Copyright and related rights waived via CC0.