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.
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.
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:
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:
n
uses the same stealth meta-address for the spending public key and viewing public key.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.
function generateStealthAddress(bytes memory stealthMetaAddress)
external
view
returns (address stealthAddress, bytes memory ephemeralPubKey, bytes1 viewTag);
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.
function checkStealthAddress(
address stealthAddress,
bytes memory ephemeralPubKey,
bytes memory viewingKey,
bytes memory spendingPubKey
) external view returns (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.
function computeStealthKey(
address stealthAddress,
bytes memory ephemeralPubKey,
bytes memory viewingKey,
bytes memory spendingKey
) external view returns (bytes memory);
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 schemeId
s are chosen to be monotonically incrementing integers for simplicity, but arbitrary or meaningful schemeId
s 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.
bytes
array, and decoding it from bytes
to the native key types of that scheme.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.
contract IERC5564Announcer {
/// @dev Emitted when sending something to a stealth address.
/// @dev See the `announce` method for documentation on the parameters.
event Announcement (
uint256 indexed schemeId,
address indexed stealthAddress,
address indexed caller,
bytes ephemeralPubKey,
bytes metadata
);
/// @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.
function announce (
uint256 schemeId,
address stealthAddress,
bytes memory ephemeralPubKey,
bytes memory metadata
)
external
{
emit Announcement(schemeId, stealthAddress, msg.sender, ephemeralPubKey, metadata);
}
}
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.
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:
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:
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:
Announcement
is not for the user and the remaining steps can be skipped. If the view tags match, continue on.true
if the stealth address of the announcement matches the derived stealth address, else return false
.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:
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.
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.
This ERC is fully backward compatible.
The ERC5564Announcer
contract is deployed at 0x55649E01B5Df198D18D95b5cc5051630cfD45564
using CREATE2
via the deterministic deployer at 0x4e59b44847b379578588920ca78fbf26c0b4956c
with a salt of 0xd0103a290d760f027c9ca72675f5121d725397fb2f618f05b6c44958b25b4447
.
You can find the implementation of the ERC5564Announcer
contract here and the interface IERC5564Announcer.sol
here.
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.
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.
Copyright and related rights waived via CC0.