new module: proto.xmr.subaddress

Given a master address, account idx and address idx, generate a subaddress.
This commit is contained in:
The MMGen Project 2026-11-03 10:16:03 +00:00
commit ef61dd104c
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 94 additions and 1 deletions

View file

@ -1 +1 @@
16.3.0dev24
16.3.0dev25

View file

@ -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])

View file

@ -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'])