This specification defines a standardized way of creating stealth addresses. This EIP enables senders of transactions/transfers to non-interactively generate private stealth addresses for their recipients that only the recipients can unlock.
Motivation
The standardization of non-interactive stealth address generation holds the potential to greatly enhance the privacy capabilities of Ethereum by enabling the recipient of a transfer to remain anonymous when receiving an asset. This is achieved through the generation of a stealth address by the sender, using a shared secret between the sender and recipient. Only the recipient is able to unlock the funds at the stealth address, as they are the only ones with access to the private key required for this purpose. As a result, observers are unable to link the recipient’s stealth address to their identity, preserving the privacy of the recipient and leaving only the sender with this information.
Specification
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.
The follow contracts are part of this specification:
IERC5564Registry stores the stealth public keys for users. This MUST be a singleton contract, with one instance per chain.
IERC5565Generator contracts are used to compute stealth addresses for a user based on a given curve. There can be many of these per chain, and for a given curve there SHOULD be one implementation per chain. Generator contracts are intended to primarily serve as reference implementations for off-chain libraries, as calling a method over HTTPS to generate a stealth address may compromise the user’s privacy depending on who runs the node.
IERC5564Messenger emits events to announce when something is sent to a stealth address. This MUST be a singleton contract, with one instance per chain.
The interface for each is specified as follows:
IERC5564Registry
/// @notice Registry to map an address to its stealth key information.
interfaceIERC5564Registry{/// @notice Returns the stealth public keys for the given `registrant` to compute a stealth
/// address accessible only to that `registrant` using the provided `generator` contract.
/// @dev MUST return zero if a registrant has not registered keys for the given generator.
functionstealthKeys(addressregistrant,addressgenerator)externalviewreturns(bytesmemoryspendingPubKey,bytesmemoryviewingPubKey);/// @notice Sets the caller's stealth public keys for the `generator` contract.
functionregisterKeys(addressgenerator,bytesmemoryspendingPubKey,bytesmemoryviewingPubKey)external;/// @notice Sets the `registrant`s stealth public keys for the `generator` contract using their
/// `signature`.
/// @dev MUST support both EOA signatures and EIP-1271 signatures.
functionregisterKeysOnBehalf(addressregistrant,addressgenerator,bytesmemorysignature,bytesmemoryspendingPubKey,bytesmemoryviewingPubKey)external;/// @dev Emitted when a registrant updates their registered stealth keys.
eventStealthKeyChanged(addressindexedregistrant,addressindexedgenerator,bytesspendingPubKey,bytesviewingPubKey);}
IERC5564Generator
/// @notice Interface for generating stealth addresses for keys from a given stealth address scheme.
/// @dev The Generator contract MUST have a method called `stealthKeys` that returns the recipient's
/// public keys as the correct types. The return types will vary for each generator, so a sample
/// is shown below.
interfaceIERC5564Generator{/// @notice Given a `registrant`, returns all relevant data to compute a stealth address.
/// @dev MUST return all zeroes if the registrant has not registered keys for this generator.
/// @dev The returned `viewTag` MUST be the hash of the `sharedSecret`. THe hashing function used
/// is specified by the generator.
/// @dev `ephemeralPubKey` represents the ephemeral public key used by the sender.
/// @dev Intended to be used off-chain only to prevent exposing secrets on-chain.
/// @dev Consider running this against a local node, or using an off-chain library with the same
/// logic, instead of via an `eth_call` to a public RPC provider to avoid leaking secrets.
functiongenerateStealthAddress(addressregistrant)externalviewreturns(addressstealthAddress,bytesmemoryephemeralPubKey,bytesmemorysharedSecret,bytes32viewTag);/// @notice Returns the stealth public keys for the given `registrant`, in the types that best
/// represent the curve.
/// @dev The below is an example for the secp256k1 curve.
functionstealthKeys(addressregistrant)externalviewreturns(uint256spendingPubKeyX,uint256spendingPubKeyY,uint256viewingPubKeyX,uint256viewingPubKeyY);}
IERC5564Messenger
/// @notice Interface for announcing that something was sent to a stealth address.
interfaceIERC5564Messenger{/// @dev Emitted when sending something to a stealth address.
/// @dev See `announce` for documentation on the parameters.
eventAnnouncement(bytesephemeralPubKey,bytes32indexedstealthRecipientAndViewTag,bytes32metadata);/// @dev Called by integrators to emit an `Announcement` event.
/// @dev `ephemeralPubKey` represents the ephemeral public key used by the sender.
/// @dev `stealthRecipientAndViewTag` contains the stealth address (20 bytes) and the view tag (12
/// bytes).
/// @dev `metadata` is an arbitrary field that the sender can use however they like, but the below
/// guidelines are recommended:
/// - When sending ERC-20 tokens, the metadata SHOULD include the token address as the first 20
/// bytes, and the amount being sent as the following 32 bytes.
/// - When sending ERC-721 tokens, the metadata SHOULD include the token address as the first 20
/// bytes, and the token ID being sent as the following 32 bytes.
functionannounce(bytesmemoryephemeralPubKey,bytes32stealthRecipientAndViewTag,bytes32metadata)external;}
Sample Generator Implementation
/// @notice Sample IERC5564Generator implementation for the secp256k1 curve.
contractSecp256k1GeneratorisIERC5564Generator{/// @notice Address of this chain's registry contract.
IERC5564RegistrypublicconstantREGISTRY=IERC5564Registry(address(0));/// @notice Sample implementation for parsing stealth keys on the secp256k1 curve.
functionstealthKeys(addressregistrant)externalviewreturns(uint256spendingPubKeyX,uint256spendingPubKeyY,uint256viewingPubKeyX,uint256viewingPubKeyY){// Fetch the raw spending and viewing keys from the registry.
(bytesmemoryspendingPubKey,bytesmemoryviewingPubKey)=REGISTRY.stealthKeys(registrant,address(this));// Parse the keys.
assembly{spendingPubKeyX:=mload(add(spendingPubKey,0x20))spendingPubKeyY:=mload(add(spendingPubKey,0x40))viewingPubKeyX:=mload(add(viewingPubKey,0x20))viewingPubKeyY:=mload(add(viewingPubKey,0x40))}}/// @notice Sample implementation for generating stealth addresses for the secp256k1 curve.
functiongenerateStealthAddress(addressregistrant,bytesmemoryephemeralPrivKey)externalviewreturns(addressstealthAddress,bytesmemoryephemeralPubKey,bytesmemorysharedSecret,bytes32viewTag){// Get the ephemeral public key from the private key.
ephemeralPubKey=ecMul(ephemeralPrivKey,G);// Get user's parsed public keys.
(uint256spendingPubKeyX,uint256spendingPubKeyY,uint256viewingPubKeyX,uint256viewingPubKeyY)=stealthKeys(registrant,address(this));// Generate shared secret from sender's private key and recipient's viewing key.
sharedSecret=ecMul(ephemeralPrivKey,viewingPubKeyX,viewingPubKeyY);bytes32sharedSecretHash=keccak256(sharedSecret);// Generate view tag for enabling faster parsing for the recipient
viewTag=sharedSecretHash[0:12];// Generate a point from the hash of the shared secret
bytesmemorysharedSecretPoint=ecMul(sharedSecret,G);// Generate sender's public key from their ephemeral private key.
bytesmemorystealthPubKey=ecAdd(spendingPubKeyX,spendingPubKeyY,sharedSecretPoint);// Compute stealth address from the stealth public key.
stealthAddress=pubkeyToAddress(stealthPubKey);}
Stealth addresses are computed using the algorithm below, assuming elliptic curves.
Other encryption schemes such as post-quantum encryption with Kyber may need to modify this approach.
$G$ is the generator point of the curve.
Recipient has private keys $p_{view}$ and $p_{spend}$.
Recipient publishes corresponding public keys $P_{view}$ and $P_{spend}$ in the IERC5564Registry.
Sender generates random 32-byte entropy ephemeral private key $p_{ephemeral}$.
Sender passes the recipient address and $p_{ephemeral}$ to the IERC5564Generator contract’s generateStealthAddress function.
This function performs the following computations:
A shared secret $s$ is computed as $s = p_{ephemeral} \cdot P_{view}$.
The secret is hashed $s_{h} = h(s)$.
The view tag $v$ is extracted by taking the most significant 12 bytes $s_{h}[0:12]$,
Multiplying the shared secret with the generator point $S = s \cdot G$.
The recipient’s stealth public key is computed as $P_{stealth} = P_{spend} + S$.
The recipient’s stealth address $a_{stealth}$ is computed as $\textrm{pubkeyToAddress}(P_{stealth})$.
Sending funds now works as follows:
Sender uses the contract of their choice to send something to $a_{stealth}$, and provides $P_{ephemeral}$ and any other metadata to the send method.
The contract calls IERC5564Messenger.announce with $a_{stealth}$, $v$, $P_{ephemeral}$, and any metadata.
To scan for funds, a recipient must retrieve all logs from the IERC5564Messenger contract.
They then check if they can compute the stealth address $P_{stealth}$ that was emitted as stealth address $a_{stealth}$ in the Announcement. If successful, the recipient can generate $p_{stealth}$, representing the private key that can eventually access $P_{stealth}$.
The parsing process can be presented as follows:
Recipient has private keys $p_{view}$ and $p_{spend}$.
Recipient parses all Announcements $a_i$ performs the following operations:
This function performs the following computations:
Computing the shared secret $s$ is computed as $s = a_{i, P_{ephemeral}} \cdot p_{view}$.
Hashing the shared secret, $s_{h} = h(s)$.
Comparing the most significant 12 bytes of the resulting hash with the view tag emitted in the event and continue if they match.
Multiplying the shared secret with the generator point $S = s \cdot G$.
Compute stealth public key as $P_{stealth} = P_{spend} + S$.
The recipient’s address is computed as $a_{stealth} = \textrm{pubkeyToAddress}(P_{stealth})$.
Compare $a_{stealth}$ with the stealth address logged the emitted Announcement event.
Parsing considerations
Usually, the recipient of a stealth address transaction has to perform the following operations to check weather he was the recipient of a certain transaction:
2x ecMUL,
2x HASH,
1x ecADD,
The view tags approach is introduced to reduce the parsing time by around 6x. Users only need to perform 1x ecMUL and 1x HASH (skipping 1x ecMUL, 1x ecADD and 1x HASH) for every parsed announcement. The 12 bytes length was is based on the freely available space in the first log of the Announcement Event. With 12 bytes as viewTag the probability for users to skip the remaining computations after hashing the shared secret $h(s)$ can be determined as follows: $1/(256^{12})$. This means that users can almost certainly skip the above three operations for any announcements that to do not involve them.
Rationale
This EIP emerged from the need of having privacy-preserving ways to transfer ownership without revealing the recipient’s identity. Tokens can reveal sensitive private information about the owner. While users might want to donate money to a specific organization/country but they might not want to reveal personal account-related information at the same time. The standardization of stealth address generation represents a significant effort for privacy: privacy-preserving solutions require standards to gain adoption, therefore it is critical to focus on generalizable ways of implementing related solutions.
The stealth address extension standardizes a protocol for generating and locating stealth addresses, enabling the transfer of assets without the need for prior interaction with the recipient and allowing recipients to verify the receipt of a transfer without interacting with the blockchain. Importantly, stealth addresses allow the recipient of a token transfer to verify receipt while maintaining their privacy, as only the recipient is able to see that they have been the recipient of the transfer.
The authors identify the trade-off between on- and off-chain efficiency: Although, including a Monero-like view tags mechanism helps recipients to parse announcements more quickly, it adds complexity to the announcement event.
The address of the recipient and the viewTag MUST be included in the announcement event, allowing users to quickly verify ownership without having to query the chain for positive account balances.
Backwards Compatibility
This EIP is fully backward compatible.
Reference Implementation
You can find an implementation of this standard in TBD.
Security Considerations
The funding of the stealth address wallet represents a known issue that might breach privacy. The wallet that funds the stealth address MUST NOT have any physical connection to the stealth address owner in order to fully leverage the privacy improvements.