addr.py: move address file-related methods to new AddrFile class

- AddrFile subclasses: KeyAddrFile,KeyFile,PasswordFile
This commit is contained in:
The MMGen Project 2022-01-15 14:00:07 +00:00
commit 012fea543f
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
7 changed files with 371 additions and 286 deletions

View file

@ -362,15 +362,6 @@ class AddrListIDStr(str,Hilite):
class AddrList(MMGenObject): # Address info for a single seed ID
msgs = {
'file_header': """
# {pnm} address file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
# A text label of {n} screen cells or less may be added to the right of each
# address, and it will be appended to the tracking wallet label upon import.
# The label may contain any printable ASCII symbol.
""".strip().format(n=TwComment.max_screen_width,pnm=pnm),
'record_chksum': """
Record this checksum: it will be used to verify the address file in the future
""".strip(),
@ -381,17 +372,14 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
}
entry_type = AddrListEntry
main_attr = 'addr'
data_desc = 'address'
file_desc = 'addresses'
desc = 'address'
gen_desc = 'address'
gen_desc_pl = 'es'
gen_addrs = True
gen_passwds = False
gen_keys = False
has_keys = False
ext = 'addrs'
chksum_rec_f = lambda foo,e: (str(e.idx), e.addr)
line_ctr = 0
def __init__(self,proto,
addrfile = '',
@ -424,7 +412,7 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
do_chksum = True
elif addrfile: # data from MMGen address file
self.infile = addrfile
adata = self.parse_file(addrfile) # sets self.al_id
adata = self.get_file().parse_file(addrfile) # sets self.al_id
do_chksum = True
elif al_id and adata: # data from tracking wallet
self.al_id = al_id
@ -457,7 +445,7 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
if do_chksum and not skip_chksum:
self.chksum = AddrListChksum(self)
qmsg(
f'Checksum for {self.data_desc} data {self.id_str.hl()}: {self.chksum.hl()}\n' +
f'Checksum for {self.desc} data {self.id_str.hl()}: {self.chksum.hl()}\n' +
self.msgs[('check_chksum','record_chksum')[src=='gen']] )
def update_msgs(self):
@ -546,17 +534,6 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
dmsg_sc('str',scramble_key)
return scramble_seed(seed,scramble_key.encode())
def encrypt(self,desc='new key list'):
from .crypto import mmgen_encrypt
self.fmt_data = mmgen_encrypt(self.fmt_data.encode(),desc,'')
self.ext += '.'+g.mmenc_ext
def write_to_file(self,ask_tty=True,ask_write_default_yes=False,binary=False,desc=None):
tn = ('.' + self.proto.network) if self.proto.testnet else ''
fn = '{}{x}{}.{}'.format(self.id_str,tn,self.ext,x='' if g.debug_utf8 else '')
ask_tty = self.has_keys and not opt.quiet
write_data_to_file(fn,self.fmt_data,desc or self.file_desc,ask_tty=ask_tty,binary=binary)
def idxs(self):
return [e.idx for e in self.data]
@ -628,225 +605,26 @@ Removed {{}} duplicate WIF key{{}} from keylist (also in {pnm} key-address file
def list_missing(self,attr):
return [d.addr for d in self.data if not getattr(d,attr)]
def make_label(self):
bc,mt = self.proto.base_coin,self.al_id.mmtype
l_coin = [] if bc == 'BTC' else [self.proto.coin] if bc == 'ETH' else [bc]
l_type = [] if mt == 'E' or (mt == 'L' and not self.proto.testnet) else [mt.name.upper()]
l_tn = [] if not self.proto.testnet else [self.proto.network.upper()]
lbl_p2 = ':'.join(l_coin+l_type+l_tn)
return self.al_id.sid + ('',' ')[bool(lbl_p2)] + lbl_p2
def format(self,add_comments=False):
out = [self.msgs['file_header']+'\n']
if self.chksum:
out.append(f'# {capfirst(self.data_desc)} data checksum for {self.id_str}: {self.chksum}')
out.append('# Record this value to a secure location.\n')
lbl = self.make_label()
dmsg_sc('lbl',lbl[9:])
out.append(f'{lbl} {{')
fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
for e in self.data:
c = ' '+e.label if add_comments and e.label else ''
if type(self) == KeyList:
out.append(fs.format( e.idx, f'{self.al_id.mmtype.wif_label}: {e.sec.wif}', c ))
elif type(self) == PasswordList:
out.append(fs.format(e.idx,e.passwd,c))
else: # First line with idx
out.append(fs.format(e.idx,e.addr,c))
if self.has_keys:
if opt.b16:
out.append(fs.format( '', f'orig_hex: {e.sec.orig_hex}', c ))
out.append(fs.format( '', f'{self.al_id.mmtype.wif_label}: {e.sec.wif}', c ))
for k in ('viewkey','wallet_passwd'):
v = getattr(e,k)
if v: out.append(fs.format( '', f'{k}: {v}', c ))
out.append('}')
self.fmt_data = '\n'.join([l.rstrip() for l in out]) + '\n'
def get_line(self,lines):
ret = lines.pop(0).split(None,2)
self.line_ctr += 1
if ret[0] == 'orig_hex:': # hacky
ret = lines.pop(0).split(None,2)
self.line_ctr += 1
return ret if len(ret) == 3 else ret + ['']
def parse_file_body(self,lines):
ret = AddrListData()
le = self.entry_type
iifs = "{!r}: invalid identifier [expected '{}:']"
while lines:
idx,addr,lbl = self.get_line(lines)
assert is_mmgen_idx(idx), f'invalid address index {idx!r}'
self.check_format(addr)
a = le(**{ 'proto': self.proto, 'idx':int(idx), self.main_attr:addr, 'label':lbl })
if self.has_keys: # order: wif,(orig_hex),viewkey,wallet_passwd
d = self.get_line(lines)
assert d[0] == self.al_id.mmtype.wif_label+':', iifs.format(d[0],self.al_id.mmtype.wif_label)
a.sec = PrivKey(proto=self.proto,wif=d[1])
for k,dtype,add_proto in (
('viewkey',ViewKey,True),
('wallet_passwd',WalletPassword,False) ):
if k in self.al_id.mmtype.extra_attrs:
d = self.get_line(lines)
assert d[0] == k+':', iifs.format(d[0],k)
setattr(a,k,dtype( *((self.proto,d[1]) if add_proto else (d[1],)) ) )
ret.append(a)
if self.has_keys and not self.skip_ka_check:
if getattr(opt,'yes',False) or keypress_confirm('Check key-to-address validity?'):
kg = KeyGenerator(self.proto,self.al_id.mmtype)
ag = AddrGenerator(self.proto,self.al_id.mmtype)
llen = len(ret)
for n,e in enumerate(ret):
qmsg_r(f'\rVerifying keys {n+1}/{llen}')
assert e.addr == ag.to_addr(kg.to_pubhex(e.sec)),(
f'Key doesn’t match address!\n {e.sec.wif}\n {e.addr}')
qmsg(' - done')
return ret
def parse_file(self,fn,buf=[],exit_on_error=True):
def parse_addrfile_label(lbl):
"""
label examples:
- Bitcoin legacy mainnet: no label
- Bitcoin legacy testnet: 'LEGACY:TESTNET'
- Bitcoin Segwit: 'SEGWIT'
- Bitcoin Segwit testnet: 'SEGWIT:TESTNET'
- Bitcoin Bech32 regtest: 'BECH32:REGTEST'
- Litecoin legacy mainnet: 'LTC'
- Litecoin Bech32 mainnet: 'LTC:BECH32'
- Litecoin legacy testnet: 'LTC:LEGACY:TESTNET'
- Ethereum mainnet: 'ETH'
- Ethereum Classic mainnet: 'ETC'
- Ethereum regtest: 'ETH:REGTEST'
"""
lbl = lbl.lower()
# remove the network component:
if lbl.endswith(':testnet'):
network = 'testnet'
lbl = lbl[:-8]
elif lbl.endswith(':regtest'):
network = 'regtest'
lbl = lbl[:-8]
else:
network = 'mainnet'
if lbl in self.bitcoin_addrtypes:
coin,mmtype_key = ( 'BTC', lbl )
elif ':' in lbl: # first component is coin, second is mmtype_key
coin,mmtype_key = lbl.split(':')
else: # only component is coin
coin,mmtype_key = ( lbl, None )
proto = init_proto(coin=coin,network=network)
if mmtype_key == None:
mmtype_key = proto.mmtypes[0]
return ( proto, proto.addr_type(mmtype_key) )
lines = get_lines_from_file(fn,self.data_desc+' data',trim_comments=True)
try:
assert len(lines) >= 3, f'Too few lines in address file ({len(lines)})'
ls = lines[0].split()
assert 1 < len(ls) < 5, f'Invalid first line for {self.gen_desc} file: {lines[0]!r}'
assert ls.pop() == '{', f'{ls!r}: invalid first line'
assert lines[-1] == '}', f'{lines[-1]!r}: invalid last line'
sid = ls.pop(0)
assert is_seed_id(sid), f'{sid!r}: invalid Seed ID'
if type(self) == PasswordList and len(ls) == 2:
ss = ls.pop().split(':')
assert len(ss) == 2, f'{ss!r}: invalid password length specifier (must contain colon)'
self.set_pw_fmt(ss[0])
self.set_pw_len(ss[1])
self.pw_id_str = MMGenPWIDString(ls.pop())
proto = init_proto('btc')# FIXME: dummy protocol
mmtype = MMGenPasswordType(proto,'P')
elif len(ls) == 1:
proto,mmtype = parse_addrfile_label(ls[0])
elif len(ls) == 0:
proto = init_proto('btc')
mmtype = proto.addr_type('L')
else:
raise ValueError(f'{lines[0]}: Invalid first line for {self.gen_desc} file {fn!r}')
if type(self) != PasswordList:
if proto.base_coin != self.proto.base_coin or proto.network != self.proto.network:
"""
Having caller supply protocol and checking address file protocol against it here
allows us to catch all mismatches in one place. This behavior differs from that of
transaction files, which determine the protocol independently, requiring the caller
to check for protocol mismatches (e.g. MMGenTX.check_correct_chain())
"""
raise ValueError(
f'{self.data_desc} file is '
+ f'{proto.base_coin} {proto.network} but protocol is '
+ f'{self.proto.base_coin} {self.proto.network}' )
self.base_coin = proto.base_coin
self.network = proto.network
self.al_id = AddrListID(SeedID(sid=sid),mmtype)
data = self.parse_file_body(lines[1:-1])
assert isinstance(data,list),'Invalid file body data'
except Exception as e:
m = 'Invalid data in {} list file {!r}{} ({!s})'.format(
self.data_desc,
self.infile,
(f', content line {self.line_ctr}' if self.line_ctr else ''),
e )
if exit_on_error:
die(3,m)
else:
msg(m)
return False
return data
def get_file(self):
import mmgen.addrfile as mod
return getattr( mod, type(self).__name__.replace('List','File') )(self)
class KeyAddrList(AddrList):
data_desc = 'key-address'
file_desc = 'secret keys'
desc = 'key-address'
gen_desc = 'key/address pair'
gen_desc_pl = 's'
gen_addrs = True
gen_keys = True
has_keys = True
ext = 'akeys'
chksum_rec_f = lambda foo,e: (str(e.idx), e.addr, e.sec.wif)
class KeyList(AddrList):
msgs = {
'file_header': f"""
# {pnm} key file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
""".strip()
}
data_desc = 'key'
file_desc = 'secret keys'
desc = 'key'
gen_desc = 'key'
gen_desc_pl = 's'
gen_addrs = False
gen_keys = True
has_keys = True
ext = 'keys'
chksum_rec_f = lambda foo,e: (str(e.idx), e.addr, e.sec.wif)
def is_bip39_str(s):
@ -859,37 +637,19 @@ def is_xmrseed(s):
from collections import namedtuple
class PasswordList(AddrList):
msgs = {
'file_header': f"""
# {pnm} password file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
# A text label of {TwComment.max_screen_width} screen cells or less may be added to the right of each
# password. The label may contain any printable ASCII symbol.
#
""".strip(),
'file_header_mn': f"""
# {pnm} {{}} password file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
#
""".strip(),
'record_chksum': """
Record this checksum: it will be used to verify the password file in the future
""".strip()
}
entry_type = PasswordListEntry
main_attr = 'passwd'
data_desc = 'password'
file_desc = 'passwords'
desc = 'password'
gen_desc = 'password'
gen_desc_pl = 's'
gen_addrs = False
gen_keys = False
gen_passwds = True
has_keys = False
ext = 'pws'
pw_len = None
dfl_pw_fmt = 'b58'
pwinfo = namedtuple('passwd_info',['min_len','max_len','dfl_len','valid_lens','desc','chk_func'])
@ -920,7 +680,7 @@ Record this checksum: it will be used to verify the password file in the future
if infile:
self.infile = infile
self.data = self.parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
self.data = self.get_file().parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
else:
if not chk_params_only:
for k in (seed,pw_idxs):
@ -936,9 +696,6 @@ Record this checksum: it will be used to verify the password file in the future
self.al_id = AddrListID(seed.sid,MMGenPasswordType(self.proto,'P'))
self.data = self.generate(seed,pw_idxs)
if self.pw_fmt in ('bip39','xmrseed'):
self.msgs['file_header'] = self.msgs['file_header_mn'].format(self.pw_fmt.upper())
self.num_addrs = len(self.data)
self.fmt_data = ''
self.chksum = AddrListChksum(self)
@ -946,7 +703,7 @@ Record this checksum: it will be used to verify the password file in the future
fs = f'{self.al_id.sid}-{self.pw_id_str}-{self.pw_fmt_disp}-{self.pw_len}[{{}}]'
self.id_str = AddrListIDStr(self,fs)
qmsg(
f'Checksum for {self.data_desc} data {self.id_str.hl()}: {self.chksum.hl()}\n' +
f'Checksum for {self.desc} data {self.id_str.hl()}: {self.chksum.hl()}\n' +
self.msgs[('record_chksum','check_chksum')[bool(infile)]] )
def set_pw_fmt(self,pw_fmt):
@ -1072,26 +829,6 @@ Record this checksum: it will be used to verify the password file in the future
dmsg_sc('str',scramble_key)
return scramble_seed(seed,scramble_key.encode())
def get_line(self,lines):
self.line_ctr += 1
if self.pw_fmt in ('bip39','xmrseed'):
ret = lines.pop(0).split(None,self.pw_len+1)
if len(ret) > self.pw_len+1:
m1 = f'extraneous text {ret[self.pw_len+1]!r} found after password'
m2 = '[bare comments not allowed in BIP39 password files]'
m = m1+' '+m2
elif len(ret) < self.pw_len+1:
m = f'invalid password length {len(ret)-1}'
else:
return (ret[0],' '.join(ret[1:self.pw_len+1]),'')
raise ValueError(m)
else:
ret = lines.pop(0).split(None,2)
return ret if len(ret) == 3 else ret + ['']
def make_label(self):
return f'{self.al_id.sid} {self.pw_id_str} {self.pw_fmt_disp}:{self.pw_len}'
class AddrData(MMGenObject):
msgs = {
'too_many_acct_addresses': f"""

339
mmgen/addrfile.py Executable file
View file

@ -0,0 +1,339 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
addrfile.py: Address and password file classes for the MMGen suite
"""
from .util import (
msg,
qmsg,
qmsg_r,
die,
capfirst,
get_lines_from_file,
write_data_to_file,
keypress_confirm,
)
from .protocol import init_proto
from .obj import *
from .seed import SeedID,is_seed_id
from .addr import KeyList,PasswordList,dmsg_sc
class AddrFile(MMGenObject):
desc = 'addresses'
ext = 'addrs'
line_ctr = 0
file_header = """
# {pnm} address file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
# A text label of {n} screen cells or less may be added to the right of each
# address, and it will be appended to the tracking wallet label upon import.
# The label may contain any printable ASCII symbol.
"""
def __init__(self,parent):
self.parent = parent
self.infile = None
def encrypt(self,desc='new key list'):
from .crypto import mmgen_encrypt
from .globalvars import g
self.fmt_data = mmgen_encrypt(self.fmt_data.encode(),desc,'')
self.ext += f'.{g.mmenc_ext}'
@property
def filename(self):
from .globalvars import g
return '{}{x}{}.{}'.format(
self.parent.id_str,
('.' + self.parent.proto.network) if self.parent.proto.testnet else '',
self.ext,
x = '' if g.debug_utf8 else '' )
def write(self,fn=None,ask_tty=True,ask_write_default_yes=False,binary=False,desc=None):
from .opts import opt
write_data_to_file(
fn or self.filename,
self.fmt_data,
desc or self.desc,
ask_tty = self.parent.has_keys and not opt.quiet,
binary = binary )
def make_label(self):
p = self.parent
bc,mt = p.proto.base_coin,p.al_id.mmtype
l_coin = [] if bc == 'BTC' else [p.proto.coin] if bc == 'ETH' else [bc]
l_type = [] if mt == 'E' or (mt == 'L' and not p.proto.testnet) else [mt.name.upper()]
l_tn = [] if not p.proto.testnet else [p.proto.network.upper()]
lbl_p2 = ':'.join(l_coin+l_type+l_tn)
return p.al_id.sid + ('',' ')[bool(lbl_p2)] + lbl_p2
def format(self,add_comments=False):
p = self.parent
fh = (
self.file_header_mn.format(p.pw_fmt.upper())
if p.gen_passwds and p.pw_fmt in ('bip39','xmrseed') else
self.file_header ).strip()
from .globalvars import g
out = [fh.format(pnm=g.proj_name,n=TwComment.max_screen_width) + '\n']
if p.chksum:
out.append(f'# {capfirst(p.desc)} data checksum for {p.id_str}: {p.chksum}')
out.append('# Record this value to a secure location.\n')
lbl = self.make_label()
dmsg_sc('lbl',lbl[9:])
out.append(f'{lbl} {{')
fs = ' {:<%s} {:<34}{}' % len(str(p.data[-1].idx))
for e in p.data:
c = ' '+e.label if add_comments and e.label else ''
if type(p) == KeyList:
out.append(fs.format( e.idx, f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c ))
elif type(p) == PasswordList:
out.append(fs.format(e.idx,e.passwd,c))
else: # First line with idx
out.append(fs.format(e.idx,e.addr,c))
if p.has_keys:
from .opts import opt
if opt.b16:
out.append(fs.format( '', f'orig_hex: {e.sec.orig_bytes.hex()}', c ))
out.append(fs.format( '', f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c ))
for k in ('viewkey','wallet_passwd'):
v = getattr(e,k)
if v: out.append(fs.format( '', f'{k}: {v}', c ))
out.append('}')
self.fmt_data = '\n'.join([l.rstrip() for l in out]) + '\n'
def get_line(self,lines):
ret = lines.pop(0).split(None,2)
self.line_ctr += 1
if ret[0] == 'orig_hex:': # hacky
ret = lines.pop(0).split(None,2)
self.line_ctr += 1
return ret if len(ret) == 3 else ret + ['']
def parse_file_body(self,lines):
p = self.parent
ret = AddrListData()
le = p.entry_type
iifs = "{!r}: invalid identifier [expected '{}:']"
while lines:
idx,addr,lbl = self.get_line(lines)
assert is_addr_idx(idx), f'invalid address index {idx!r}'
p.check_format(addr)
a = le(**{ 'proto': p.proto, 'idx':int(idx), p.main_attr:addr, 'label':lbl })
if p.has_keys: # order: wif,(orig_hex),viewkey,wallet_passwd
d = self.get_line(lines)
assert d[0] == p.al_id.mmtype.wif_label+':', iifs.format(d[0],p.al_id.mmtype.wif_label)
a.sec = PrivKey(proto=p.proto,wif=d[1])
for k,dtype,add_proto in (
('viewkey',ViewKey,True),
('wallet_passwd',WalletPassword,False) ):
if k in p.al_id.mmtype.extra_attrs:
d = self.get_line(lines)
assert d[0] == k+':', iifs.format(d[0],k)
setattr(a,k,dtype( *((p.proto,d[1]) if add_proto else (d[1],)) ) )
ret.append(a)
if p.has_keys and not p.skip_ka_check:
from .opts import opt
if getattr(opt,'yes',False) or keypress_confirm('Check key-to-address validity?'):
from .addr import KeyGenerator,AddrGenerator
kg = KeyGenerator(p.proto,p.al_id.mmtype.pubkey_type)
ag = AddrGenerator(p.proto,p.al_id.mmtype)
llen = len(ret)
for n,e in enumerate(ret):
qmsg_r(f'\rVerifying keys {n+1}/{llen}')
assert e.addr == ag.to_addr(kg.to_pubhex(e.sec)),(
f'Key doesn’t match address!\n {e.sec.wif}\n {e.addr}')
qmsg(' - done')
return ret
def parse_file(self,fn,buf=[],exit_on_error=True):
def parse_addrfile_label(lbl):
"""
label examples:
- Bitcoin legacy mainnet: no label
- Bitcoin legacy testnet: 'LEGACY:TESTNET'
- Bitcoin Segwit: 'SEGWIT'
- Bitcoin Segwit testnet: 'SEGWIT:TESTNET'
- Bitcoin Bech32 regtest: 'BECH32:REGTEST'
- Litecoin legacy mainnet: 'LTC'
- Litecoin Bech32 mainnet: 'LTC:BECH32'
- Litecoin legacy testnet: 'LTC:LEGACY:TESTNET'
- Ethereum mainnet: 'ETH'
- Ethereum Classic mainnet: 'ETC'
- Ethereum regtest: 'ETH:REGTEST'
"""
lbl = lbl.lower()
# remove the network component:
if lbl.endswith(':testnet'):
network = 'testnet'
lbl = lbl[:-8]
elif lbl.endswith(':regtest'):
network = 'regtest'
lbl = lbl[:-8]
else:
network = 'mainnet'
if lbl in p.bitcoin_addrtypes:
coin,mmtype_key = ( 'BTC', lbl )
elif ':' in lbl: # first component is coin, second is mmtype_key
coin,mmtype_key = lbl.split(':')
else: # only component is coin
coin,mmtype_key = ( lbl, None )
proto = init_proto(coin=coin,network=network)
if mmtype_key == None:
mmtype_key = proto.mmtypes[0]
return ( proto, proto.addr_type(mmtype_key) )
p = self.parent
lines = get_lines_from_file(fn,p.desc+' data',trim_comments=True)
try:
assert len(lines) >= 3, f'Too few lines in address file ({len(lines)})'
ls = lines[0].split()
assert 1 < len(ls) < 5, f'Invalid first line for {p.gen_desc} file: {lines[0]!r}'
assert ls.pop() == '{', f'{ls!r}: invalid first line'
assert lines[-1] == '}', f'{lines[-1]!r}: invalid last line'
sid = ls.pop(0)
assert is_seed_id(sid), f'{sid!r}: invalid Seed ID'
if type(p) == PasswordList and len(ls) == 2:
ss = ls.pop().split(':')
assert len(ss) == 2, f'{ss!r}: invalid password length specifier (must contain colon)'
p.set_pw_fmt(ss[0])
p.set_pw_len(ss[1])
p.pw_id_str = MMGenPWIDString(ls.pop())
proto = init_proto('btc') # FIXME: dummy protocol
mmtype = MMGenPasswordType(proto,'P')
elif len(ls) == 1:
proto,mmtype = parse_addrfile_label(ls[0])
elif len(ls) == 0:
proto = init_proto('btc')
mmtype = proto.addr_type('L')
else:
raise ValueError(f'{lines[0]}: Invalid first line for {p.gen_desc} file {fn!r}')
if type(p) != PasswordList:
if proto.base_coin != p.proto.base_coin or proto.network != p.proto.network:
"""
Having caller supply protocol and checking address file protocol against it here
allows us to catch all mismatches in one place. This behavior differs from that of
transaction files, which determine the protocol independently, requiring the caller
to check for protocol mismatches (e.g. MMGenTX.check_correct_chain())
"""
raise ValueError(
f'{p.desc} file is '
+ f'{proto.base_coin} {proto.network} but protocol is '
+ f'{p.proto.base_coin} {p.proto.network}' )
p.base_coin = proto.base_coin
p.network = proto.network
p.al_id = AddrListID(SeedID(sid=sid),mmtype)
data = self.parse_file_body(lines[1:-1])
assert isinstance(data,list),'Invalid file body data'
except Exception as e:
m = 'Invalid data in {} list file {!r}{} ({!s})'.format(
p.desc,
self.infile,
(f', content line {self.line_ctr}' if self.line_ctr else ''),
e )
if exit_on_error:
die(3,m)
else:
msg(m)
return False
return data
class KeyAddrFile(AddrFile):
desc = 'secret keys'
ext = 'akeys'
class KeyFile(KeyAddrFile):
ext = 'keys'
file_header = """
# {pnm} key file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
"""
class PasswordFile(AddrFile):
desc = 'passwords'
ext = 'pws'
file_header = """
# {pnm} password file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
# A text label of {n} screen cells or less may be added to the right of each
# password. The label may contain any printable ASCII symbol.
#
"""
file_header_mn = """
# {{pnm}} {} password file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by {{pnm}}.
#
"""
def get_line(self,lines):
self.line_ctr += 1
p = self.parent
if p.pw_fmt in ('bip39','xmrseed'):
ret = lines.pop(0).split(None,p.pw_len+1)
if len(ret) > p.pw_len+1:
m1 = f'extraneous text {ret[p.pw_len+1]!r} found after password'
m2 = '[bare comments not allowed in BIP39 password files]'
m = m1+' '+m2
elif len(ret) < p.pw_len+1:
m = f'invalid password length {len(ret)-1}'
else:
return (ret[0],' '.join(ret[1:p.pw_len+1]),'')
raise ValueError(m)
else:
ret = lines.pop(0).split(None,2)
return ret if len(ret) == 3 else ret + ['']
def make_label(self):
p = self.parent
return f'{p.al_id.sid} {p.pw_id_str} {p.pw_fmt_disp}:{p.pw_len}'

View file

@ -24,6 +24,7 @@ mmgen-addrgen: Generate a series or range of addresses from an MMGen
from .common import *
from .crypto import *
from .addr import AddrList,KeyAddrList,KeyList,MMGenAddrType,AddrIdxList
from .addrfile import AddrFile
from .wallet import Wallet
if g.prog_name == 'mmgen-keygen':
@ -161,13 +162,17 @@ al = globals()[gen_clsname](
addr_idxs = idxs,
mmtype = addr_type )
al.format()
af = al.get_file()
af.format()
if al.gen_addrs and opt.print_checksum:
Die(0,al.checksum)
if al.gen_keys and keypress_confirm('Encrypt key list?'):
al.encrypt()
al.write_to_file(binary=True,desc='encrypted '+al.file_desc)
af.encrypt()
af.write(
binary = True,
desc = f'encrypted {af.desc}' )
else:
al.write_to_file()
af.write()

View file

@ -169,12 +169,14 @@ al = PasswordList(
pw_len = pw_len,
pw_fmt = pw_fmt )
al.format()
af = al.get_file()
af.format()
if keypress_confirm('Encrypt password list?'):
al.encrypt(desc='password list')
al.write_to_file(binary=True,desc='encrypted password list')
af.encrypt(desc='password list')
af.write(binary=True,desc='encrypted password list')
else:
if g.test_suite_popen_spawn and g.platform == 'win':
time.sleep(0.1)
al.write_to_file(desc='password list')
af.write(desc='password list')

View file

@ -57,7 +57,7 @@ def get_obj(objname,*args,**kwargs):
else:
return True if return_bool else ret
def is_mmgen_idx(s): return get_obj(AddrIdx, n=s, silent=True,return_bool=True)
def is_addr_idx(s): return get_obj(AddrIdx, n=s, silent=True,return_bool=True)
def is_addrlist_id(s): return get_obj(AddrListID, sid=s, silent=True,return_bool=True)
def is_mmgen_id(proto,s): return get_obj(MMGenID, proto=proto, id_str=s, silent=True,return_bool=True)

View file

@ -724,8 +724,9 @@ class MMGenTX:
async def get_outputs_from_cmdline(self,cmd_args):
from .addr import AddrList,AddrData,TwAddrData
from .addrfile import AddrFile
addrfiles = remove_dups(
tuple(a for a in cmd_args if get_extension(a) == AddrList.ext),
tuple(a for a in cmd_args if get_extension(a) == AddrFile.ext),
desc = 'command line',
edesc = 'argument',
)

View file

@ -291,8 +291,9 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
a.set_comment(idx,get_label())
else:
if n % 2: a.set_comment(idx,f'Test address {n}')
a.format(add_comments=True)
write_data_to_file(outfile,a.fmt_data,quiet=True,ignore_opt_outdir=True)
af = a.get_file()
af.format(add_comments=True)
write_data_to_file(outfile,af.fmt_data,quiet=True,ignore_opt_outdir=True)
end_silence()
def setup(self):