EDIT: I have generalised the idea a little bit more under this thread . You will also find the draft of the EIP and its implementation posted there. In this thread, I wanted to quickly discuss the idea and find fellows to collaborate with.
I’m thinking of an extension for the ERC-721 standard which would add a zk-SNARK interface to it.
The rational behind the idea is enabling the receivers of POAPs to proof their ownership without revealing the owners identity. As mentioned by Vitalik in a post on Soulbound tokens, having the option to proof ownerships without revealing identities would be a significant improvement to privacy in general.
Imagine, ETHGlobal wants to give out POAPs to the hackathon attendants, while allowing them to claim their POA without revealing their identities. At registration, attendants would provide their hashes (commitments). These consist of of a) a secrets and b) a nullifiers; both are private. After the event, the organizers would create a merkle tree, incorporating all the secrets of participating attendants. Attendants could then claim their POAPs to a fresh address and even after they were claimed, participants can still verify their participation using zk-proofs.
Merkle trees are commonly used by related privacy-preserving applications such as Tornado Cash or StealthDrop to generate zk merkle proofs that can be used to proof that a certain value is included in the merkle tree, without revealing its exact position. Therefore, I think the proposed implementation may be as minimalistic as possible but still providing a generalisable interface for similar applications.
As an extension, I propose to add 4 state variables to the ERC721 standard:
- root - representing the merkle tree root
- verifier - interface to the zk verifier contract (created with zksnark)
- nullifierHashes - array with known/claimed nullifiers
- tokenId - counter
Furthermore, this requires the following two functions:
- verify(…) - calls Verifier to verify proof
- root() - view function to get the root
After quickly drafting it out, it looks like the following:
pragma solidity 0.8.8;
...
contract zkExtension is ERC721 {
bytes32 public _root;
Verifier public _verifier;
mapping(bytes32 => bool) public _nullifierHashes;
uint256 private _tokenId;
constructor(
string memory name_,
string memory symbol_,
bytes32 root_,
Verifier verifier_
)
ERC721(name_, symbol_)
{
_root = root_;
_verifier = verifier_;
}
function root() public view virtual returns (bytes32) {
return _root;
}
// @notice mints POA to address if nullifierHash is unknown
// Returns true for valid proofs
function verify(uint[2] memory a_,
uint[2][2] memory b_,
uint[2] memory c_,
uint[1] memory input_,
bytes32 root_,
bytes32 nullifierHash_,
address recipient_) public returns (bool valid)
{
// Check if right tree
require(root_ == _root, "Wrong root");
if (_verifier.verifyProof(a_,b_,c_,input_)) {
if (!_nullifierHashes[nullifierHash_]) {
_nullifierHashes[nullifierHash_] = true;
_mint(recipient_, _tokenId);
_tokenId += 1;
}
return true;
}
return false;
}
}
I don’t have conrete plans yet to propose an EIP. I’m still at the beginning with zk-SNARKS and the gerneral Ethereum development process. I first wanted to invite the zk-SNARK community to provide me with some feedback on the idea in general and if its appropriate for an EIP. If someone with more experience on EIPs and SNARKS would like to join - perfect, I am open to collaborate!
Note, building on top of ERC721 would include transfer functionality. As soon as a valid standard for Soulbound tokens exists, it can be adapted. Anyways, proofs would be valid even when tokens were transfered.
EDIT: Storing the recipient addresses in another merkle tree and implementing a PrivToAddr circuit may enable to prove ownership after claiming. This would result in two merkle trees, one for claiming and one for the ownership structure after the claiming. The merkle tree could be the same that is used at TornadoCash. It would live on-chain and provide users with root + branches to prove their ownership/leaf.