forked from DecentralizedClimateFoundation/DCIPs
128 lines
6.8 KiB
Markdown
128 lines
6.8 KiB
Markdown
---
|
|
eip: 6066
|
|
title: Signature Validation Method for NFTs
|
|
description: A way to verify signatures when the signing entity is an ERC-721 or ERC-1155 NFT
|
|
author: Jack Boyuan Xu (@boyuanx)
|
|
discussions-to: https://ethereum-magicians.org/t/eip-6066-signature-validation-method-for-nfts/
|
|
status: Review
|
|
type: Standards Track
|
|
category: ERC
|
|
created: 2022-11-29
|
|
requires: 721, 1155, 1271, 5750
|
|
---
|
|
|
|
## Abstract
|
|
|
|
While **E**xternally **O**wned **A**ccounts can validate signed messages with `ecrecover()` and smart contracts can validate signatures using specifications outlined in [ERC-1271](./eip-1271.md), 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](./eip-1271.md): `isValidSignature(tokenId, hash, data)`.
|
|
|
|
## Motivation
|
|
|
|
With billions of ETH in trading volume, the **N**on-**F**ungible **T**oken 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](./eip-721.md) & [ERC-1155](./eip-1155.md) 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 IEIP6066 {
|
|
/**
|
|
* @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 0xdff13226 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](./eip-721.md) or [ERC-1155](./eip-1155.md) 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](./eip-721.md) or [ERC-1155](./eip-1155.md)).
|
|
|
|
## 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](./eip-1271.md) 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](./eip-5750.md) 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](./eip-721.md) compliant contract that conforms to [ERC-6066](./eip-6066.md) with a custom signing function:
|
|
|
|
```
|
|
pragma solidity ^0.8.0;
|
|
|
|
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
import "./interfaces/draft-IEIP6066.sol";
|
|
|
|
contract EIP6066Reference is ERC721, IEIP6066 {
|
|
bytes4 public constant MAGICVALUE = 0xdff13226;
|
|
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;
|
|
}
|
|
}
|
|
```
|
|
|
|
## 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](../LICENSE.md).
|