Is it possible from Section 4. Address Serialization cns007.txt and Section 3. Variable-Length Encoding of Integers cns003.txt to compute a Monero Stealth Address from the following?

1) Private Spend Key: 198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601

2) Private View Key is computed from a 256-bit Keccak hash:

% keccak -256 198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601 889DA12A88D36BCE0966AB1A79125779DD1F2FC6F1145DE131FD52A5B468796D

3) Edwards 25519 Public Key corresponding to the Private Spend Key:

% ed25519 198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601 c2b898f1c5136fe21aeff926655563a0868e23647798e3f33691e94ef7de0b64

4) Edwards 25519 Public Key corrresponding to the Private View Key:

% ed25519 889DA12A88D36BCE0966AB1A79125779DD1F2FC6F1145DE131FD52A5B468796D 0a2ada1dc29cb84280376970a3c703d5a3a148f83db5aaed44a1fa33de59bdde

If so, how can the information above be used to construct a Monero Stealth address?

  • I'm presuming those aren't your actual private keys. Commented Aug 30, 2016 at 13:02
  • What does this have to do with cold storage? Commented Aug 30, 2016 at 13:03
  • @PyRulez - Please just shoot me if I'm that stupid to use private keys from a production Monero wallet! A stealth address can be created from a permanently offline computer. Then funds may be swept to a dynamically calculated recipient address from an online computer using the stealth address assuming a permanent Diffie-Hellman key exchange gets posted on the Monero blockchain. Sweeping actions are required put funds into cold storage.
    – skaht
    Commented Aug 30, 2016 at 16:11
  • Why not a regular address, which simplewallet already supports? Commented Aug 30, 2016 at 18:17
  • Okay. I would just like to note that although stealth addresses could be quite useful for transacting with other people, they don't have much to do with cold storage (stealth addresses are involved in all transactions, but they are not specific to cold storage at all). Commented Aug 30, 2016 at 19:31

3 Answers 3


In Monero, coins are received to a unique, one-time stealth address. The formula for stealth addresses is as follows:

P = Hs(rA)G + B


  • P -- the final stealth address (one-time output key, the destination where funds will actually be sent);
  • Hs* -- a hashing algorithm that returns a scalar (i.e., the hash output is interpreted as an integer and reduced modulo l);
  • r -- the new random scalar Alice chose for this transaction;
  • A -- Bob's public view key;
  • G -- the standard Ed25519 base point;
  • B -- Bob's public spend key.

You provided us with the private view key (a) and private spend key (b), and the public view key (A) and public spend key (B). In addition, we have the base point (G). It should be noted that you forgot to reduce modulo l your private view key. Your correct private view key (a) is:


This gives the correct public view key (A), which is:


I don't know what you used to get to your public spend key (B), but it returns differently for me, namely:


For what it's worth, the curve constants from the CryptoNote whitepaper can be seen here. Note that for a dual-key stealth address to be created, the sender (Alice) does ECDH (Elliptic curve Diffie–Hellman) with her randomly-chosen r (private tx key) and the receiver's (Bob) public view key, A. This is point D (D = rA), which is a shared, secret point known only to Alice and Bob (D = D'). Thus, no one other than Alice or Bob is able to compute D. Second, Alice uses D to generate a new scalar, f, with f = Hs(D). Third, Alice computes F = fG. Lastly, Alice computes P = F + B (Bob's public spend key). Note that F is equal to Hs(rA)G in the formula above.

Back to your question. You first have to generate a random r, which can be done as follows in the Chrome console using Javascript on this page. Since you already generated a random private spend key, we only have to generate a random r. This can be done by using random_scalar();. Using this I get:


The public tx key (R) is then:


For what it is worth, the public address is:


