From ef61dd104c0d788d472f3fb946ea241ffdabfaf0 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 3 Nov 2026 10:16:03 +0000 Subject: [PATCH] new module: `proto.xmr.subaddress` Given a master address, account idx and address idx, generate a subaddress. --- mmgen/data/version | 2 +- mmgen/proto/xmr/subaddress.py | 50 +++++++++++++++++++++++++++++++++++ test/modtest_d/misc.py | 43 ++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 mmgen/proto/xmr/subaddress.py diff --git a/mmgen/data/version b/mmgen/data/version index 5011aac6..d3eb0db8 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -16.3.0dev24 +16.3.0dev25 diff --git a/mmgen/proto/xmr/subaddress.py b/mmgen/proto/xmr/subaddress.py new file mode 100644 index 00000000..900dffe5 --- /dev/null +++ b/mmgen/proto/xmr/subaddress.py @@ -0,0 +1,50 @@ +# ~/.local/lib/python3.13/site-packages/monero/wallet.py + +from nacl import bindings as nacl + +from ...util2 import get_keccak +from ...addr import MoneroIdx + +from .addrgen import b58enc + +keccak_256 = get_keccak() + +add = nacl.crypto_core_ed25519_add +scalarmult_base = nacl.crypto_scalarmult_ed25519_base_noclamp +scalarmult = nacl.crypto_scalarmult_ed25519_noclamp + +# src/ringct/rctTypes.h: +H = bytes.fromhex('8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94') + +def scalar_reduce(v): + return nacl.crypto_core_ed25519_scalar_reduce(v.ljust(64, b'\0')) + +def get_subaddress(*, proto, master_addr, master_svk, acct_idx, addr_idx): + """ + Generate subaddress for given account index and address index + """ + + if acct_idx == addr_idx == 0: + return master_addr + + acct_idx = MoneroIdx(acct_idx) + addr_idx = MoneroIdx(addr_idx) + + master_psk = proto.decode_addr(master_addr).bytes[:32] # => psk + pvk + + hash_in = ( + b'SubAddr\0' + + master_svk + + acct_idx.to_bytes(4, 'little') + + addr_idx.to_bytes(4, 'little')) + + m = keccak_256(hash_in).digest() + + # psk = master_psk + m * B + psk = add(master_psk, scalarmult_base(scalar_reduce(m))) + + # pvk = master_svk * psk + pvk = scalarmult(master_svk, psk) + + data = proto.addr_fmt_to_ver_bytes['monero_sub'] + psk + pvk + return b58enc(data + keccak_256(data).digest()[:4]) diff --git a/test/modtest_d/misc.py b/test/modtest_d/misc.py index efe77a5e..2307df49 100644 --- a/test/modtest_d/misc.py +++ b/test/modtest_d/misc.py @@ -60,6 +60,49 @@ class unit_tests: return True + def xmr_subaddress(self, name, ut, desc='generating XMR subaddresses'): + vectors = [ + ['45mLzbVEU5jNEN5qrfvbWMEbBK3LHDdzvgtSxiBgoUs7WYyKatHdzzEM612XpKv3xR52JZq5V7GURSbp5EqpqvQ7UCPrmwU'], + ['82x63Tnxi8xTLkU1WvYm3MTDcFZek6AyN1W6TppMtTnUVuK11GRGtXGJLQKCB5cGU7hukQuoKQsMm1hvnW3mrGxY8JQqVWD'], + [ + '8AEK8kKkU5ddKr8PL32RgoUW97fDeMFrJNJ3Fh15RWfFUqjodYcd6oAjCdrLrYT3io22vFUkB3FAP7r1hgY39bzEKtHGChq', + '86JzhV6CUy1bFrLoLJ23Qmgmphz5bsMVubLosp87RRstNxCsderXWvRhEe3s1PWdjv1j6mG9t4hSSQ1JXu1bAZRDSvadW9m', + '88jM6aonkzqP3EcHYCQnMSAHrvv3ZBZo8gC1sVEc4nUgBZsPww3WEx6jfsdLxbWBLE3zVDvp1w6CWAPeybrSF1Cx3BH3EYC', + ] + ] + from mmgen.proto.xmr.subaddress import get_subaddress + from mmgen.addrlist import KeyAddrList + from mmgen.cfg import Config + from mmgen.protocol import init_proto + from mmgen.seed import Seed + with open('test/ref/FE3C6545.hex') as fh: + seed_hex = fh.read().strip() + cfg = Config() + proto = init_proto(cfg, 'xmr', network='regtest') + kal = KeyAddrList( + cfg = cfg, + proto = proto, + seed = Seed(cfg, seed_bin=bytes.fromhex(seed_hex)), + addr_idxs = '4', + mmtype = 'M', + skip_chksum = True) + data = kal.data[0] + for acct_idx, addr_idx in ( + (0, 0), + (1, 0), + (2, 0), + (2, 1), + (2, 2)): + addr = get_subaddress( + proto = proto, + master_addr = data.addr, + master_svk = bytes.fromhex(data.viewkey), + acct_idx = acct_idx, + addr_idx = addr_idx) + vmsg(f'{acct_idx=} {addr_idx=} {addr=}') + assert addr == vectors[acct_idx][addr_idx] + return True + def xmrwallet_uarg_info(self, name, ut, desc='dict xmrwallet.xmrwallet_uarg_info'): # WIP from mmgen.xmrwallet import uarg_info vs = namedtuple('vector_data', ['text', 'groups'])