git / moonbite-core / doc/mweb/stealth-addresses.md · raw

# Stealth AddressesTransacting 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 <code>(A<sub>i</sub>, B<sub>i</sub>)</code> which are derived from 2 master keypairs <code>(a, A)</code> and <code>(b, B)</code>,and which can be used repeatedly to generate new outputs that are unlinkable by those without knowledge of the master keys.### Notation* <code>G</code>, <code>H</code>, and <code>J</code> are curve generator points* Uppercase letter in code font (<code>A</code>, <code>A<sub>i</sub></code>, etc.) represents a secp256k1 public key* Lowercase letter in code font (<code>a</code>, <code>a<sub>i</sub></code>, etc.) represents a scalar* A letter in single quotes (<code>'K'</code>) represents the ASCII value of the character literal* <code>HASH32(x||y||z)</code> represents the standard 32-byte `BLAKE3` hash of the conjoined serializations of `x`, `y`, and `z`    * <code>HASH32(...)</code> outputs are interpreted as scalars modulo the curve order where scalar arithmetic is used.* <code>COMMIT(v,x) = v⋅H + x⋅G</code> represents the pedersen commitment generated with value <code>v</code> and blinding factor <code>x</code>.* <code>BLIND_SWITCH(v,r) = r + HASH32(r⋅G + v⋅H || r⋅J)</code> represents the blinding factor for a value <code>v</code> and randomness <code>r</code>.* <code>x[y,z]</code> suffix represents the byte range from <code>y</code> to <code>z</code> (inclusive, zero-based indices) of the input <code>x</code>### Address GenerationUnique stealth addresses should be generated deterministically from a master scan keypair <code>(a, A)</code> and master spend keypair <code>(b, B)</code> using the following process:1. Choose the lowest unused address index <code>i</code>2. Calculate one-time spend keypair <code>(b<sub>i</sub>, B<sub>i</sub>)</code> as:<br/>    <code>b<sub>i</sub> = b + HASH32('A'||i||a)</code><br/>    <code>B<sub>i</sub> = b<sub>i</sub>⋅G</code>3. Calculate one-time scan keypair <code>(a<sub>i</sub>, A<sub>i</sub>)</code> as:<br/>    <code>a<sub>i</sub> = a⋅b<sub>i</sub></code><br/>    <code>A<sub>i</sub> = a<sub>i</sub>⋅G</code>### OutputsOutputs consist of the following data:* <code>C<sub>o</sub></code> - The pedersen commitment to the value.* <code>K<sub>s</sub></code> - The sender's public key.* <code>K<sub>o</sub></code> - The receiver's one-time public key.* Output message, <code>m</code>, consisting of:  * Feature bits  * <code>K<sub>e</sub></code> - The key exchange public key.  * <code>t</code> - The view tag.  * <code>v'</code> - The masked value.  * <code>n'</code> - The masked nonce.  * Optional extra data (if enabled by feature bits)* A signature using the sender's key <code>k<sub>s</sub></code>.* A rangeproof of <code>C<sub>o</sub></code> that also commits to the <code>m</code>.### Output ConstructionTo create an output for value <code>v</code> to a receiver's stealth address <code>(A<sub>i</sub>,B<sub>i</sub>)</code>:1. Generate a random sender keypair <code>(k<sub>s</sub>, K<sub>s</sub>)</code>.2. Derive the nonce <code>n = HASH32('N'||k<sub>s</sub>)[0,15]</code>3. Derive the sending key <code>s = HASH32('S'||A<sub>i</sub>||B<sub>i</sub>||v||n)</code>4. Calculate <code>P = s⋅A<sub>i</sub></code>. Derive the view tag and shared secret as:<br/>   <code>t = HASH32('T'||P)[0]</code><br/>   <code>e = HASH32('D'||P)</code>5. Calculate the receiver's one-time public key <code>K<sub>o</sub> = HASH32('O'||e)⋅B<sub>i</sub></code>6. Calculate the key exchange pubkey <code>K<sub>e</sub> = s⋅B<sub>i</sub></code>7. Encrypt the value using <code>v' = v &oplus; LE64(HASH32('Y'||e))</code>8. Encrypt the nonce using <code>n' = n &oplus; HASH32('X'||e)[0,15]</code>9. Calculate the blinding factor <code>x = BLIND_SWITCH(v,HASH32('B'||e))</code>10. Calculate the commitment <code>C<sub>o</sub> = COMMIT(v,x)</code>11. Generate the rangeproof <code>&pi;</code> for <code>C<sub>o</sub></code>, committing also to the output message <code>m</code>. TODO: Link to 'm' definition and rangeproof spec12. Sign the message <code>HASH32(C<sub>o</sub>||K<sub>s</sub>||K<sub>o</sub>||HASH32(m)||HASH32(&pi;))</code> using the sender key <code>k<sub>s</sub></code>.### Output IdentificationTo check if an output belongs to a wallet:1. Calculate the ECDHE point <code>P = a⋅K<sub>e</sub></code>.2. If <code>HASH32('T'||P)[0]</code> does not match the view tag <code>t</code>, the output does not belong to the wallet.3. Derive the shared secret <code>e = HASH32('D'||P)</code>.4. Recover the one-time spend pubkey: <code>B<sub>i</sub> = K<sub>o</sub> / HASH32('O'||e)</code>.5. Lookup the index <code>i</code> that generates <code>B<sub>i</sub></code> from the wallet's map <code>B<sub>i</sub>->i</code>. If not found, the output does not belong to the wallet.6. Decrypt the value <code>v = v' &oplus; LE64(HASH32('Y'||e))</code>.7. Decrypt the nonce <code>n = n' &oplus; HASH32('X'||e)[0,15]</code>.8. Verify that <code>COMMIT(v, BLIND_SWITCH(v,HASH32('B'||e))) =? C<sub>o</sub></code>.9. Calculate the send key <code>s = HASH32('S'||A<sub>i</sub>||B<sub>i</sub>||v||n)</code>.10. Verify that <code>K<sub>e</sub> =? s⋅B<sub>i</sub></code>.If all verifications succeed, the output belongs to the wallet, and is safe to use.#### Spend Key RecoveryThe spend key can be recovered by <code>k<sub>o</sub> = b<sub>i</sub>⋅HASH32('O'||e)</code>.Derivation proof:<br/><pre><code>k<sub>o</sub> = b<sub>i</sub>⋅HASH32('O'||e)</code>K<sub>o</sub> = HASH32('O'||e)⋅B<sub>i</sub>      = HASH32('O'||e)⋅(b<sub>i</sub>⋅G)      = (b<sub>i</sub>⋅HASH32('O'||e))⋅G      = k<sub>o</sub>⋅G</pre>### Implementation Notes* Litecoin Core MWEB uses tagged BLAKE3 hashes with one-byte domains (<code>'A'</code>, <code>'B'</code>, <code>'D'</code>, <code>'N'</code>, <code>'O'</code>, <code>'S'</code>, <code>'T'</code>, <code>'X'</code>, <code>'Y'</code>) and a multiplicative output-key tweak (<code>K<sub>o</sub> = B<sub>i</sub>⋅HASH32('O'||e)</code>).  * This differs from some DKSAP writeups that use additive output-key tweaks or define the view tag directly as the first byte of <code>e</code>.* Wallets must keep a map <code>B<sub>i</sub>->i</code> 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 <code>(a, A)</code>: <code>m/0'/100'/0'</code>  * Master spend key <code>(b, B)</code>: <code>m/0'/100'/1'</code>