forked from DecentralizedClimateFoundation/DCIPs
324 lines
19 KiB
Markdown
324 lines
19 KiB
Markdown
---
|
|
eip: 5564
|
|
title: Stealth Addresses
|
|
description: Private, non-interactive transfers and interactions
|
|
author: Toni Wahrstätter (@nerolation), Matt Solomon (@mds1), Ben DiFrancesco (@apbendi), Vitalik Buterin (@vbuterin)
|
|
discussions-to: https://ethereum-magicians.org/t/eip-5566-stealth-addresses-for-smart-contract-wallets/10614
|
|
status: Draft
|
|
type: Standards Track
|
|
category: ERC
|
|
created: 2022-08-13
|
|
---
|
|
|
|
## 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](../assets/eip-5564/scheme_ids.md). 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 is StakeManager{
|
|
/// @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);
|
|
}
|
|
}
|
|
```
|
|
|
|
The `ERC5564Messenger` contract inherits the `StakeManager` contract allowing users to stake ETH that is uses as an anti-DoS measure. More details in the ... section.
|
|
|
|
```solidity
|
|
/// @notice Interface for the Stake Manager contract.
|
|
contract StakeManager{
|
|
|
|
/// @dev Emitted when stake is deposited.
|
|
/// @param account The address of the staker who deposited the stake.
|
|
/// @param totalStaked The new total amount of staked tokens for the account.
|
|
event StakeDeposit (
|
|
address indexed account,
|
|
uint256 totalStaked
|
|
);
|
|
|
|
/// @dev Emitted when stake is withdrawn.
|
|
/// @param account The address of the staker who withdrew the stake.
|
|
/// @param withdrawAddress The address to which the withdrawn amount was sent.
|
|
/// @param amount The amount of tokens withdrawn.
|
|
event StakeWithdrawal (
|
|
address indexed account,
|
|
address withdrawAddress,
|
|
uint256 amount
|
|
);
|
|
|
|
/**
|
|
* @notice Returns the stake of the account.
|
|
* @param account The address of the staker to check the stake for.
|
|
* @return uint256 The amount of staked tokens for the account.
|
|
*/
|
|
function balanceOf(address account) external view returns (uint256);
|
|
|
|
/**
|
|
* @notice Adds the specified amount to the account's stake.
|
|
* @dev The function is payable, so the amount is sent as value with the transaction.
|
|
* @param staker The address of the staker to add the stake for.
|
|
*/
|
|
function addStake(address staker) external payable;
|
|
|
|
/**
|
|
* @notice Withdraws the stake for the caller and sends it to the specified address.
|
|
* @param withdrawAddress The address to send the withdrawn amount to.
|
|
*/
|
|
function withdrawStake(address payable withdrawAddress) external;
|
|
}
|
|
```
|
|
|
|
|
|
### Stealth meta-address format
|
|
|
|
The new address format for the stealth meta-addresses is based on [ERC-3770](./eip-3770.md) 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, as the prefix is sliced away before doing any computations on the stealth meta-address.*
|
|
|
|
---
|
|
|
|
### Initial Implementation of SECP256k1 with View Tags
|
|
|
|
This improvement proposal provides a foundation that is not tied to any specific cryptographic system through the `IERC5564Messenger` contract. In addition, it introduces the first implementation of a [stealth address scheme](../assets/eip-5564/scheme_ids.md) 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:
|
|
|
|
1. Stealth address generation
|
|
|
|
2. Parsing announcements
|
|
|
|
3. 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 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)$.
|
|
- 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 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.
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
### DoS Measures
|
|
|
|
There are potential 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 as low because **no monetary benefit can be obtained** and, in theory, nothing prevents parsing providers to ignore the spamming when serving announcements to users.
|
|
However, sophisticated spamming (*sybil attacks*), which are not considered worth the associated costs, could make it difficult for paprsing providers to develop filters for such announcement events.
|
|
Therefore, to counter spamming directly, a staking mechanism is introduced in the EIP that allows users to stake an unslashable amount of ETH. Staking allows parsing providers to better tackle potential spam through *sybil attacks*, enabling them to filter spam more effectively filter.
|
|
|
|
Similar to [ERC-4337](./eip-4337), parsing providers agree on a `MINIMUM_STAKE`, such that the minimum required stake is not enforce on-chain. Users *can* withdraw their stake at any time without any delay. Parsing providers can de-prioritized senders who have not staked a certain minimum amount or withdrew their stake immediatly.
|
|
|
|
### 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.
|
|
|
|
## Copyright
|
|
|
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|