Raxol.Payments.Xochi.Stealth (Raxol Payments v0.2.0)

Copy Markdown View Source

ERC-5564/ERC-6538 stealth address derivation and scanning.

Implements the secp256k1 stealth address scheme: a sender generates an ephemeral keypair, computes a shared secret with the recipient's viewing key via ECDH, and derives a one-time stealth address. The recipient scans announcements using their viewing key and view tag optimization (256x speedup).

Flow

Sender (generate):

  1. Parse recipient's meta-address (spending pubkey + viewing pubkey)
  2. Generate ephemeral keypair (r, R = r*G)
  3. Compute shared secret: S = r * V (ECDH with viewing pubkey)
  4. Hash: h = keccak256(compress(S))
  5. View tag: first byte of h
  6. Stealth pubkey: P' = P + h*G
  7. Stealth address: last 20 bytes of keccak256(P'_uncompressed[1:])

Recipient (scan):

  1. For each announcement, check view tag (fast filter, 1:256 false positive)
  2. Compute S = v * R (ECDH with ephemeral pubkey)
  3. Derive expected stealth address
  4. If match: stealth privkey = (p + h) mod n

Standards

  • ERC-5564: Stealth Addresses (announce format)
  • ERC-6538: Stealth Meta-Address Registry
  • Scheme ID 1: secp256k1 with view tags

Summary

Functions

ERC-5564 Announcer contract address (Ethereum mainnet).

Create metadata bytes with view tag as first byte.

Decode a meta-address from ERC-6538 st:eth:0x format.

Derive stealth keys from an EVM signature (domain-separated).

Encode a meta-address in ERC-6538 st:eth:0x format.

Extract view tag from announcement metadata.

Generate a stealth address from a recipient's meta-address.

Generate a stealth address using a caller-supplied ephemeral private key.

ERC-6538 Registry contract address (Ethereum mainnet).

Scan announcements for payments to our stealth addresses.

Scheme ID for secp256k1 with view tags.

Validate a compressed secp256k1 public key (33 bytes, 02/03 prefix).

Validate a view tag (0-255 integer).

Types

announcement()

@type announcement() :: %{
  scheme_id: pos_integer(),
  stealth_address: String.t(),
  caller: String.t(),
  ephemeral_pub_key: binary(),
  metadata: binary(),
  block_number: non_neg_integer(),
  tx_hash: String.t(),
  log_index: non_neg_integer()
}

meta_address()

@type meta_address() :: %{
  spending_pub_key: binary(),
  viewing_pub_key: binary(),
  chain_id: pos_integer()
}

settlement()

@type settlement() :: %{
  stealth_address: String.t(),
  ephemeral_pub_key: binary(),
  view_tag: 0..255
}

stealth_payment()

@type stealth_payment() :: %{
  announcement: announcement(),
  stealth_priv_key: Raxol.Payments.Secret.t()
}

Functions

announcer_address()

@spec announcer_address() :: String.t()

ERC-5564 Announcer contract address (Ethereum mainnet).

create_metadata(view_tag, extra \\ <<>>)

@spec create_metadata(0..255, binary()) :: binary()

Create metadata bytes with view tag as first byte.

Optionally appends extra data (token address, amount, etc).

decode_meta_address(arg1, chain_id)

@spec decode_meta_address(String.t(), pos_integer()) ::
  {:ok, meta_address()} | {:error, term()}

Decode a meta-address from ERC-6538 st:eth:0x format.

derive_keys(signature)

@spec derive_keys(String.t()) ::
  {:ok, %{spending: {binary(), binary()}, viewing: {binary(), binary()}}}
  | {:error, term()}

Derive stealth keys from an EVM signature (domain-separated).

encode_meta_address(map)

@spec encode_meta_address(meta_address()) :: String.t()

Encode a meta-address in ERC-6538 st:eth:0x format.

extract_view_tag(bin)

@spec extract_view_tag(binary() | String.t()) :: {:ok, 0..255} | {:error, term()}

Extract view tag from announcement metadata.

The view tag is the first byte of the metadata field.

generate(meta)

@spec generate(meta_address()) :: {:ok, settlement()} | {:error, term()}

Generate a stealth address from a recipient's meta-address.

Returns the stealth address, ephemeral public key, and view tag for use in an ERC-5564 announcement.

generate(arg1, ephemeral_priv)

@spec generate(meta_address(), <<_::256>>) :: {:ok, settlement()} | {:error, term()}

Generate a stealth address using a caller-supplied ephemeral private key.

Identical to generate/1 but deterministic: the same meta-address and ephemeral key always yield the same stealth address, ephemeral public key, and view tag. Used for reproducible cross-implementation conformance checks.

registry_address()

@spec registry_address() :: String.t()

ERC-6538 Registry contract address (Ethereum mainnet).

scan(spending_priv_key, viewing_priv_key, announcements)

@spec scan(binary(), binary(), [announcement()]) ::
  {:ok, [stealth_payment()]} | {:error, term()}

Scan announcements for payments to our stealth addresses.

Uses view tag optimization: only performs full ECDH for announcements where the view tag matches (1:256 false positive rate).

Returns a list of discovered payments with their stealth private keys.

scheme_id()

@spec scheme_id() :: pos_integer()

Scheme ID for secp256k1 with view tags.

valid_compressed_pubkey?(arg1)

@spec valid_compressed_pubkey?(binary()) :: boolean()

Validate a compressed secp256k1 public key (33 bytes, 02/03 prefix).

valid_view_tag?(tag)

@spec valid_view_tag?(term()) :: boolean()

Validate a view tag (0-255 integer).