This standard defines IZRC20, a minimal interface for privacy-preserving fungible tokens on Ethereum. It uses zero-knowledge proofs to enable confidential transfers where transaction amounts, sender, and recipient identities remain hidden. The core mechanism relies on cryptographic commitments stored in a Merkle tree, with nullifiers preventing double-spending.
This interface serves as a foundational building block for both wrapper protocols (adding privacy to existing ERC-20 tokens) and dual-mode tokens (single tokens supporting both transparent and private transfers).
Motivation
Privacy Infrastructure Needs Standardization
While building privacy solutions for Ethereum, we identified recurring patterns:
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.
Definitions
Native Privacy Asset: A token with privacy as an inherent property from genesis, not achieved through post-hoc mixing
Commitment: A cryptographic binding of value and ownership that hides both the amount and recipient identity
Nullifier: A unique identifier proving a commitment has been spent, preventing double-spending
Note: Off-chain encrypted data (amount, publicKey, randomness) for recipient
Merkle Tree: Authenticated structure storing commitments for zero-knowledge membership proofs
Proof Type: Parameter routing different proof strategies
Core Interface
// SPDX-License-Identifier: CC0-1.0
pragmasolidity^0.8.0;/**
* @title IZRC20
* @notice Minimal interface for native privacy assets on Ethereum
* @dev This standard defines the foundation for privacy-preserving tokens
* that can be used directly or as building blocks for wrapper protocols
* and dual-mode protocols implementations.
*/interfaceIZRC20{// ═══════════════════════════════════════════════════════════════════════
// Events
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Emitted when a commitment is added to the Merkle tree
* @param subtreeIndex Subtree index (0 for single-tree implementations)
* @param commitment The cryptographic commitment hash
* @param leafIndex Position within subtree (or global index)
* @param timestamp Block timestamp of insertion
* @dev For single-tree: subtreeIndex SHOULD be 0, leafIndex is global position
* @dev For dual-tree: subtreeIndex identifies which subtree, leafIndex is position within it
*/eventCommitmentAppended(uint32indexedsubtreeIndex,bytes32commitment,uint32indexedleafIndex,uint256timestamp);/**
* @notice Emitted when a nullifier is spent (note consumed)
* @param nullifier The unique nullifier hash
* @dev Once spent, nullifier can never be reused (prevents double-spending)
*/eventNullifierSpent(bytes32indexednullifier);/**
* @notice Emitted when tokens are minted directly into privacy mode
* @param minter Address that initiated the mint
* @param commitment The commitment created for minted value
* @param encryptedNote Encrypted note for recipient
* @param subtreeIndex Subtree where commitment was added
* @param leafIndex Position within subtree
* @param timestamp Block timestamp of mint
*/eventMinted(addressindexedminter,bytes32commitment,bytesencryptedNote,uint32subtreeIndex,uint32leafIndex,uint256timestamp);/**
* @notice Emitted on privacy transfers with public scanning data
* @param newCommitments Output commitments created (typically 1-2)
* @param encryptedNotes Encrypted notes for recipients
* @param ephemeralPublicKey Ephemeral public key for ECDH key exchange (if used)
* @param viewTag Scanning optimization byte (0 if not used)
* @dev Provides data for recipients to detect and decrypt their notes
*/eventTransaction(bytes32[2]newCommitments,bytes[]encryptedNotes,uint256[2]ephemeralPublicKey,uint256viewTag);// ═══════════════════════════════════════════════════════════════════════
// Metadata (ERC-20 compatible, OPTIONAL but RECOMMENDED)
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Returns the token name
* @return Token name string
* @dev OPTIONAL but RECOMMENDED for UX and interoperability
*/functionname()externalviewreturns(stringmemory);/**
* @notice Returns the token symbol
* @return Token symbol string
* @dev OPTIONAL but RECOMMENDED for UX and interoperability
*/functionsymbol()externalviewreturns(stringmemory);/**
* @notice Returns the number of decimals
* @return Number of decimals (typically 18)
* @dev OPTIONAL but RECOMMENDED for amount formatting
*/functiondecimals()externalviewreturns(uint8);/**
* @notice Returns the total supply across all privacy notes
* @return Total token supply
* @dev OPTIONAL - May be required for certain economic models (e.g., fixed cap)
* Individual balances remain private; only aggregate supply is visible
*/functiontotalSupply()externalviewreturns(uint256);// ═══════════════════════════════════════════════════════════════════════
// Core Functions
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Mints new privacy tokens
* @param proofType Type of proof to support multiple proof strategies.
* @param proof Zero-knowledge proof of valid transfer
* @param encryptedNote Encrypted note for minter's wallet
* @dev Proof must demonstrate valid commitment creation and payment
* Implementations define minting rules
*/functionmint(uint8proofType,bytescalldataproof,bytescalldataencryptedNote)externalpayable;/**
* @notice Executes a privacy-preserving transfer
* @param proofType Implementation-specific proof type identifier
* @param proof Zero-knowledge proof of valid transfer
* @param encryptedNotes Encrypted output notes (for recipient and/or change)
* @dev Proof must demonstrate:
* 1. Input commitments exist in Merkle tree
* 2. Prover knows private keys
* 3. Nullifiers not spent
* 4. Value conservation: sum(inputs) = sum(outputs)
*/functiontransfer(uint8proofType,bytescalldataproof,bytes[]calldataencryptedNotes)external;// ═══════════════════════════════════════════════════════════════════════
// Query Functions
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Check if a nullifier has been spent
* @param nullifier The nullifier to check
* @return True if nullifier spent, false otherwise
* @dev Implementations using `mapping(bytes32 => bool) public nullifiers`
* will auto-generate this function.
*/functionnullifiers(bytes32nullifier)externalviewreturns(bool);/**
* @notice Returns the current active subtree Merkle root
* @return The root hash of the active subtree
* @dev The active subtree stores recent commitments for faster proof computation.
* For dual-tree implementations, this is the root of the current working subtree.
*/functionactiveSubtreeRoot()externalviewreturns(bytes32);// ═══════════════════════════════════════════════════════════════════════
// Privacy Configuration (OPTIONAL but RECOMMENDED for client interoperability)
// ═══════════════════════════════════════════════════════════════════════
/**
* @notice Returns the URI pointing to the Privacy Configuration File
* @return URI string (e.g., "ipfs://Qm..." or "https://...")
* @dev OPTIONAL but RECOMMENDED for client interoperability
* The configuration file contains implementation-specific parameters
* See specification for the full Privacy Configuration File schema
*/functionprivacyConfigURI()externalviewreturns(stringmemory);/**
* @notice Sets the Privacy Configuration File URI
* @param configURI The configuration URI (can be set multiple times to update)
* @dev OPTIONAL - Implementation may restrict access (e.g., onlyOwner)
* Each call overwrites the previous URI
*/functionsetPrivacyConfigURI(stringcalldataconfigURI)external;}
Privacy Configuration File
Since this standard defines a minimal interface, different implementations may use different:
To enable client interoperability across different implementations, this standard defines an OPTIONAL but RECOMMENDED Privacy Configuration File mechanism, inspired by ERC-8004’s Agent Registration File pattern.
Configuration URI Functions
Implementations SHOULD provide:
privacyConfigURI(): Returns the URI pointing to the configuration file
setPrivacyConfigURI(string): Sets/updates the configuration URI (typically owner-restricted)
Privacy Configuration File Schema
The configuration file MUST be a valid JSON document. The following shows the schema structure with field descriptions:
Public mode: Standard ERC-20 (works with all DeFi)
Private mode: Standard IZRC20 (works with all privacy wallets)
Higher-level protocols can focus more on specific business scenarios and solving concrete problems—this is the advantage of a unified privacy asset interface.
Backwards Compatibility
This standard defines a minimal interface for native privacy assets. It is an independent interface implementation that does not depend on other protocols.
As described in the Motivation section, this standard serves as a foundational building block for higher-level protocols to rapidly implement privacy capabilities: