tool: new fstr class

This commit is contained in:
The MMGen Project 2026-09-29 09:23:30 +00:00
commit 3c0c961385
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
7 changed files with 47 additions and 44 deletions

View file

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

View file

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

View file

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

View file

@ -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:

View file

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

View file

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

View file

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