Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-5564: Non-Interactive Stealth Address Generation

Stealth addresses for private transfers

Authors Toni Wahrstätter (@nerolation), Matt Solomon (@mds1), Ben DiFrancesco (@apbendi), Vitalik Buterin <[email protected]>
Created 2022-08-13
Discussion Link https://ethereum-magicians.org/t/eip-5566-stealth-addresses-for-smart-contract-wallets/10614

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.

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.
interface IERC5564Registry {
  /// @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.
  function stealthKeys(address registrant, address generator)
    external
    view
    returns (bytes memory spendingPubKey, bytes memory viewingPubKey);

  /// @notice Sets the caller's stealth public keys for the `generator` contract.
  function registerKeys(address generator, bytes memory spendingPubKey, bytes memory viewingPubKey)
    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.
  function registerKeysOnBehalf(
    address registrant,
    address generator,
    bytes memory signature,
    bytes memory spendingPubKey,
    bytes memory viewingPubKey
  ) external;

  /// @dev Emitted when a registrant updates their registered stealth keys.
  event StealthKeyChanged(
    address indexed registrant, address indexed generator, bytes spendingPubKey, bytes viewingPubKey
  );
}

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.
interface IERC5564Generator {
  /// @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.
  function generateStealthAddress(address registrant)
    external
    view
    returns (
      address stealthAddress,
      bytes memory ephemeralPubKey,
      bytes memory sharedSecret,
      bytes32 viewTag
    );

  /// @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.
  function stealthKeys(address registrant)
    external
    view
    returns (
      uint256 spendingPubKeyX,
      uint256 spendingPubKeyY,
      uint256 viewingPubKeyX,
      uint256 viewingPubKeyY
    );
}

IERC5564Messenger

/// @notice Interface for announcing that something was sent to a stealth address.
interface IERC5564Messenger {
  /// @dev Emitted when sending something to a stealth address.
  /// @dev See `announce` for documentation on the parameters.
  event Announcement(
    bytes ephemeralPubKey, bytes32 indexed stealthRecipientAndViewTag, bytes32 metadata
  );

  /// @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.
  function announce(
    bytes memory ephemeralPubKey,
    bytes32 stealthRecipientAndViewTag,
    bytes32 metadata
  ) external;
}

Sample Generator Implementation

/// @notice Sample IERC5564Generator implementation for the secp256k1 curve.
contract Secp256k1Generator is IERC5564Generator {
  /// @notice Address of this chain's registry contract.
  IERC5564Registry public constant REGISTRY = IERC5564Registry(address(0));

  /// @notice Sample implementation for parsing stealth keys on the secp256k1 curve.
  function stealthKeys(address registrant)
    external
    view
    returns (
      uint256 spendingPubKeyX,
      uint256 spendingPubKeyY,
      uint256 viewingPubKeyX,
      uint256 viewingPubKeyY
    )
  {
    // Fetch the raw spending and viewing keys from the registry.
    (bytes memory spendingPubKey, bytes memory viewingPubKey) =
      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.
  function generateStealthAddress(address registrant, bytes memory ephemeralPrivKey)
    external
    view
    returns (
      address stealthAddress,
      bytes memory ephemeralPubKey,
      bytes memory sharedSecret,
      bytes32 viewTag
    )
  {
    // Get the ephemeral public key from the private key.
    ephemeralPubKey = ecMul(ephemeralPrivKey, G);

    // Get user's parsed public keys.
    (
      uint256 spendingPubKeyX,
      uint256 spendingPubKeyY,
      uint256 viewingPubKeyX,
      uint256 viewingPubKeyY
    ) = stealthKeys(registrant, address(this));

    // Generate shared secret from sender's private key and recipient's viewing key.
    sharedSecret = ecMul(ephemeralPrivKey, viewingPubKeyX, viewingPubKeyY);
    bytes32 sharedSecretHash = 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
    bytes memory sharedSecretPoint = ecMul(sharedSecret, G);

    // Generate sender's public key from their ephemeral private key.
    bytes memory stealthPubKey = 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.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Toni Wahrstätter (@nerolation), Matt Solomon (@mds1), Ben DiFrancesco (@apbendi), Vitalik Buterin <[email protected]>, "EIP-5564: Non-Interactive Stealth Address Generation [DRAFT]," Ethereum Improvement Proposals, no. 5564, August 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5564.