4.7 KiB
Stealth Addresses
Transacting on the MWEB happens via stealth addresses. Stealth addresses are supported using the Dual-Key Stealth Address Protocol (DKSAP). These addresses consist of 2 secp256k1 public keys (Ai, Bi) where Ai is the scan pubkey used for identifying outputs, and Bi is the spend pubkey.
Notation
- G = Curve generator point
- Bold uppercase letter (A, Ai, etc.) represents a secp256k1 public key
- Bold lowercase letter (a, ai, etc.) represents a scalar
- A letter in single quotes ('K') represents the ASCII value of the character literal
- HASH32(x | y | z) represents the standard 32-byte
BLAKE3
hash of the conjoined serializations ofx
,y
, andz
- HASH64(m) represents the
BLAKE3
hash ofm
using a 64-byte output digest - SWITCH(v, r) represents the pedersen commitment generated using a blind switch with value v and key r.
Address Generation
Unique stealth addresses should be generated deterministically from a wallet's seed using the following formula (using G = generator point):
- Generate master scan keypair (a, A) using HD keychain path
m/1/0/100'
- Generate master spend keypair (b, B) using HD keychain path
m/1/0/101'
- Choose the lowest unused address index i
- Calculate one-time spend keypair (bi, Bi) as:
bi = b + HASH32(A | i | a)
Bi = bi*G where G refers to the curve generator point - Calculate one-time scan keypair (ai, Ai) as:
ai = a*bi
Ai = ai*G where G refers to the curve generator point
Outputs
Outputs consist of the following data:
- Co - The pedersen commitment to the value.
- Output message, m, consisting of:
- Ko - The receiver's one-time public key.
- Ke - The key exchange public key.
- Ks - The sender's public key.
- t - The view tag. This is the first byte of the shared secret.
- v' - The masked value.
- n' - The masked nonce.
- A signature of the m using the sender's key ks.
- A rangeproof of Co that also commits to the m.
Output Construction
To create an output for value v to a receiver's stealth address (Ai,Bi):
- Generate a random sender keypair (ks, Ks). # TODO: Can we generate this deterministically?
- Derive the nonce n as the first 16 bytes of HASH32('N'|ks)
- Derive the sending key s = HASH32('S'|Ai|Bi|v|n)
- Derive the shared secret e = HASH32('D'|s*Ai). The view tag t is the first byte of e.
- Calculate the receiver's one-time public key Ko = Bi + HASH32('O'|e)*G
- Calculate the key exchange pubkey Ke = s*Bi
- Derive the 64-byte mask m = HASH64(e)
- Encrypt the value using v' = v ⊕ m[32,39]
- Encrypt the nonce using n' = n ⊕ m[40,55]
- Calculate the commitment Co = SWITCH(v, m[0,31]).
- Generate the rangeproof for Co, committing also to m.
- Sign m using the sender key ks.
Output Identification
NOTE: the wallet must keep a map Bi->i of all used spend pubkeys and the next few unused ones.
To check if an output belongs to a wallet:
- Calculate the ECDHE shared secret e = HASH32('D'|a*Ke)
- If the first byte of e does not match the view tag t, the output does not belong to the wallet.
- Calculate the one-time spend pubkey: Bi = Ko - HASH('O'|e)*G
- Lookup the index i that generates Bi from the wallet's map Bi->i. If not found, the output does not belong to the wallet.
- Derive the 64-byte mask m = HASH64(e)
- Decrypt the value v = v' ⊕ m[32,39] where m[32,39] refers to bytes 32-39 (0-based big-endian) of the 64 byte mask m.
- Verify that SWITCH(v,m[0-31]) ?= Co
- Decrypt the nonce n = n' ⊕ m[40,55].
- Calculate the send key s = HASH32('S'|Ai|Bi|v|n)
- Verify that Ke ?= s*Bi.
If all verifications succeed, the output belongs to the wallet, and is safe to use.
The spend key can be recovered by ko = HASH32('O'|e) + ai.