From 3c0c9613854cb278c8e880303c78c45189247d5b Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 29 Sep 2026 09:23:30 +0000 Subject: [PATCH] tool: new `fstr` class --- mmgen/main_tool.py | 7 ++++--- mmgen/tool/coin.py | 38 +++++++++++++++++++------------------- mmgen/tool/common.py | 3 +++ mmgen/tool/help.py | 8 ++++---- mmgen/tool/mnemonic.py | 6 +++--- mmgen/tool/util.py | 28 ++++++++++++++-------------- pyproject.toml | 1 - 7 files changed, 47 insertions(+), 44 deletions(-) diff --git a/mmgen/main_tool.py b/mmgen/main_tool.py index ccc3df17..178245bb 100755 --- a/mmgen/main_tool.py +++ b/mmgen/main_tool.py @@ -24,6 +24,7 @@ mmgen-tool: Perform various MMGen- and cryptocoin-related operations. import sys, os, importlib from .cfg import gc, Config from .util import msg, Msg, die, capfirst, suf, async_run, isAsync +from .tool.common import fstr opts_data = { 'filter_codes': ['-'], @@ -211,17 +212,17 @@ def create_call_sig(cmd, cls, *, as_string=False): for i, a in enumerate(args[nargs:])) if as_string: - get_type_from_ann = lambda x: 'str or STDIN' if ann[x] == 'sstr' else ann[x].__name__ + get_type_from_ann = lambda x: 'str or STDIN' if ann[x] == fstr else ann[x].__name__ return ' '.join( [f'{a} [{get_type_from_ann(a)}]' for a in args[:nargs]] + [f'{a} [{dfl_types[n].__name__}={dfls[n]!r}]' for n, a in enumerate(args[nargs:])]) else: - get_type_from_ann = lambda x: 'str' if ann[x] == 'sstr' else ann[x].__name__ + get_type_from_ann = lambda x: 'str' if ann[x] == fstr else ann[x].__name__ return ( [(a, get_type_from_ann(a)) for a in args[:nargs]], # c_args {a: dfls[n] for n, a in enumerate(args[nargs:])}, # c_kwargs {a: dfl_types[n] for n, a in enumerate(args[nargs:])}, # c_kwargs_types - ('STDIN_OK' if nargs and ann[args[0]] == 'sstr' else flag), # flag + ('STDIN_OK' if nargs and ann[args[0]] == fstr else flag), # flag ann) # ann def process_args(cmd, cmd_args, cls): diff --git a/mmgen/tool/coin.py b/mmgen/tool/coin.py index 2164c1dc..a28c1c70 100755 --- a/mmgen/tool/coin.py +++ b/mmgen/tool/coin.py @@ -23,7 +23,7 @@ tool.coin: Cryptocoin routines for the 'mmgen-tool' utility from collections import namedtuple generator_data = namedtuple('generator_data', ['kg', 'ag']) -from .common import tool_cmd_base +from .common import tool_cmd_base, fstr from ..key import PrivKey from ..addr import CoinAddr, MMGenAddrType @@ -57,7 +57,7 @@ class tool_cmd(tool_cmd_base): pubkey_type = self.mmtype.pubkey_type, compressed = self.mmtype.compressed).wif - def privhex2pair(self, privhex: 'sstr'): + def privhex2pair(self, privhex: fstr): "generate a wifkey/address pair from the provided hexadecimal key" gd = self._init_generators() privkey = PrivKey( @@ -74,13 +74,13 @@ class tool_cmd(tool_cmd_base): from ..crypto import Crypto return self.privhex2pair(Crypto(self.cfg).get_random(32).hex()) - def wif2hex(self, wifkey: 'sstr'): + def wif2hex(self, wifkey: fstr): "convert a private key from WIF to hexadecimal format" return PrivKey( self.proto, wif = wifkey).hex() - def hex2wif(self, privhex: 'sstr'): + def hex2wif(self, privhex: fstr): "convert a private key from hexadecimal to WIF format" return PrivKey( self.proto, @@ -88,7 +88,7 @@ class tool_cmd(tool_cmd_base): pubkey_type = self.mmtype.pubkey_type, compressed = self.mmtype.compressed).wif - def wif2addr(self, wifkey: 'sstr'): + def wif2addr(self, wifkey: fstr): "generate a coin address from a key in WIF format" gd = self._init_generators() privkey = PrivKey( @@ -96,7 +96,7 @@ class tool_cmd(tool_cmd_base): wif = wifkey) return gd.ag.to_addr(gd.kg.gen_data(privkey)) - def wif2redeem_script(self, wifkey: 'sstr'): # new + def wif2redeem_script(self, wifkey: fstr): # new "convert a WIF private key to a Segwit P2SH-P2WPKH redeem script" assert self.mmtype.name == 'segwit', 'This command is meaningful only for --type=segwit' gd = self._init_generators() @@ -105,7 +105,7 @@ class tool_cmd(tool_cmd_base): wif = wifkey) return gd.ag.to_segwit_redeem_script(gd.kg.gen_data(privkey)) - def wif2segwit_pair(self, wifkey: 'sstr'): + def wif2segwit_pair(self, wifkey: fstr): "generate a Segwit P2SH-P2WPKH redeem script and address from a WIF private key" assert self.mmtype.name == 'segwit', 'This command is meaningful only for --type=segwit' gd = self._init_generators() @@ -116,7 +116,7 @@ class tool_cmd(tool_cmd_base): gd.ag.to_segwit_redeem_script(data), gd.ag.to_addr(data)) - def _privhex2out(self, privhex: 'sstr', *, output_pubhex=False): + def _privhex2out(self, privhex: fstr, *, output_pubhex=False): gd = self._init_generators() pk = PrivKey( self.proto, @@ -126,15 +126,15 @@ class tool_cmd(tool_cmd_base): data = gd.kg.gen_data(pk) return data.pubkey.hex() if output_pubhex else gd.ag.to_addr(data) - def privhex2addr(self, privhex: 'sstr'): + def privhex2addr(self, privhex: fstr): "generate a coin address from raw hexadecimal private key data" return self._privhex2out(privhex) - def privhex2pubhex(self, privhex: 'sstr'): # new + def privhex2pubhex(self, privhex: fstr): # new "generate a hexadecimal public key from raw hexadecimal private key data" return self._privhex2out(privhex, output_pubhex=True) - def pubhex2addr(self, pubkeyhex: 'sstr'): + def pubhex2addr(self, pubkeyhex: fstr): "convert a hexadecimal pubkey to an address" if self.proto.base_proto == 'Ethereum' and len(pubkeyhex) == 128: # support raw ETH pubkeys pubkeyhex = '04' + pubkeyhex @@ -147,13 +147,13 @@ class tool_cmd(tool_cmd_base): compressed = self.mmtype.compressed, )) - def pubhex2redeem_script(self, pubkeyhex: 'sstr'): # new + def pubhex2redeem_script(self, pubkeyhex: fstr): # new "convert a hexadecimal pubkey to a Segwit P2SH-P2WPKH redeem script" assert self.mmtype.name == 'segwit', 'This command is meaningful only for --type=segwit' from ..proto.btc.common import hash160 return self.proto.pubhash2redeem_script(hash160(bytes.fromhex(pubkeyhex))).hex() - def redeem_script2addr(self, redeem_script_hex: 'sstr'): # new + def redeem_script2addr(self, redeem_script_hex: fstr): # new "convert a Segwit P2SH-P2WPKH redeem script to an address" assert self.mmtype.name == 'segwit', 'This command is meaningful only for --type=segwit' assert redeem_script_hex.startswith('0014'), f'{redeem_script_hex!r}: invalid redeem script' @@ -161,7 +161,7 @@ class tool_cmd(tool_cmd_base): from ..proto.btc.common import hash160 return self.proto.pubhash2addr(hash160(bytes.fromhex(redeem_script_hex)), 'p2sh') - def pubhash2addr(self, pubhashhex: 'sstr'): + def pubhash2addr(self, pubhashhex: fstr): "convert public key hash to address" pubhash = bytes.fromhex(pubhashhex) match self.mmtype.name: @@ -182,25 +182,25 @@ class tool_cmd(tool_cmd_base): die(2, f'{ap.fmt} addresses cannot be converted to {ptype}') return ap.bytes.hex() - def addr2pubhex(self, addr: 'sstr'): + def addr2pubhex(self, addr: fstr): "convert coin address to public key" return self._addr2pub(addr, ptype='pubkey') - def addr2pubhash(self, addr: 'sstr'): + def addr2pubhash(self, addr: fstr): "convert coin address to public key hash" return self._addr2pub(addr, ptype='pubhash') - def addr2scriptpubkey(self, addr: 'sstr'): + def addr2scriptpubkey(self, addr: fstr): "convert coin address to scriptPubKey" from ..proto.btc.tx.base import addr2scriptPubKey return addr2scriptPubKey(self.proto, CoinAddr(self.proto, addr)) - def scriptpubkey2addr(self, hexstr: 'sstr'): + def scriptpubkey2addr(self, hexstr: fstr): "convert scriptPubKey to coin address" from ..proto.btc.tx.base import decodeScriptPubKey return decodeScriptPubKey(self.proto, hexstr).addr - def eth_checksummed_addr(self, addr: 'sstr'): + def eth_checksummed_addr(self, addr: fstr): "create a checksummed Ethereum address" from ..protocol import init_proto return init_proto(self.cfg, 'eth').checksummed_addr(addr) diff --git a/mmgen/tool/common.py b/mmgen/tool/common.py index 289566ba..bcf013ed 100755 --- a/mmgen/tool/common.py +++ b/mmgen/tool/common.py @@ -22,6 +22,9 @@ tool.common: Base class and shared routines for the 'mmgen-tool' utility from ..objmethods import MMGenObject +class fstr(str): + pass + def options_annot_str(l): return "(valid choices: '{}')".format("','".join(l)) diff --git a/mmgen/tool/help.py b/mmgen/tool/help.py index ac5fbc9a..9155b180 100755 --- a/mmgen/tool/help.py +++ b/mmgen/tool/help.py @@ -22,7 +22,7 @@ tool.help: Help screen routines for the 'mmgen-tool' utility from .. import main_tool -from .common import tool_cmd_base +from .common import tool_cmd_base, fstr def main_help(): @@ -156,12 +156,12 @@ def gen_tool_cmd_usage(mod, cmdname): yield f'Required {ARGS} (type shown in square brackets):' yield '' for argname, argtype in args: - have_sstr = ann.get(argname) == 'sstr' + have_fstr = ann.get(argname) == fstr yield ' {a:{w}} [{b}]{c}{d}'.format( a = argname, b = argtype, - c = " (use '-' to read from STDIN)" if have_sstr else '', - d = ' ' + ann[argname] if isinstance(ann.get(argname), str) and not have_sstr else '', + c = " (use '-' to read from STDIN)" if have_fstr else '', + d = ' ' + ann[argname] if isinstance(ann.get(argname), str) and not have_fstr else '', w = max_w) if kwargs: diff --git a/mmgen/tool/mnemonic.py b/mmgen/tool/mnemonic.py index baccea86..0dd70b76 100755 --- a/mmgen/tool/mnemonic.py +++ b/mmgen/tool/mnemonic.py @@ -22,7 +22,7 @@ tool.mnemonic: Mnemonic routines for the 'mmgen-tool' utility from collections import namedtuple -from .common import tool_cmd_base, options_annot_str +from .common import tool_cmd_base, options_annot_str, fstr from ..baseconv import baseconv from ..xmrseed import xmrseed @@ -93,14 +93,14 @@ class tool_cmd(tool_cmd_base): "generate a random 256-bit mnemonic seed phrase" return self._do_random_mn(32, fmt) - def hex2mn(self, hexstr: 'sstr', fmt: mn_opts_disp = dfl_mnemonic_fmt): + def hex2mn(self, hexstr: fstr, fmt: mn_opts_disp = dfl_mnemonic_fmt): "convert a 16, 24 or 32-byte hexadecimal string to a mnemonic seed phrase" if fmt == 'xmrseed': hexstr = self._xmr_reduce(bytes.fromhex(hexstr)).hex() f = mnemonic_fmts[fmt] return ' '.join(f.conv_cls(fmt).fromhex(hexstr, pad=f.pad)) - def mn2hex(self, seed_mnemonic: 'sstr', fmt: mn_opts_disp = dfl_mnemonic_fmt): + def mn2hex(self, seed_mnemonic: fstr, fmt: mn_opts_disp = dfl_mnemonic_fmt): "convert a mnemonic seed phrase to a hexadecimal string" f = mnemonic_fmts[fmt] return f.conv_cls(fmt).tohex(seed_mnemonic.split(), pad=f.pad) diff --git a/mmgen/tool/util.py b/mmgen/tool/util.py index 284457af..d50a3ef3 100755 --- a/mmgen/tool/util.py +++ b/mmgen/tool/util.py @@ -24,7 +24,7 @@ import os from ..cfg import gc -from .common import tool_cmd_base +from .common import tool_cmd_base, fstr class tool_cmd(tool_cmd_base): "general string conversion and hashing utilities" @@ -100,7 +100,7 @@ class tool_cmd(tool_cmd_base): from ..crypto import Crypto return Crypto(self.cfg).get_random(nbytes).hex() - def hexreverse(self, hexstr: 'sstr'): + def hexreverse(self, hexstr: fstr): "reverse bytes of a hexadecimal string" return bytes.fromhex(hexstr.strip())[::-1].hex() @@ -110,7 +110,7 @@ class tool_cmd(tool_cmd_base): data = get_data_from_file(self.cfg, infile, dash=True, quiet=True, binary=True) return data.hex() - def unhexlify(self, hexstr: 'sstr'): + def unhexlify(self, hexstr: fstr): "convert a hexadecimal string to bytes (warning: outputs binary data)" return bytes.fromhex(hexstr) @@ -135,7 +135,7 @@ class tool_cmd(tool_cmd_base): hexdata = get_data_from_file(self.cfg, infile, dash=True, quiet=True) return decode_pretty_hexdump(hexdata) - def hash160(self, hexstr: 'sstr'): + def hash160(self, hexstr: fstr): "compute ripemd160(sha256(data)) (convert hex pubkey to hex addr)" from ..proto.btc.common import hash160 return hash160(bytes.fromhex(hexstr)).hex() @@ -165,7 +165,7 @@ class tool_cmd(tool_cmd_base): return make_chksum_6( get_data_from_file(self.cfg, infile, dash=True, quiet=True, binary=True)) - def str2id6(self, string: 'sstr'): # retain ignoring of space for backwards compat + def str2id6(self, string: fstr): # retain ignoring of space for backwards compat "generate 6-character MMGen ID for a string, ignoring spaces in string" from ..util import make_chksum_6 return make_chksum_6(''.join(string.split())) @@ -192,43 +192,43 @@ class tool_cmd(tool_cmd_base): data = get_data_from_file(self.cfg, infile, dash=True, quiet=True, binary=True) return baseconv('b58').frombytes(data, pad=pad, tostr=True) - def b58tobytes(self, b58_str: 'sstr', *, pad: 'pad output to this width' = 0): + def b58tobytes(self, b58_str: fstr, *, pad: 'pad output to this width' = 0): "convert a base 58 string to bytes (warning: outputs binary data)" from ..baseconv import baseconv return baseconv('b58').tobytes(b58_str, pad=pad) - def hextob58(self, hexstr: 'sstr', *, pad: 'pad output to this width' = 0): + def hextob58(self, hexstr: fstr, *, pad: 'pad output to this width' = 0): "convert a hexadecimal string to base 58" from ..baseconv import baseconv return baseconv('b58').fromhex(hexstr, pad=pad, tostr=True) - def b58tohex(self, b58_str: 'sstr', *, pad: 'pad output to this width' = 0): + def b58tohex(self, b58_str: fstr, *, pad: 'pad output to this width' = 0): "convert a base 58 string to hexadecimal" from ..baseconv import baseconv return baseconv('b58').tohex(b58_str, pad=pad) - def hextob58chk(self, hexstr: 'sstr'): + def hextob58chk(self, hexstr: fstr): "convert a hexadecimal string to base58-check encoding" from ..proto.btc.common import b58chk_encode return b58chk_encode(bytes.fromhex(hexstr)) - def b58chktohex(self, b58chk_str: 'sstr'): + def b58chktohex(self, b58chk_str: fstr): "convert a base58-check encoded string to hexadecimal" from ..proto.btc.common import b58chk_decode return b58chk_decode(b58chk_str).hex() - def hextob32(self, hexstr: 'sstr', *, pad: 'pad output to this width' = 0): + def hextob32(self, hexstr: fstr, *, pad: 'pad output to this width' = 0): "convert a hexadecimal string to an MMGen-flavor base 32 string" from ..baseconv import baseconv return baseconv('b32').fromhex(hexstr, pad=pad, tostr=True) - def b32tohex(self, b32_str: 'sstr', *, pad: 'pad output to this width' = 0): + def b32tohex(self, b32_str: fstr, *, pad: 'pad output to this width' = 0): "convert an MMGen-flavor base 32 string to hexadecimal" from ..baseconv import baseconv return baseconv('b32').tohex(b32_str.upper(), pad=pad) def hextob6d(self, - hexstr: 'sstr', + hexstr: fstr, *, pad: 'pad output to this width' = 0, add_spaces: 'add a space after every 5th character' = True): @@ -238,7 +238,7 @@ class tool_cmd(tool_cmd_base): ret = baseconv('b6d').fromhex(hexstr, pad=pad, tostr=True) return block_format(ret, gw=5, cols=None).strip() if add_spaces else ret - def b6dtohex(self, b6d_str: 'sstr', *, pad: 'pad output to this width' = 0): + def b6dtohex(self, b6d_str: fstr, *, pad: 'pad output to this width' = 0): "convert a die roll base6 (base6d) string to hexadecimal" from ..baseconv import baseconv from ..util import remove_whitespace diff --git a/pyproject.toml b/pyproject.toml index ab11b2e3..d6ab18b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,6 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "protobuf.py" = [ "UP045" ] # Use `X | None` for type annotations "mmgen/rpc/backends/curl.py" = [ "ASYNC221" ] # blocking method in async function -"mmgen/tool/*" = [ "F821" ] # Undefined name `sstr` "create-bip-hd-chain-params.py" = [ "F401" ] "examples/*" = [ "ASYNC230" ] # open() in async function