Thus, to summarize, our "start" variables are:

  1. var b = "198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601"; | Your private spend key

  2. var a = hash_to_scalar(b); | Your private view key (deterministic derivation) | a = "faa5defce980fdbd03b9dd4841371dfcdc1f2fc6f1145de131fd52a5b468790d"

  3. var B = sec_key_to_pub(b); | Your public spend key, this function multiplies base G by its input | B = "b66991d7d7c68513533d0560f820d75adfb0911487ba62274b759f7b3ccd4a90"

  4. var A = sec_key_to_pub(a); | Your public view key | A = "3c450f27cd6849d9130addb2c566d910c5ef9bf4cecaed547004496fda52a4ff"

  5. pubkeys_to_string(B,A); | This will return your public address | Public Address = "48Y3H2eSZ6C4EUjY1B5viSGCbCLPcmMiy7aD69yqUsaHR8GLE3rvSwrdJtpZYG1peC3oipCqfUvCcF89i86kuEjVVr5GCdj"

  6. var r = "9f558def5f918481f3c130d4cbea908f0cef9aedc5f5b259bb18b3b9ea4e5b0e"; | private tx key

  7. var R = sec_key_to_pub(r); | Public tx key | R = "11f63287b184708fd40618154a19afa35e2366ef22417223e8feac1639b68ace"

Now let's create a stealth address! This is done as follows:

  1. var D = generate_key_derivation(A, r); | ECDH, rA | D = "b2896554aa33603868b4c5d106f4873620aabb96d22bc236192f1bcfa38451bb"

  2. var f = derivation_to_scalar(D, 0); | 0 is the output index. The standard method combines these last few steps into one, but for clarity they are split here | f = "236b65c107cf838db70d41f5e795e526e2043f471d2b2daefa24fb4b22f0340c"

  3. var F = sec_key_to_pub(f); | F = "2dd26c9941f5fe2aeeab30d2ebc41dd21241cb4d4327c27588bde076a0e893f4"

  4. var P = ge_add(F,B); | "ge" means group element. This function adds two points together. Note that F is Hs(rA)G | P = "b696760c160efa648d681a7a65cdee3c5f0c23d3866c08c90473021d7892f1a3"

P (b696760c160efa648d681a7a65cdee3c5f0c23d3866c08c90473021d7892f1a3) is a valid stealth address. An image of the aforementioned steps performed in the browser console on https://xmr.llcoins.net can be seen here.

Note, however, that in Monero the stealth addresses are created by the sender and not by the receiver. Thus, those last four steps are performed by the sender (Alice) in Monero.




  • 2
    wowzers debruyne your getting well versed in your monero knowledge!!
    – Ginger Ale
    Commented Sep 2, 2016 at 0:14
  • @dEBRUYNE - Not interested in the mathematics behind the development of the actual address for particular transaction from the sender's (Alice) ephemeral wallet number, r. The stealth address I'm interested in computing is the one solely computed by the recipient. The addresstests provided by you was most helpful. Your assertion about not applying the bitmonero/src/crypto/crypto-ops.c sc_reduce32() function was spot on. Still scratching my head for how Monero computes public ed25519 keys. My libsodium & ed25519-donna results still don't match.
    – skaht
    Commented Sep 3, 2016 at 4:08
  • My ed25519 public key generators using libsodium and ed25519-donna match TOR test vectors for: ED25519_SECRET_KEYS[], ED25519_PUBLIC_KEYS[], and ED25519_CURVE25519_PUBLIC_KEYS[]. Trying to figure out if other modulo normalization may be happening.
    – skaht
    Commented Sep 3, 2016 at 4:18
  • 1
    Just for the records: Not only the last four steps but also step 6. and 7. are performed by the sender, where 6. is the sender's one-time private transaction key used for this transaction only. Commented Jan 27, 2018 at 6:36

Here is a functional example for deriving a Monero stealth address, developer mechanics not cryptographic theory. Results below duplicate functionality that is part of Crypto Note Test Address.

It is worth noting custom bytes_to_words, sc_reduce32, and secret_key_to_public_key executables (coded in C or C++) below were named after Monero's functions that yielded output results. C++ coding insights came from main.cpp. The bx command line is bitcoin-explorer, see bx wiki.

