This specification establishes a standardized method for interacting with stealth addresses, which allow senders of transactions or transfers to non-interactively generate private accounts exclusively accessible by their recipients. Moreover, this specification enables developers to create stealth address protocols based on the foundational implementation outlined in this ERC, utilizing a singleton contract deployed at 0x55649E01B5Df198D18D95b5cc5051630cfD45564 to emit the necessary information for recipients. In addition to the base implementation, this ERC also outlines the first implementation of a cryptographic scheme, specifically the SECP256k1 curve.
Motivation
The standardization of non-interactive stealth address generation presents the potential to significantly improve the privacy capabilities of the Ethereum network and other EVM-compatible chains by allowing recipients to remain private when receiving assets. This is accomplished through the sender generating a stealth address based on a shared secret known exclusively to the sender and recipient. The recipients alone can access the funds stored at their stealth addresses, as they are the sole possessors of the necessary private key. As a result, observers are unable to associate the recipient’s stealth address with their identity, thereby preserving the recipient’s privacy and leaving the sender as the only party privy to this information. By offering a foundational implementation in the form of a single contract that is compatible with multiple cryptographic schemes, recipients are granted a centralized location to monitor, ensuring they do not overlook any incoming transactions.
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.
Definitions:
A “stealth meta-address” is a set of one or two public keys that can be used to compute a stealth address for a given recipient.
A “spending key” is a private key that can be used to spend funds sent to a stealth address. A “spending public key” is the corresponding public key.
A “viewing key” is a private key that can be used to determine if funds sent to a stealth address belong to the recipient who controls the corresponding spending key. A “viewing public key” is the corresponding public key.
Different stealth address schemes will have different expected stealth meta-address lengths. A scheme that uses public keys of length n bytes MUST define stealth meta-addresses as follows:
A stealth meta-address of length n uses the same stealth meta-address for the spending public key and viewing public key.
A stealth meta-address of length 2n uses the first n bytes as the spending public key and the last n bytes as the viewing public key.
Given a recipient’s stealth meta-address, a sender MUST be able generate a stealth address for the recipient by calling a method with the following signature:
/// @notice Generates a stealth address from a stealth meta address.
/// @param stealthMetaAddress The recipient's stealth meta-address.
/// @return stealthAddress The recipient's stealth address.
/// @return ephemeralPubKey The ephemeral public key used to generate the stealth address.
/// @return viewTag The view tag derived from the shared secret.
functiongenerateStealthAddress(bytesmemorystealthMetaAddress)externalviewreturns(addressstealthAddress,bytesmemoryephemeralPubKey,bytes1viewTag);
A recipient MUST be able to check if a stealth address belongs to them by calling a method with the following signature:
/// @notice Returns true if funds sent to a stealth address belong to the recipient who controls
/// the corresponding spending key.
/// @param stealthAddress The recipient's stealth address.
/// @param ephemeralPubKey The ephemeral public key used to generate the stealth address.
/// @param viewingKey The recipient's viewing private key.
/// @param spendingPubKey The recipient's spending public key.
/// @return True if funds sent to the stealth address belong to the recipient.
functioncheckStealthAddress(addressstealthAddress,bytesmemoryephemeralPubKey,bytesmemoryviewingKey,bytesmemoryspendingPubKey)externalviewreturns(bool);
A recipient MUST be able to compute the private key for a stealth address by calling a method with the following signature:
/// @notice Computes the stealth private key for a stealth address.
/// @param stealthAddress The expected stealth address.
/// @param ephemeralPubKey The ephemeral public key used to generate the stealth address.
/// @param viewingKey The recipient's viewing private key.
/// @param spendingKey The recipient's spending private key.
/// @return stealthKey The stealth private key corresponding to the stealth address.
/// @dev The stealth address input is not strictly necessary, but it is included so the method
/// can validate that the stealth private key was generated correctly.
functioncomputeStealthKey(addressstealthAddress,bytesmemoryephemeralPubKey,bytesmemoryviewingKey,bytesmemoryspendingKey)externalviewreturns(bytesmemory);
The implementation of these methods is scheme-specific. The specification of a new stealth address scheme MUST specify the implementation for each of these methods. Additionally, although these function interfaces are specified in Solidity, they do not necessarily ever need to be implemented in Solidity, but any library or SDK conforming to this specification MUST implement these methods with compatible function interfaces.
A 256 bit integer (schemeId) is used to identify stealth address schemes. A mapping from the schemeId to its specification MUST be declared in the ERC that proposes to standardize a new stealth address scheme. It is RECOMMENDED that schemeIds are chosen to be monotonically incrementing integers for simplicity, but arbitrary or meaningful schemeIds may be chosen. This ERC introduces schemeId 1 with the following extensions:
1 is the integer identifier for the scheme,
viewTags MUST be included in the announcement event and is used to reduce the parsing time for the recipients.
SECP256k1 is the algorithm for encoding a stealth meta-address (i.e. the spending public key and viewing public key) into a bytes array, and decoding it from bytes to the native key types of that scheme.
SECP256k1 with view tags will be used in generateStealthAddress, checkStealthAddress, and computeStealthKey methods.
This specification additionally defines a singleton ERC5564Announcer contract that emits events to announce when something is sent to a stealth address. This MUST be a singleton contract, with one instance per chain. The contract is specified as follows:
/// @notice Interface for announcing when something is sent to a stealth address.
contractIERC5564Announcer{/// @dev Emitted when sending something to a stealth address.
/// @dev See the `announce` method for documentation on the parameters.
eventAnnouncement(uint256indexedschemeId,addressindexedstealthAddress,addressindexedcaller,bytesephemeralPubKey,bytesmetadata);/// @dev Called by integrators to emit an `Announcement` event.
/// @param schemeId The integer specifying the applied stealth address scheme.
/// @param stealthAddress The computed stealth address for the recipient.
/// @param ephemeralPubKey Ephemeral public key used by the sender.
/// @param metadata An arbitrary field MUST include the view tag in the first byte.
/// Besides the view tag, the metadata can be used by the senders however they like,
/// but the below guidelines are recommended:
/// The first byte of the metadata MUST be the view tag.
/// - When sending/interacting with the native token of the blockchain (cf. ETH), the metadata SHOULD be structured as follows:
/// - Byte 1 MUST be the view tag, as specified above.
/// - Bytes 2-5 are `0xeeeeeeee`
/// - Bytes 6-25 are the address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
/// - Bytes 26-57 are the amount of ETH being sent.
/// - When interacting with ERC-20/ERC-721/etc. tokens, the metadata SHOULD be structured as follows:
/// - Byte 1 MUST be the view tag, as specified above.
/// - Bytes 2-5 are a function identifier. When a function selector (e.g.
/// the first (left, high-order in big-endian) four bytes of the Keccak-256
/// hash of the signature of the function, like Solidity and Vyper use) is
/// available, it MUST be used.
/// - Bytes 6-25 are the token contract address.
/// - Bytes 26-57 are the amount of tokens being sent/interacted with for fungible tokens, or
/// the token ID for non-fungible tokens.
functionannounce(uint256schemeId,addressstealthAddress,bytesmemoryephemeralPubKey,bytesmemorymetadata)external{emitAnnouncement(schemeId,stealthAddress,msg.sender,ephemeralPubKey,metadata);}}
Stealth meta-address format
The new address format for the stealth meta-address extends the chain specific address format by adding a st: (stealth) prefix.
Thus, a stealth meta-address on Ethereum has the following format:
st:eth:0x<spendingPubKey><viewingPubKey>
Stealth meta-addresses may be managed by the user and/or registered within a publicly available Registry contract, as delineated in ERC-6538. This provides users with a centralized location for identifying stealth meta-addresses associated with other individuals while simultaneously enabling recipients to express their openness to engage via stealth addresses.
Notably, the address format is used only to differentiate stealth addresses from standard addresses, as the prefix is removed before performing any computations on the stealth meta-address.
Initial Implementation of SECP256k1 with View Tags
This ERC provides a foundation that is not tied to any specific cryptographic system through the IERC5564Announcer contract. In addition, it introduces the first implementation of a stealth address scheme that utilizes the SECP256k1 elliptic curve and view tags. The SECP256k1 elliptic curve is defined with the equation $y^2 = x^3 + 7 \pmod{p}$, where $p = 2^{256} - 2^{32} - 977$.
The following reference is divided into three sections:
Stealth address generation
Parsing announcements
Stealth private key derivation
Definitions:
$G$ represents the generator point of the curve.
Generation - Generate stealth address from stealth meta-address:
Recipient has access to the private keys $p_{spend}$, $p_{view}$ from which public keys $P_{spend}$ and $P_{view}$ are derived.
Recipient has published a stealth meta-address that consists of the public keys $P_{spend}$ and $P_{view}$.
Sender passes the stealth meta-address to the generateStealthAddress function.
The generateStealthAddress function performs the following computations:
Generate a random 32-byte entropy ephemeral private key $p_{ephemeral}$.
Derive the ephemeral public key $P_{ephemeral}$ from $p_{ephemeral}$.
Parse the spending and viewing public keys, $P_{spend}$ and $P_{view}$, from the stealth meta-address.
A shared secret $s$ is computed as $s = p_{ephemeral} \cdot P_{view}$.
The secret is hashed $s_{h} = \textrm{h}(s)$.
The view tag $v$ is extracted by taking the most significant byte $s_{h}[0]$,
Multiply the hashed shared secret with the generator point $S_h = s_h \cdot G$.
The recipient’s stealth public key is computed as $P_{stealth} = P_{spend} + S_h$.
The recipient’s stealth address $a_{stealth}$ is computed as $\textrm{pubkeyToAddress}(P_{stealth})$.
The function returns the stealth address $a_{stealth}$, the ephemeral public key $P_{ephemeral}$ and the view tag $v$.
Parsing - Locate one’s own stealth address(es):
User has access to the viewing private key $p_{view}$ and the spending public key $P_{spend}$.
User has access to a set of Announcement events and applies the checkStealthAddress function to each of them.
The checkStealthAddress function performs the following computations:
Shared secret $s$ is computed by multiplying the viewing private key with the ephemeral public key of the announcement $s = p_{view}$ * $P_{ephemeral}$.
The secret is hashed $s_{h} = h(s)$.
The view tag $v$ is extracted by taking the most significant byte $s_{h}[0]$ and can be compared to the given view tag. If the view tags do not match, this Announcement is not for the user and the remaining steps can be skipped. If the view tags match, continue on.
Multiply the hashed shared secret with the generator point $S_h = s_h \cdot G$.
The stealth public key is computed as $P_{stealth} = P_{spend} + S_h$.
The derived stealth address $a_{stealth}$ is computed as $\textrm{pubkeyToAddress}(P_{stealth})$.
Return true if the stealth address of the announcement matches the derived stealth address, else return false.
Private key derivation - Generate the stealth address private key from the hashed shared secret and the spending private key.
User has access to the viewing private key $p_{view}$ and spending private key $p_{spend}$.
User has access to a set of Announcement events for which the checkStealthAddress function returns true.
The computeStealthKey function performs the following computations:
Shared secret $s$ is computed by multiplying the viewing private key with the ephemeral public key of the announcement $s = p_{view}$ * $P_{ephemeral}$.
The secret is hashed $s_{h} = h(s)$.
The stealth private key is computed as $p_{stealth} = p_{spend} + s_h$.
Parsing considerations
Usually, the recipient of a stealth address transaction has to perform the following operations to check whether 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 1-byte view tag length is based on the maximum required space to reliably filter non-matching announcements. With a 1-byte viewTag, the probability for users to skip the remaining computations after hashing the shared secret $h(s)$ is $255/256$. This means that users can almost certainly skip the above three operations for any announcements that do not involve them. Since the view tag reveals one byte of the shared secret, the security margin is reduced from 128 bits to 124 bits. Notably, this only affects the privacy and not the secure generation of a stealth address.
Rationale
This ERC emerged from the need for privacy-preserving ways to transfer ownership without disclosing any information about the recipients’ identities. Token ownership can expose sensitive personal information. While individuals may wish to donate to a specific organization or country, they might prefer not to disclose a link between themselves and the recipient simultaneously. Standardizing stealth address generation represents a significant step towards unlinkable interactions, since such privacy-enhancing solutions require standards to achieve widespread adoption. Consequently, it is crucial to focus on developing generalizable approaches for implementing related solutions.
The stealth address specification standardizes a protocol for generating and locating stealth addresses, facilitating the transfer of assets without requiring prior interaction with the recipient. This enables recipients to verify the receipt of a transfer without the need to interact with the blockchain and query account balances. Importantly, stealth addresses enable token transfer recipients to verify receipt while maintaining their privacy, as only the recipient can recognize themselves as the recipient of the transfer.
The authors recognize the trade-off between on- and off-chain efficiency. Although incorporating a Monero-like view tags mechanism enables recipients to parse announcements more efficiently, it adds complexity to the announcement event.
The recipient’s address and the viewTag must be included in the announcement event, allowing users to quickly verify ownership without querying the chain for positive account balances.
Backwards Compatibility
This ERC is fully backward compatible.
Deployment Method
The ERC5564Announcer contract is deployed at 0x55649E01B5Df198D18D95b5cc5051630cfD45564 using CREATE2 via the deterministic deployer at 0x4e59b44847b379578588920ca78fbf26c0b4956c with a salt of 0xd0103a290d760f027c9ca72675f5121d725397fb2f618f05b6c44958b25b4447.
Reference Implementation
You can find the implementation of the ERC5564Announcer contract here and the interface IERC5564Announcer.solhere.
Security Considerations
DoS Countermeasures
There are potential denial of service (DoS) attack vectors that are not mitigated by network transaction fees. Stealth transfer senders cause an externality for recipients, as parsing announcement events consumes computational resources that are not compensated with gas. Therefore, spamming announcement events can be a detriment to the user experience, as it can lead to longer parsing times.
We consider the incentives to carry out such an attack to be low because no monetary benefit can be obtained
However, to tackle potential spam, parsing providers may adopt their own anti-DoS attack methods. These may include ignoring the spamming users when serving announcements to users or, less harsh, de-prioritizing them when ordering the announcements. The indexed caller keyword may help parsing providers to effectively filter known spammers.
Furthermore, parsing providers have a few options to counter spam, such as introducing staking mechanisms or requiring senders to pay a toll before including their Announcement. Moreover, a Staking mechanism may allow users to stake an unslashable amount of ETH (similarly to ERC-4337), to help mitigate potential spam through sybil attacks and enable parsing providers filtering spam more effectively.
Introducing a toll, paid by sending users, would simply put a cost on each stealth address transaction, making spamming economically unattractive.
Recipients’ transaction costs
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.
Thus, the sender may attach a small amount of ETH to each stealth address transaction, thereby sponsoring subsequent transactions of the recipient.