ERC-5564 Stealth Address (Stealth Addresses)

Final · Accounts & wallets · created 2022-08-13

Lets a sender create a private address that only the recipient can open and send assets to it, without contacting the recipient beforehand. A single notification contract per chain announces that a payment was received.

Why it is needed

All transactions on Ethereum are public. Anyone can see which address received which tokens, so once an address is linked to a person, that person's entire asset history is exposed. This is the case when someone wants to donate to a particular organization or country without revealing the connection between themselves and the recipient. A recipient can avoid tracking by creating and sharing a new address for every transaction, but this requires the two sides to exchange messages before every transfer. Unless the way a sender alone creates an address dedicated to a recipient is set as a standard, each wallet and app will do it differently, and recipients can easily lose track of where the assets sent to them are. Privacy features like this can only be widely used if a standard exists.

What to implement

In depth

The three keys and the meta-address length rule

A stealth meta-address is a bundle of one or two public keys used to compute the recipient's stealth address. The spending key is the private key that spends funds sent to a stealth address, and the viewing key is a private key that can only determine which stealth addresses belong to the recipient. Each has a corresponding spending public key and viewing public key. For a scheme whose public key is n bytes long, if the meta-address length is n, the same key is used as both the spending public key and the viewing public key, and if the length is 2n, the first n bytes are the spending public key and the last n bytes are the viewing public key. This rule is a MUST and every scheme must follow it.

The three functions every scheme must implement

The sender must be able to obtain a stealth address, an ephemeral public key (ephemeralPubKey), and a view tag (viewTag) from a meta-address using generateStealthAddress. The recipient must be able to check with checkStealthAddress whether a stealth address belongs to them, and to compute that address's private key with computeStealthKey. The stealthAddress argument of computeStealthKey is not strictly needed for the computation, but it is accepted so the derived private key can be verified as correct. The implementation of the three functions differs by scheme, and an ERC proposing a new scheme must specify the implementation of each function. The interface is written in Solidity, but it does not have to be implemented in Solidity. However, libraries or SDKs that follow this standard must implement compatible function interfaces.

schemeId and scheme 1 (SECP256k1 + view tags)

Schemes are identified by a 256-bit integer schemeId. An ERC that standardizes a new scheme must declare the mapping between the schemeId and its specification. Using a simple integer that increases by 1 is RECOMMENDED, but arbitrary or meaningful values are also allowed. This ERC defines schemeId 1. In scheme 1, the view tag must be included in the announcement event to reduce the recipient's parsing time, and the SECP256k1 curve is used to encode the meta-address (spending public key and viewing public key) as bytes and decode it back into key types. generateStealthAddress, checkStealthAddress, and computeStealthKey all use the SECP256k1 method with view tags.

The ERC5564Announcer singleton and the metadata rules

A single ERC5564Announcer contract per chain emits the Announcement event. The event has schemeId, stealthAddress, and caller as indexed fields and carries ephemeralPubKey and metadata. The announce function emits the event with the caller (msg.sender) as caller. The first byte of metadata must be the view tag, and the rest can be used freely, but the following structure is recommended. When sending the native token (ETH), bytes 2 to 5 hold 0xeeeeeeee, bytes 6 to 25 hold the address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, and bytes 26 to 57 hold the amount of ETH sent. For tokens such as ERC-20 and ERC-721, bytes 2 to 5 hold the function identifier (the function selector, if there is one, must be used), bytes 6 to 25 hold the token contract address, and bytes 26 to 57 hold the amount or token ID. The contract is deployed at 0x55649E01B5Df198D18D95b5cc5051630cfD45564, via CREATE2 through the deterministic deployer 0x4e59b44847b379578588920ca78fbf26c0b4956c, using the salt 0xd0103a290d760f027c9ca72675f5121d725397fb2f618f05b6c44958b25b4447.

Meta-address format st:eth:

A stealth meta-address is the chain-specific address format with the st: (stealth) prefix added in front. On Ethereum it takes the form st:eth:0x<spendingPubKey><viewingPubKey>. This format only serves to distinguish it from a regular address, and the prefix is stripped before computation. Users can manage meta-addresses themselves or register them in the public Registry contract defined in ERC-6538. With a registry, anyone can look up another person's meta-address in one place, and a recipient can signal that they are willing to transact via stealth addresses.

Address generation in scheme 1

The SECP256k1 curve is defined by y^2 = x^3 + 7 (mod p), with p = 2^256 - 2^32 - 977, and G is the generator point. The recipient holds the private keys p_spend and p_view and the corresponding public keys P_spend and P_view, and publishes the meta-address. generateStealthAddress computes in the following order. Create a 32-byte random ephemeral private key p_ephemeral and derive the ephemeral public key P_ephemeral. Parse P_spend and P_view from the meta-address. Compute the shared secret s = p_ephemeral · P_view and hash it to get s_h = h(s). The view tag v is the most significant byte of s_h, s_h[0]. Compute S_h = s_h · G and then the stealth public key P_stealth = P_spend + S_h. The stealth address a_stealth is pubkeyToAddress(P_stealth). The function returns a_stealth, P_ephemeral, and v.

Announcement checking and private key derivation in scheme 1

checkStealthAddress works using only the viewing private key p_view and the spending public key P_spend. It multiplies the announcement's ephemeral public key by p_view to get the shared secret s = p_view · P_ephemeral and computes s_h = h(s). It compares s_h[0] with the announcement's view tag, and if they differ the announcement is not yours, so the remaining computation is skipped. If they match, it computes S_h = s_h · G, P_stealth = P_spend + S_h, and a_stealth = pubkeyToAddress(P_stealth), and returns true if this matches the announcement's stealth address. computeStealthKey uses both the viewing private key and the spending private key p_spend. After obtaining s_h the same way, it computes the stealth private key as p_stealth = p_spend + s_h. Because deriving the private key requires the spending private key, the viewing key alone can only confirm receipt.

How view tags reduce parsing cost

Without a view tag, the recipient must perform 2 elliptic curve multiplications (ecMUL), 2 hashes, and 1 elliptic curve addition (ecADD) for every announcement they check. With a view tag, most announcements are filtered out by hashing the shared secret and comparing only the first byte, so only 1 ecMUL and 1 hash are needed, and parsing time drops by about 6 times. Because the view tag is 1 byte, the probability of skipping the remaining computation for an announcement that is not yours is 255/256. In exchange, 1 byte of the shared secret is revealed, so the security margin falls from 128 bits to 124 bits. This reduction affects only privacy and does not affect the security of stealth address generation itself. The authors describe this as a tradeoff in which view tags improve off-chain efficiency at the cost of making the announcement event more complex.

Things to watch out for

Original ERC · ← ERCs