Still investigating if Monero's secret_key_to_public_key() functionality is true Ed25519 technology or a derived Ed25519 technology. Results are different from TOR test vectors results that custom executables utilizing libsodium and ed25519-donna yield, but Monero C/C++ code results match that from Crypto Note Test Address.

256-bit hexadecimal-encoded seed is assumed to be:


Electrum mnemonic words corresponding to seed:

% ./bytes_to_words 198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601 wallets drinks insult popular fall textbook scoop apology unsafe fifteen cuffs pimple roster nerves pixels upstairs academy sprig eclipse leopard peeled faxed gutter happens roster

Private spend key calculation:

% ./sc_reduce32 198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601 198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601

Private view key calculation:

% ./keccak 198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601 889DA12A88D36BCE0966AB1A79125779DD1F2FC6F1145DE131FD52A5B468796D

% ./sc_reduce32 889DA12A88D36BCE0966AB1A79125779DD1F2FC6F1145DE131FD52A5B468796D faa5defce980fdbd03b9dd4841371dfcdc1f2fc6f1145de131fd52a5b468790d

Public spend key calculation:

% ./secret_key_to_public_key 198584347013dd91832be3d82529437db7cc8e1850e559cdd3872b29ca819601 b66991d7d7c68513533d0560f820d75adfb0911487ba62274b759f7b3ccd4a90

Public view key calculation:

% ./secret_key_to_public_key faa5defce980fdbd03b9dd4841371dfcdc1f2fc6f1145de131fd52a5b468790d 3c450f27cd6849d9130addb2c566d910c5ef9bf4cecaed547004496fda52a4ff

Stealth address checksum calculation:

% ./keccak 12b66991d7d7c68513533d0560f820d75adfb0911487ba62274b759f7b3ccd4a903c450f27cd6849d9130addb2c566d910c5ef9bf4cecaed547004496fda52a4ff ADD568169DBF2C6D3F595EE8610A189955BECD1EDF150627CBF2F2C49B0AEA71

Stealth address in hexadecimal format is the concatenation of prefix + public_spend_key + view_public_key + keccak_checksum_postfix:


Computing stealth address in base58 format:

Note the nine (9) fields of the stealth address in hexadecimal format.


1) % bx base58-encode 12b66991d7d7c685


2)% bx base58-encode 13533d0560f820d7


3)% bx base58-encode 5adfb0911487ba62


4)% bx base58-encode 274b759f7b3ccd4a


5)% bx base58-encode 903c450f27cd6849


6)% bx base58-encode d9130addb2c566d9


7)% bx base58-encode 10c5ef9bf4cecaed


8)% bx base58-encode 547004496fda52a4


9)% bx base58-encode ffADD56816


Concatenating the nine base-58 encoded data yields the base58-encoded stealth address:



I created a small repository with some basic functions implemented in simple python for educational purpose.

Checkout test_monero_crypto.py for data of real transactions.

def calc_stealth_address(r: bytes, A: bytes , B: bytes, i: int)-> str:
    """ Calculates stealth address in the form P = Hs(rA)G + B.
    Be careful, index and respent output (here you have to use your own public keys) have to match
        r: bytes ; Transaction secret key (ephemeral random)
        A: bytes; pyblic view key
        B: bytes; public spend key
        i: int; output index 
        stealth address; subaddresses are prefixed with '8', addresses are prefixec with '4'
    rA  = ed25519.encodepoint(ed25519.scalarmult(ed25519.decodepoint(A), ed25519.decodeint(r)))
    rA =  ed25519.encodepoint(ed25519.scalarmult(ed25519.decodepoint(rA), 8 )) # There is a mathematical reason for this...
    rA += bytes([i])

    Hs = sc_reduce32(keccak_256(rA).digest()) # Hs stands for Hash to scalar, interpret result as scalar
    HsG = ed25519.publickey(Hs)
    return ed25519.encodepoint(ed25519.edwards(ed25519.decodepoint(HsG), ed25519.decodepoint(B))).hex()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.