From 797912283c6c4971b29ed56a615529b0b8371725 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 26 Oct 2025 10:35:49 +0000 Subject: [PATCH] addr.py: support extended MMGen IDs --- mmgen/addr.py | 15 +++++++++++-- test/objtest_d/btc_mainnet.py | 1 + test/objtest_d/xmr_mainnet.py | 41 +++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100755 test/objtest_d/xmr_mainnet.py diff --git a/mmgen/addr.py b/mmgen/addr.py index 44ba5da9..26998800 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -23,7 +23,7 @@ addr: MMGen address-related types from collections import namedtuple from .objmethods import HiliteStr, InitErrors, MMGenObject -from .obj import ImmutableAttr, MMGenIdx, get_obj +from .obj import ImmutableAttr, MMGenIdx, Int, get_obj from .seed import SeedID from . import color as color_mod @@ -93,6 +93,10 @@ class MMGenPasswordType(MMGenAddrType): class AddrIdx(MMGenIdx): max_digits = 7 +class MoneroIdx(Int): + max_digits = 5 + min_val = 0 + def is_addr_idx(s): return get_obj(AddrIdx, n=s, silent=True, return_bool=True) @@ -136,7 +140,14 @@ class MMGenID(HiliteStr, InitErrors, MMGenObject): mmtype = proto.dfl_mmtype case _: raise ValueError('not 2 or 3 colon-separated items') - me = str.__new__(cls, f'{sid}:{mmtype}:{idx}') + if '-' in idx: # extended Monero ID + assert proto.coin == 'XMR', 'extended MMGen IDs supported for XMR only' + assert id_str.count(':') == 2, 'mmtype letter required for extended MMGen IDs' + me = str.__new__(cls, id_str) + idx, ext = idx.split('-', 1) + me.acct_num, me.acct_addr_num = [MoneroIdx(e) for e in ext.split('/', 1)] + else: + me = str.__new__(cls, f'{sid}:{mmtype}:{idx}') me.sid = SeedID(sid=sid) me.mmtype = proto.addr_type(mmtype) me.idx = AddrIdx(idx) diff --git a/test/objtest_d/btc_mainnet.py b/test/objtest_d/btc_mainnet.py index 06401918..b0c35a22 100755 --- a/test/objtest_d/btc_mainnet.py +++ b/test/objtest_d/btc_mainnet.py @@ -176,6 +176,7 @@ tests = { {'id_str': 'x:L:3', 'proto': proto}, {'id_str': 'F00BAA12', 'proto': proto}, {'id_str': 'F00BAA12:Z:99', 'proto': proto}, + {'id_str': 'F00BAA12:B:1-0/0', 'proto': proto}, ), 'good': ( {'id_str': 'F00BAA12:99', 'proto': proto, 'ret': 'F00BAA12:L:99'}, diff --git a/test/objtest_d/xmr_mainnet.py b/test/objtest_d/xmr_mainnet.py new file mode 100755 index 00000000..bc35bc97 --- /dev/null +++ b/test/objtest_d/xmr_mainnet.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# +# MMGen Wallet, a terminal-based cryptocurrency wallet +# Copyright (C)2013-2025 The MMGen Project + +""" +test.objtest_d.xmr_mainnet: XMR mainnet test vectors for MMGen data objects +""" + +from mmgen.protocol import init_proto + +from mmgen.addr import MMGenID + +from ..include.common import cfg + +proto = init_proto(cfg, 'xmr', need_amt=True) + +tests = { + 'MMGenID': { + 'arg1': 'id_str', + 'bad': ( + {'id_str': 'F00BAA12', 'proto': proto}, + {'id_str': 'F00BAA12:C:99', 'proto': proto}, + {'id_str': 'F00BAA12:M', 'proto': proto}, + {'id_str': 'F00BAA12:M:', 'proto': proto}, + {'id_str': 'F00BAA12:M:99-FOO', 'proto': proto}, + {'id_str': 'F00BAA12:M:99-1', 'proto': proto}, + {'id_str': 'F00BAA12:M:99-x/y', 'proto': proto}, + {'id_str': 'F00BAA12:M:99-1/y', 'proto': proto}, + {'id_str': 'F00BAA12:M:0-1/1', 'proto': proto}, + {'id_str': 'F00BAA12:M:1-0/-1', 'proto': proto}, + {'id_str': 'F00BAA12:1-0/0', 'proto': proto}, + {'id_str': 'F00BAA12:M:1-111111/3', 'proto': proto}, + ), + 'good': ( + {'id_str': 'F00BAA12:M:1', 'proto': proto}, + {'id_str': 'F00BAA12:M:1-0/0', 'proto': proto}, + {'id_str': 'F00BAA12:M:9999999-99999/99999', 'proto': proto}, + ), + }, +}