eip | title | description | author | discussions-to | status | type | category | created | requires |
---|---|---|---|---|---|---|---|---|---|
<to be assigned> |
ERC-721 zk-Provable Token Extension |
An extension to the ERC-721 standard to add merkle trees for storing account balances |
Anton Wahrstätter (@Nerolation) |
Idea |
Standards Track |
ERC |
2022-08-04 |
EIP 165, 721 |
This specification defines an extension to the ERC-721 standard. The extension adds a standardized API for storing ownership information in merkle roots for s.
A standard interface for zk succinct non-interactive ownership proofs that allows users to prove ownership of an asset without revealing private account-related information.
Using merkle trees for storing a hash of
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
Every ERC0000
compliant contract MUST implement the ERC-721
(0x80ac58cd
) and ERC165
(0x01ffc9a7
) interfaces:
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.6;
...
interface ERC0001 /* is ERC721, ERC165 */ {
/// @notice Mints token to a stealth address `stA` if proof is valid. stA is derived from
/// stealthAddressBytes which is the MIMC Sponge hash `h` (220 rounds) of the user address `eoa`,
/// the token id `tid` and a user-generated secret `s`, such that stA <== address() <== h(eoa,tid,s).
/// @dev Requires a proof that verifies the following:
/// - prover can generate the StealthAddress (e.g. user signs msg => computePublicKey() => computeStealthAddress() ).
/// - prover can generate the merkle root from an empty leaf.
/// - prover can generate the merkle root after updating the empty leaf.
/// @param currentRoot A known (historic) root.
/// @param newRoot Updated root.
/// @param stealthAddressBytes Hash of user address, tokenId and secret.
/// @param tokenId The Id of the token.
/// @param proof The zk-SNARK.
function _mint(bytes32 currentRoot, bytes32 newRoot, bytes32 stealthAddressBytes, uint256 tokenId, bytes proof) external;
/// @notice Burns token with specified Id from stealth address `stA` if proof is valid.
/// @dev Requires a proof that verifies the following:
/// - prover can generate the StealthAddress (e.g. user signs msg => computePublicKey() => computeStealthAddress() )
/// - prover can generate the merkle root from an non-empty leaf.
/// - prover can generate the merkle root after nullifieing the non-empty leaf.
/// @param currentRoot A known (historic) root.
/// @param newRoot Updated root.
/// @param stealthAddressBytes Hash of user address, tokenId and secret.
/// @param tokenId The Id of the token.
/// @param proof The zk-SNARK.
function _burn(bytes32 currentRoot, bytes32 newRoot, bytes32 stealthAddressBytes, uint256 tokenId, bytes proof) external;
/// @notice Transfers token with specified Id from current owner to the recipient's
/// stealth address, if proof is valid.
/// @dev Requires a proof that verifies the following:
/// - prover can generate the StealthAddress (e.g. user signs msg => computePublicKey() => computeStealthAddress() ).
/// - prover can generate the merkle root from an non-empty leaf.
/// - prover can generate the merkle root after updating the non-empty leaf.
/// @param currentRoot A known (historic) root.
/// @param newRoot Updated root.
/// @param stealthAddressBytes Hash of user address, tokenId and secret.
/// @param tokenId The Id of the token.
/// @param proof The zk-SNARK.
function _transfer(bytes32 currentRoot, bytes32 newRoot, bytes32 stealthAddressBytes, uint256 tokenId, bytes proof) external;
/// @notice Verifies zk-SNARKs
/// @dev Forwards the different proofs to the right `Verifier` contracts.
/// Different Verifiers are required for each action, because of the merkle-tree logic involved.
/// @param currentRoot A known (historic) root.
/// @param newRoot Updated root.
/// @param stealthAddressBytes Hash of user address, tokenId and secret.
/// @param tokenId The Id of the token.
/// @param proof The zk-SNARK.
/// @return Validity of the provided proof.
function _verifyProof(bytes32 currentRoot, bytes32 newRoot, bytes32 stealthAddressBytes, uint256 tokenId, bytes proof) external returns (bool);
}
EIP-0000
emerged from the need to proof ownership of non-fungible tokens without revealing private information. While users might want to prove the ownership of a NFT-concert ticket, they might not want to reveal personal account-related information at the same time. Privacy-preserving solutions require standards to gain adoption, therefore it is critical to focus on generalisable ways of implement ownership-proofs into related contracts.
This extension enables to implement the framework required for zero-knowledge ownership proofs.
EIP-0000
extends ERC-721 contracts in a way to provide users possibilities to prove ownership in privacy-preserving ways. Basically, the contract maintains information about ownership in a merkle tree, in addition to commonly used mappings. Using zk-SNARKs (e.g. EdDSAMiMCVerifier
of circom, or see github at 0xPARC/circom-ecdsa), users can generate off-chain merkle-proofs and submit them to the contract for verification.
Implementing EIP-0000 adds a state variable storing the most recent merkle root to the ERC-721 standard. Further, an array with historic roots must be maintained.
Stealth Addresses are used to hide the owner of a certain token. The Stealth Address is generated from the ownerAddress address(uint160(uint256(stealthAddressBytes)))
.
The different functions require different circuits for proving. The verifyProof
MUST ensure to forward the respective proofs to the right Verifier
contract
All Verifier
contracts MUST represent a immutable contract.
EIP--- is not backwards compatibly with basic ERC-721
contracts. EIP---
implements the EIP-165
standard.
You can find an implementation of this standard in assets/eip-0000.sol.
Merkle-trees are defined and created during deployment and cannot be enlarged.
Copyright and related rights waived via CC0.