I guess we have something similar to Bitocin HD wallet structure but the resources I found (https://monerodocs.org/public-address/subaddress/ and discussion in https://github.com/monero-project/research-lab/issues/7) seem to describe only a basic sub-address generation.

How is the account/subaddress structure built? For example, how can I compute subaddress Sa(a,i) where a is the account index and i is the subaddress index within the account?


2 Answers 2


how can I compute subaddress Sa(a,i) where a is the account index and i is the subaddress index within the account?

You're right, it is not fully explained in either of those links.

Take a look at get_subaddress_spend_public_keys and get_subaddress_secret_key in the source code.

You'll see in the code that subaddress_index is a struct containing "major" and "minor", where major means the account index and minor means the subaddress index within that account. You'll also see the in second link that the string "SubAddr" is added into the mix.

Bringing it all together:

m = Hs("SubAddr" || a || account_index || subaddress_index_within_account)

Where a is a 32 byte scalar (the private view key), and account_index and subaddress_index_within_account are both 32 bit little endian unsigned integers.

Note that "SubAddr" needs to be a null terminated string, i.e. will be 8 bytes and not 7.

Finally, to get the wallet address for a subaddress, you need to follow the usual base58 procedure (which involves adding an identifier for the type of wallet address and adding a checksum) on the public view and spend keys (C, D), calculated as:

D = B + m*G
C = a*D

Also note that there is a special case, where the first subaddress of the first account is not calculated using the formula above, and instead is simply the ordinary main wallet address (A, B).

Edit: See an example Javascript implementation of a subaddress generator here: https://github.com/knaccc/subaddress-js


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

def calc_subaddress(A:bytes, v: bytes, i: int, a: int):
        A: bytes; public spend key
        v: bytes; private view key
        i: int; subaddress index 
        a: int; account index

    data = b'SubAddr\x00' + v + a.to_bytes(4, byteorder="little") +  i.to_bytes(4, byteorder="little")
    HsG = ed25519.publickey(sc_reduce32(keccak_256(data).digest()))
    Si = ed25519.encodepoint(ed25519.edwards(ed25519.decodepoint(HsG), ed25519.decodepoint(A)))
    Vi = ed25519.encodepoint(ed25519.scalarmult(ed25519.decodepoint(Si), ed25519.decodeint(v)))
    data = bytearray([42]) + Si + Vi 
    checksum = keccak_256(data).digest()[:4]
    return base58.encode((data + checksum).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.