AlertSourceDiscuss
Skip to content
On this page

ERC-5564: Stealth Addresses

Private, non-interactive transfers and interactions

⚠️ DraftERC

Draft Notice

This EIP is in the process of being drafted. The content of this EIP is not final and can change at any time; this EIP is not yet suitable for use in production. Thank you!

AuthorsToni Wahrstätter (@nerolation), Matt Solomon (@mds1), Ben DiFrancesco (@apbendi), Vitalik Buterin <vitalik.buterin@ethereum.org>
Created2022-09-05

Abstract

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.

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:

solidity
/// @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:

solidity
/// @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:

solidity
/// @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 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 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 one byte integer (schemeId) is used to identify stealth address schemes. The schemeId represents an incrementing integer beginning at 0. A mapping from the schemeId to it's specification MUST be declared in the EIP that proposes to standardize a new stealth address scheme. Furthermore, the schemeId MUST be added to this overview. These EIP extensions MUST specify:

  • The integer identifier for the scheme.

  • 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.

  • The algorithm for the generateStealthAddress method.

  • The algorithm for the checkStealthAddress method.

  • The algorithm for the computeStealthKey method.

This specification additionally defines a singleton ERC5564Messenger 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:

solidity
/// @notice Interface for announcing when something is sent to a stealth address.
contract IERC5564Messenger {
  /// @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, 
    bytes ephemeralPubKey, 
    bytes metadata
  );

  /// @dev Called by integrators to emit an `Announcement` event.
  /// @param schemeId The applied stealth address scheme (f.e. secp25k1).
  /// @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 ERC-20 tokens, the metadata SHOULD be structured as follows:
  ///   - Byte 1 MUST be the view tag, as specified above.
  ///   - Bytes 2-5 are the method Id, which the hash of the canonical representation of the function to call.
  ///   - Bytes 6-25 are the token contract address.
  ///   - Bytes 26-57 are the amount of tokens being sent.
  /// - When approving a stealth address to spend ERC-20 tokens, the metadata SHOULD be structured as follows:
  ///   - Byte 1 MUST be the view tag, as specified above.
  ///   - Bytes 2-5 are 0xe1f21c67, which the signature for the ERC-20 approve method.
  ///   - Bytes 6-25 are the token address.
  ///   - Bytes 26-57 are the approval amount.
  /// - When sending ERC-721 tokens, the metadata SHOULD be structured as follows:
  ///   - Byte 1 MUST be the view tag, as specified above.
  ///   - Bytes 2-5 are the method Id.
  ///   - Bytes 6-25 are the token address.
  ///   - Bytes 26-57 are the token ID of the token being sent.
  function announce (
    uint256 schemeId, 
    address stealthAddress, 
    bytes memory ephemeralPubKey, 
    bytes memory metadata
  )
    external
  {
    emit Announcement(schemeId, stealthAddress, ephemeralPubKey, metadata);
  }
}

An example stealth address scheme for elliptic curves is below. Other stealth schemes may need to modify this approach.

  • $G$ represents the generator point of the curve.

  • Recipient has published a stealth meta-address that constists of the public keys $P_{spend}$ and $P_{view}$.

  • Recipient has has access to the private keys $p_{spend}$, $p_{view}$ from which $P_{spend}$ and $P_{view}$ are derived.

  • 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$.

An example function to check whether a stealth address belongs to oneself:

  • User has access to the viewing private key $p_{view}$ and the public spending 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)$.
    • 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.

An example function to derive the private key of a stealth address:

  • User has access to the viewing private key, 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 ad security 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 1 bytes length was is based on the maximum required space to reliably filter not matching announcement. With 1 bytes as viewTag the probability for users to skip the remaining computations after hashing the shared secret $h(s)$ is $1/256$. This means that users can almost certainly skip the above three operations for any announcements that to 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.

Stealth meta-address format

The new address format for the stealth meta-addresses is based on ERC-3770 and extends it by adding a st: (stealth) as prefix. Thus, stealth meta-addresses on Ethereum come with the following format:

st:eth:0x<spendingKey><viewingKey>

Notably, the address-format is only used to differentiate stealth addresses from standard addresses, however, the prefix is sliced away before doing any computations on the stealth meta-address.

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.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Toni Wahrstätter, Matt Solomon, Ben DiFrancesco, Vitalik Buterin, "ERC-5564: Stealth Addresses[DRAFT]," Ethereum Improvement Proposals, no. 5564, 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5564.