# 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) which are derived from 2 master keypairs (a, A) and (b, B), and which can be used repeatedly to generate new outputs that are unlinkable by those without knowledge of the master keys. ### Notation * G, H, and J are curve generator points * Uppercase letter in code font (A, Ai, etc.) represents a secp256k1 public key * Lowercase letter in code font (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 of `x`, `y`, and `z` * HASH32(...) outputs are interpreted as scalars modulo the curve order where scalar arithmetic is used. * COMMIT(v,x) = v⋅H + x⋅G represents the pedersen commitment generated with value v and blinding factor x. * BLIND_SWITCH(v,r) = r + HASH32(r⋅G + v⋅H || r⋅J) represents the blinding factor for a value v and randomness r. * x[y,z] suffix represents the byte range from y to z (inclusive, zero-based indices) of the input x ### Address Generation Unique stealth addresses should be generated deterministically from a master scan keypair (a, A) and master spend keypair (b, B) using the following process: 1. Choose the lowest unused address index i 2. Calculate one-time spend keypair (bi, Bi) as:
bi = b + HASH32('A'||i||a)
Bi = bi⋅G 3. Calculate one-time scan keypair (ai, Ai) as:
ai = a⋅bi
Ai = ai⋅G ### Outputs Outputs consist of the following data: * Co - The pedersen commitment to the value. * Ks - The sender's public key. * Ko - The receiver's one-time public key. * Output message, m, consisting of: * Feature bits * Ke - The key exchange public key. * t - The view tag. * v' - The masked value. * n' - The masked nonce. * Optional extra data (if enabled by feature bits) * A signature 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): 1. Generate a random sender keypair (ks, Ks). 2. Derive the nonce n = HASH32('N'||ks)[0,15] 3. Derive the sending key s = HASH32('S'||Ai||Bi||v||n) 4. Calculate P = s⋅Ai. Derive the view tag and shared secret as:
t = HASH32('T'||P)[0]
e = HASH32('D'||P) 5. Calculate the receiver's one-time public key Ko = HASH32('O'||e)⋅Bi 6. Calculate the key exchange pubkey Ke = s⋅Bi 7. Encrypt the value using v' = v ⊕ LE64(HASH32('Y'||e)) 8. Encrypt the nonce using n' = n ⊕ HASH32('X'||e)[0,15] 9. Calculate the blinding factor x = BLIND_SWITCH(v,HASH32('B'||e)) 10. Calculate the commitment Co = COMMIT(v,x) 11. Generate the rangeproof π for Co, committing also to the output message m. TODO: Link to 'm' definition and rangeproof spec 12. Sign the message HASH32(Co||Ks||Ko||HASH32(m)||HASH32(π)) using the sender key ks. ### Output Identification To check if an output belongs to a wallet: 1. Calculate the ECDHE point P = a⋅Ke. 2. If HASH32('T'||P)[0] does not match the view tag t, the output does not belong to the wallet. 3. Derive the shared secret e = HASH32('D'||P). 4. Recover the one-time spend pubkey: Bi = Ko / HASH32('O'||e). 5. 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. 6. Decrypt the value v = v' ⊕ LE64(HASH32('Y'||e)). 7. Decrypt the nonce n = n' ⊕ HASH32('X'||e)[0,15]. 8. Verify that COMMIT(v, BLIND_SWITCH(v,HASH32('B'||e))) =? Co. 9. Calculate the send key s = HASH32('S'||Ai||Bi||v||n). 10. Verify that Ke =? s⋅Bi. If all verifications succeed, the output belongs to the wallet, and is safe to use. #### Spend Key Recovery The spend key can be recovered by ko = bi⋅HASH32('O'||e). Derivation proof:
ko = bi⋅HASH32('O'||e)
Ko = HASH32('O'||e)⋅Bi
      = HASH32('O'||e)⋅(bi⋅G)
      = (bi⋅HASH32('O'||e))⋅G
      = ko⋅G
### Implementation Notes * Litecoin Core MWEB uses tagged BLAKE3 hashes with one-byte domains ('A', 'B', 'D', 'N', 'O', 'S', 'T', 'X', 'Y') and a multiplicative output-key tweak (Ko = Bi⋅HASH32('O'||e)). * This differs from some DKSAP writeups that use additive output-key tweaks or define the view tag directly as the first byte of e. * Wallets must keep a map Bi->i of all used spend pubkeys and the next several unused ones. * To be consistent with Litecoin Core, we recommend using the following derivation paths: * Master scan key (a, A): m/0'/100'/0' * Master spend key (b, B): m/0'/100'/1'