XMR (Monero) key/address generation support and wallet generating utility

- Monero key-address files include spendkey, viewkey and wallet password
- Wallet password is first 16 bytes of SHA256x2(spendkey)
- Generate Monero wallets either by hand with:
    1) `monero-wallet-cli --generate-from-spend-key walletfile`, using the keyaddrfile data,
  or automatically, using:
    2) `mmgen-tool keyaddrlist2monerowallet keyaddrfile`
  The utility will generate a wallet for each key/address pair in the keyaddrfile and encrypt
  it using the password.  The password is supplied via stdin.

- Other feature: 32-byte hexadecimal password generation with `mmgen-passgen --hex`
This commit is contained in:
MMGen 2017-12-28 16:03:28 +03:00
commit df0385160b
Signed by untrusted user who does not match committer: mmgen
GPG key ID: 62DBE9E5212F05BE
18 changed files with 442 additions and 99 deletions

View file

@ -3,10 +3,11 @@ include doc/wiki/using-mmgen/*
include test/*.py
include test/ref/*
include test/ref/litecoin/*
include test/ref/zcash/*
include test/ref/dash/*
include test/ref/ethereum/*
include test/ref/ethereum_classic/*
include test/ref/dash/*
include test/ref/zcash/*
include test/ref/monero/*
include scripts/bitcoind-walletunlock.py
include scripts/compute-file-chksum.py

View file

@ -40,15 +40,16 @@ class AddrGenerator(MMGenObject):
gen_method = addr_type.gen_method
else:
raise TypeError,'{}: incorrect argument type for {}()'.format(type(addr_type),cls.__name__)
d = {
'p2pkh': AddrGeneratorP2PKH,
'segwit': AddrGeneratorSegwit,
gen_methods = {
'p2pkh': AddrGeneratorP2PKH,
'segwit': AddrGeneratorSegwit,
'ethereum': AddrGeneratorEthereum,
'zcash_z': AddrGeneratorZcashZ
'zcash_z': AddrGeneratorZcashZ,
'monero': AddrGeneratorMonero
}
assert gen_method in d
me = super(cls,cls).__new__(d[gen_method])
me.desc = d
assert gen_method in gen_methods
me = super(cls,cls).__new__(gen_methods[gen_method])
me.desc = gen_methods
return me
class AddrGeneratorP2PKH(AddrGenerator):
@ -113,6 +114,57 @@ class AddrGeneratorZcashZ(AddrGenerator):
def to_segwit_redeem_script(self,pubhex):
raise NotImplementedError,'Zcash z-addresses incompatible with Segwit'
class AddrGeneratorMonero(AddrGenerator):
def b58enc(self,addr_str):
enc,l = baseconv.fromhex,len(addr_str)
a = ''.join([enc(addr_str[i*8:i*8+8].encode('hex'),'b58',pad=11,tostr=True) for i in range(l/8)])
b = enc(addr_str[l-l%8:].encode('hex'),'b58',pad=7,tostr=True)
return a + b
def to_addr(self,sk_hex): # sk_hex instead of pubhex
# ed25519ll, a low-level ctypes wrapper for Ed25519 digital signatures by
# Daniel Holth <dholth@fastmail.fm> - http://bitbucket.org/dholth/ed25519ll/
try:
from ed25519ll.djbec import scalarmult,edwards,encodepoint,B
except:
from mmgen.ed25519 import scalarmult,edwards,encodepoint,B
# Source and license for scalarmultbase function:
# https://github.com/bigreddmachine/MoneroPy/blob/master/moneropy/crypto/ed25519.py
# Copyright (c) 2014-2016, The Monero Project
# All rights reserved.
def scalarmultbase(e):
if e == 0: return [0, 1]
Q = scalarmult(B, e//2)
Q = edwards(Q, Q)
if e & 1: Q = edwards(Q, B)
return Q
def hex2int_le(hexstr):
return int(hexstr.decode('hex')[::-1].encode('hex'),16)
vk_hex = self.to_viewkey(sk_hex)
pk_str = encodepoint(scalarmultbase(hex2int_le(sk_hex)))
pvk_str = encodepoint(scalarmultbase(hex2int_le(vk_hex)))
addr_p1 = g.proto.addr_ver_num['monero'][0].decode('hex') + pk_str + pvk_str
import sha3
return CoinAddr(self.b58enc(addr_p1 + sha3.keccak_256(addr_p1).digest()[:4]))
def to_wallet_passwd(self,sk_hex):
from mmgen.protocol import hash256
return WalletPassword(hash256(sk_hex)[:32])
def to_viewkey(self,sk_hex):
assert len(sk_hex) == 64,'{}: incorrect privkey length'.format(len(sk_hex))
import sha3
return MoneroViewKey(g.proto.preprocess_key(sha3.keccak_256(sk_hex.decode('hex')).hexdigest(),None))
def to_segwit_redeem_script(self,sk_hex):
raise NotImplementedError,'Monero addresses incompatible with Segwit'
class KeyGenerator(MMGenObject):
def __new__(cls,addr_type,generator=None,silent=False):
@ -130,7 +182,7 @@ class KeyGenerator(MMGenObject):
else:
msg('Using (slow) native Python ECDSA library for address generation')
return super(cls,cls).__new__(KeyGeneratorPython)
elif pubkey_type == 'zcash_z':
elif pubkey_type in ('zcash_z','monero'):
g.proto.addr_width = 95
me = super(cls,cls).__new__(KeyGeneratorDummy)
me.desc = 'mmgen-'+pubkey_type
@ -198,10 +250,11 @@ class KeyGeneratorDummy(KeyGenerator):
class AddrListEntry(MMGenListItem):
addr = MMGenListItemAttr('addr','CoinAddr')
viewkey = MMGenListItemAttr('viewkey','ZcashViewKey')
idx = MMGenListItemAttr('idx','AddrIdx') # not present in flat addrlists
label = MMGenListItemAttr('label','TwComment',reassign_ok=True)
sec = MMGenListItemAttr('sec',PrivKey,typeconv=False)
viewkey = MMGenListItemAttr('viewkey','ViewKey')
wallet_passwd = MMGenListItemAttr('wallet_passwd','WalletPassword')
class PasswordListEntry(MMGenListItem):
passwd = MMGenImmutableAttr('passwd',unicode,typeconv=False) # TODO: create Password type
@ -214,7 +267,11 @@ class AddrListChksum(str,Hilite):
trunc_ok = False
def __new__(cls,addrlist):
lines = [' '.join(addrlist.chksum_rec_f(e)) for e in addrlist.data]
ea = addrlist.al_id.mmtype.extra_attrs # add viewkey and passwd to the mix, if present
lines = [' '.join(
addrlist.chksum_rec_f(e) +
tuple(getattr(e,a) for a in ea if getattr(e,a))
) for e in addrlist.data]
return str.__new__(cls,make_chksum_N(' '.join(lines), nchars=16, sep=True))
class AddrListIDStr(unicode,Hilite):
@ -341,7 +398,9 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
compressed = self.al_id.mmtype.compressed
pubkey_type = self.al_id.mmtype.pubkey_type
has_viewkey = self.al_id.mmtype.has_viewkey
gen_wallet_passwd = type(self) == KeyAddrList and 'wallet_passwd' in self.al_id.mmtype.extra_attrs
gen_viewkey = type(self) == KeyAddrList and 'viewkey' in self.al_id.mmtype.extra_attrs
if self.gen_addrs:
kg = KeyGenerator(self.al_id.mmtype)
@ -369,8 +428,10 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
if self.gen_addrs:
ph = kg.to_pubhex(e.sec)
e.addr = ag.to_addr(ph)
if has_viewkey:
if gen_viewkey:
e.viewkey = ag.to_viewkey(ph)
if gen_wallet_passwd:
e.wallet_passwd = ag.to_wallet_passwd(ph)
if type(self) == PasswordList:
e.passwd = unicode(self.make_passwd(e.sec)) # TODO - own type
@ -502,16 +563,17 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
for e in self.data:
c = ' '+e.label if enable_comments and e.label else ''
if type(self) == KeyList:
out.append(fs.format(e.idx,'wif: {}'.format(e.sec.wif),c))
out.append(fs.format(e.idx,'{} {}'.format(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 self.al_id.mmtype.has_viewkey:
out.append(fs.format('','view: '+e.viewkey,c))
if opt.b16: out.append(fs.format('', 'hex: '+e.sec,c))
out.append(fs.format('','wif: '+e.sec.wif,c))
if opt.b16: out.append(fs.format('', 'orig_hex: '+e.sec.orig_hex,c))
out.append(fs.format('','{} {}'.format(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('','{}: {}'.format(k,v),c))
out.append('}')
self.fmt_data = '\n'.join([l.rstrip() for l in out]) + '\n'
@ -521,24 +583,30 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
ret = AddrListList()
le = self.entry_type
while lines:
l = lines.pop(0)
d = l.split(None,2)
def get_line():
ret = lines.pop(0).split(None,2)
if ret[0] == 'orig_hex:': # hacky
return lines.pop(0).split(None,2)
return ret
assert is_mmgen_idx(d[0]),"'%s': invalid address num. in line: '%s'" % (d[0],l)
while lines:
d = get_line()
assert is_mmgen_idx(d[0]),"'%s': invalid address num. in line: '%s'" % (d[0],' '.join(d))
assert self.check_format(d[1]),"'{}': invalid {}".format(d[1],self.data_desc)
if len(d) != 3: d.append('')
a = le(**{'idx':int(d[0]),self.main_attr:d[1],'label':d[2]})
if self.has_keys:
if self.al_id.mmtype.has_viewkey:
d = lines.pop(0).split(None,2)
assert d[0] == 'view:',"Invalid line in file: '{}'".format(' '.join(d))
a.viewkey = ZcashViewKey(d[1])
d = lines.pop(0).split(None,2)
assert d[0] == 'wif:',"Invalid line in file: '{}'".format(' '.join(d))
if self.has_keys: # order: wif,(orig_hex),viewkey,wallet_passwd
d = get_line()
assert d[0] == self.al_id.mmtype.wif_label,"Invalid line in file: '{}'".format(' '.join(d))
a.sec = PrivKey(wif=d[1])
for k,dtype in (('viewkey',ViewKey),('wallet_passwd',WalletPassword)):
if k in self.al_id.mmtype.extra_attrs:
d = get_line()
assert d[0] == k+':',"Invalid line in file: '{}'".format(' '.join(d))
setattr(a,k,dtype(d[1]))
ret.append(a)
@ -571,12 +639,7 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
if not al_mmtype:
mmtype = MMGenAddrType('E' if al_coin in ('ETH','ETC') else 'L',on_fail='raise')
else:
try:
mmtype = MMGenAddrType(al_mmtype,on_fail='raise')
except:
raise ValueError,(
u"'{}': invalid address type in address file. Must be one of: {}".format(
mmtype.upper(),' '.join([i['name'].upper() for i in MMGenAddrType.mmtypes.values()])))
mmtype = MMGenAddrType(al_mmtype,on_fail='raise')
from mmgen.protocol import CoinProtocol
base_coin = CoinProtocol(al_coin or 'BTC',testnet=False).base_coin
@ -685,7 +748,8 @@ Record this checksum: it will be used to verify the password file in the future
pw_fmt = None
pw_info = {
'b58': { 'min_len': 8 , 'max_len': 36 ,'dfl_len': 20, 'desc': 'base-58 password' },
'b32': { 'min_len': 10 ,'max_len': 42 ,'dfl_len': 24, 'desc': 'base-32 password' }
'b32': { 'min_len': 10 ,'max_len': 42 ,'dfl_len': 24, 'desc': 'base-32 password' },
'hex': { 'min_len': 64 ,'max_len': 64 ,'dfl_len': 64, 'desc': 'raw hex password' }
}
chksum_rec_f = lambda foo,e: (str(e.idx), e.passwd)
@ -751,11 +815,14 @@ Record this checksum: it will be used to verify the password file in the future
def make_passwd(self,hex_sec):
assert self.pw_fmt in self.pw_info
# we take least significant part
return ''.join(baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len))[-self.pw_len:]
if self.pw_fmt == 'hex':
return hex_sec
else:
# we take least significant part
return baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len,tostr=True)[-self.pw_len:]
def check_format(self,pw):
if not (is_b58_str,is_b32_str)[self.pw_fmt=='b32'](pw):
if not {'b58':is_b58_str,'b32':is_b32_str,'hex':is_hex_str}[self.pw_fmt](pw):
msg('Password is not a valid {} string'.format(self.pw_fmt))
return False
if len(pw) != self.pw_len:

57
mmgen/ed25519.py Normal file
View file

@ -0,0 +1,57 @@
# The reference Ed25519 software is in the public domain.
# Source: https://ed25519.cr.yp.to/python/ed25519.py
# Date accessed: 2 Nov. 2016
import hashlib
b = 256
q = 2**255 - 19
l = 2**252 + 27742317777372353535851937790883648493
def H(m):
return hashlib.sha512(m).digest()
def expmod(b, e, m):
if e == 0: return 1
t = expmod(b, e//2, m)**2 % m
if e & 1: t = (t*b) % m
return t
def inv(x):
return expmod(x, q-2, q)
d = -121665 * inv(121666)
I = expmod(2, (q-1)//4, q)
def xrecover(y):
xx = (y*y-1) * inv(d*y*y+1)
x = expmod(xx, (q+3)//8, q)
if (x*x - xx) % q != 0: x = (x*I) % q
if x % 2 != 0: x = q-x
return x
By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx%q, By%q]
def edwards(P, Q):
x1 = P[0]
y1 = P[1]
x2 = Q[0]
y2 = Q[1]
x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2)
y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
return [x3%q, y3%q]
def scalarmult(P, e):
if e == 0: return [0, 1]
Q = scalarmult(P, e//2)
Q = edwards(Q, Q)
if e & 1: Q = edwards(Q, P)
return Q
def encodepoint(P):
x = P[0]
y = P[1]
bits = [(y >> i) & 1 for i in range(b-1)] + [x & 1]
return b''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b//8)])

View file

@ -44,7 +44,7 @@ class g(object):
proj_name = 'MMGen'
proj_url = 'https://github.com/mmgen/mmgen'
prog_name = os.path.basename(sys.argv[0])
author = 'Philemon'
author = 'The MMGen Project'
email = '<mmgen@tuta.io>'
Cdates = '2013-2017'
keywords = 'Bitcoin, BTC, cryptocurrency, wallet, cold storage, offline, online, spending, open-source, command-line, Python, Linux, Bitcoin Core, bitcoind, hd, deterministic, hierarchical, secure, anonymous, Electrum, seed, mnemonic, brainwallet, Scrypt, utility, script, scriptable, blockchain, raw, transaction, permissionless, console, terminal, curses, ansi, color, tmux, remote, client, daemon, RPC, json, entropy, xterm, rxvt, PowerShell, MSYS, MinGW, mswin, Armbian, Raspbian, Raspberry Pi, Orange Pi, BCash, BCH, Litecoin, LTC, altcoin, ZEC, Zcash, DASH, ETH, Ethereum, Classic, SHA256Compress'
@ -73,6 +73,7 @@ class g(object):
force_256_color = False
testnet = False
regtest = False
accept_defaults = False
chain = None # set by first call to rpc_init()
chains = 'mainnet','testnet','regtest'
daemon_version = None # set by first call to rpc_init()
@ -108,7 +109,8 @@ class g(object):
# User opt sets global var:
common_opts = (
'color','no_license','rpc_host','rpc_port','testnet','rpc_user','rpc_password',
'daemon_data_dir','force_256_color','regtest','coin','bob','alice'
'daemon_data_dir','force_256_color','regtest','coin','bob','alice',
'accept_defaults'
)
required_opts = (
'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout',
@ -116,6 +118,7 @@ class g(object):
'brain_params','b16','usr_randchars','coin','bob','alice','key_generator'
)
incompatible_opts = (
('base32','hex'), # mmgen-passgen
('bob','alice'),
('quiet','verbose'),
('label','keep_label'),

View file

@ -57,5 +57,5 @@ def launch(what):
else:
try: m = u'{}\n'.format(e[0])
except: m = u'{!r}\n'.format(e[0])
sys.stderr.write(m)
sys.stderr.write(u'ERROR: ' + m)
sys.exit(2)

View file

@ -29,7 +29,8 @@ from mmgen.obj import MMGenPWIDString
dfl_len = {
'b58': PasswordList.pw_info['b58']['dfl_len'],
'b32': PasswordList.pw_info['b32']['dfl_len']
'b32': PasswordList.pw_info['b32']['dfl_len'],
'hex': PasswordList.pw_info['hex']['dfl_len']
}
opts_data = lambda: {
@ -41,6 +42,7 @@ opts_data = lambda: {
-h, --help Print this help message
--, --longhelp Print help message for long options (common options)
-b, --base32 Generate passwords in Base32 format instead of Base58
-x, --hex Generate passwords in raw hex format instead of Base58
-d, --outdir= d Output files to directory 'd' instead of working dir
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
@ -48,9 +50,9 @@ opts_data = lambda: {
'f' at offset 'o' (comma-separated)
-O, --old-incog-fmt Specify old-format incognito input
-L, --passwd-len= l Specify length of generated passwords
(default: {d58} chars [base58], {d32} chars [base32]).
An argument of 'h' will generate passwords of half
the default length.
(default: {d58} chars [base58], {d32} chars [base32],
{dhex} chars [hex]). An argument of 'h' will generate
passwords of half the default length.
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option
is required only for brainwallet and incognito inputs
with non-standard (< {g.seed_len}-bit) seed lengths
@ -65,7 +67,7 @@ opts_data = lambda: {
-v, --verbose Produce more verbose output
""".format(
seed_lens=', '.join([str(i) for i in g.seed_lens]),
g=g,pnm=g.proj_name,d58=dfl_len['b58'],d32=dfl_len['b32'],
g=g,pnm=g.proj_name,d58=dfl_len['b58'],d32=dfl_len['b32'],dhex=dfl_len['hex'],
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)])
),
'notes': """
@ -124,7 +126,7 @@ pw_id_str = cmd_args.pop()
sf = get_seed_file(cmd_args,1)
pw_fmt = ('b58','b32')[bool(opt.base32)]
pw_fmt = ('b58','b32','hex')[bool(opt.base32)+2*bool(opt.hex)]
pw_len = (opt.passwd_len,dfl_len[pw_fmt]/2)[opt.passwd_len in ('h','H')]

View file

@ -20,7 +20,7 @@
obj.py: MMGen native classes
"""
import sys
import sys,os
from decimal import *
from mmgen.color import *
from string import hexdigits,ascii_letters,digits
@ -32,7 +32,7 @@ def is_coin_addr(s): return CoinAddr(s,on_fail='silent')
def is_addrlist_id(s): return AddrListID(s,on_fail='silent')
def is_tw_label(s): return TwLabel(s,on_fail='silent')
def is_wif(s): return WifKey(s,on_fail='silent')
def is_viewkey(s): return ZcashViewKey(s,on_fail='silent')
def is_viewkey(s): return ViewKey(s,on_fail='silent')
class MMGenObject(object):
@ -111,9 +111,11 @@ class InitErrors(object):
@staticmethod
def init_fail(m,on_fail,silent=False):
if silent: m = ''
from mmgen.util import die,msg
if on_fail == 'die': die(1,m)
if silent: m = ''
if os.getenv('MMGEN_TRACEBACK'):
raise ValueError,m
elif on_fail == 'die': die(1,m)
elif on_fail == 'return':
if m: msg(m)
return None # TODO: change to False
@ -404,6 +406,16 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
d = rpc_init().validateaddress(self)
return d['iswatchonly'] and 'account' in d
class ViewKey(object):
def __new__(cls,s,on_fail='die'):
from mmgen.globalvars import g
if g.proto.name == 'zcash':
return ZcashViewKey.__new__(ZcashViewKey,s,on_fail)
elif g.proto.name == 'monero':
return MoneroViewKey.__new__(MoneroViewKey,s,on_fail)
else:
raise ValueError,'{}: protocol does not support view keys'.format(g.proto.name.capitalize())
class ZcashViewKey(CoinAddr): hex_width = 128
class SeedID(str,Hilite,InitErrors):
@ -513,11 +525,11 @@ class HexStr(str,Hilite,InitErrors):
m = "{!r}: value cannot be converted to {} (value is {})"
return cls.init_fail(m.format(s,cls.__name__,e[0]),on_fail)
class MMGenTxID(HexStr,Hilite,InitErrors):
color = 'red'
width = 6
class HexStrWithWidth(HexStr):
color = 'nocolor'
trunc_ok = False
hexcase = 'upper'
hexcase = 'lower'
width = None
def __new__(cls,s,on_fail='die'):
cls.arg_chk(cls,on_fail)
try:
@ -528,10 +540,10 @@ class MMGenTxID(HexStr,Hilite,InitErrors):
m = "{}\n{!r}: value cannot be converted to {}"
return cls.init_fail(m.format(e[0],s,cls.__name__),on_fail)
class CoinTxID(MMGenTxID):
color = 'purple'
width = 64
hexcase = 'lower'
class MMGenTxID(HexStrWithWidth): color,width,hexcase = 'red',6,'upper'
class MoneroViewKey(HexStrWithWidth): color,width,hexcase = 'cyan',64,'lower'
class WalletPassword(HexStrWithWidth): color,width,hexcase = 'blue',32,'lower'
class CoinTxID(HexStrWithWidth): color,width,hexcase = 'purple',64,'lower'
class WifKey(str,Hilite,InitErrors):
width = 53
@ -582,8 +594,9 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
w2h = g.proto.wif2hex(wif) # raises exception on error
me = str.__new__(cls,w2h['hex'])
me.compressed = w2h['compressed']
me.pubkey_type = w2h['pubkey_type']
me.wif = str.__new__(WifKey,wif) # check has been done
me.pubkey_type = w2h['pubkey_type']
me.wif = str.__new__(WifKey,wif) # check has been done
me.orig_hex = None
return me
except Exception as e:
fs = "Value {!r} cannot be converted to {} WIF key ({})"
@ -593,6 +606,7 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
assert s and type(compressed) == bool and pubkey_type,'Incorrect args for PrivKey()'
assert len(s) == cls.width / 2,'Key length must be {}'.format(cls.width/2)
me = str.__new__(cls,g.proto.preprocess_key(s.encode('hex'),pubkey_type))
me.orig_hex = s.encode('hex') # save the non-preprocessed key
me.compressed = compressed
me.pubkey_type = pubkey_type
if pubkey_type != 'password': # skip WIF creation for passwds
@ -705,6 +719,12 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
'gen_method':'zcash_z',
'addr_fmt':'zcash_z',
'desc':'Zcash z-address' },
'M': { 'name':'monero',
'pubkey_type':'monero',
'compressed':False,
'gen_method':'monero',
'addr_fmt':'monero',
'desc':'Monero address'}
}
def __new__(cls,s,on_fail='die',errmsg=None):
if type(s) == cls: return s
@ -719,7 +739,10 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
setattr(me,k,v[k])
assert me in g.proto.mmtypes + ('P',), (
"'{}': invalid address type for {}".format(me.name,g.proto.__name__))
me.has_viewkey = me.name == 'zcash_z'
me.extra_attrs = []
if me.name in ('monero','zcash_z'): me.extra_attrs += ['viewkey']
if me.name == 'monero': me.extra_attrs += ['wallet_passwd']
me.wif_label = ('wif:','spendkey:')[me.name=='monero']
return me
raise ValueError,'not found'
except Exception as e:

View file

@ -181,7 +181,7 @@ Are you sure you want to continue?
""".strip().format(g.coin,tl[trust_level],pn=g.proj_name)
if os.getenv('MMGEN_TEST_SUITE'):
msg(m); return
if not keypress_confirm(m):
if not keypress_confirm(m,default_yes=True):
sys.exit(0)
def init(opts_f,add_opts=[],opt_filter=None):
@ -191,6 +191,7 @@ def init(opts_f,add_opts=[],opt_filter=None):
# most, but not all, of these set the corresponding global var
common_opts_data = """
--, --accept-defaults Accept defaults at all prompts
--, --coin=c Choose coin unit. Default: {cu_dfl}. Options: {cu_all}
--, --color=0|1 Disable or enable color output
--, --force-256-color Force 256-color output when color is enabled

View file

@ -251,27 +251,32 @@ class LitecoinTestnetProtocol(LitecoinProtocol):
class BitcoinProtocolAddrgen(BitcoinProtocol): mmcaps = ('key','addr')
class BitcoinTestnetProtocolAddrgen(BitcoinTestnetProtocol): mmcaps = ('key','addr')
class EthereumProtocol(BitcoinProtocolAddrgen):
class DummyWIF(object):
@classmethod
def hex2wif(cls,hexpriv,pubkey_type,compressed):
n = cls.name.capitalize()
assert pubkey_type == cls.pubkey_type,'{}: invalid pubkey_type for {}!'.format(pubkey_type,n)
assert compressed == False,'{} does not support compressed pubkeys!'.format(n)
return str(hexpriv)
@classmethod
def wif2hex(cls,wif):
return { 'hex':str(wif), 'pubkey_type':cls.pubkey_type, 'compressed':False }
class EthereumProtocol(DummyWIF,BitcoinProtocolAddrgen):
addr_width = 40
mmtypes = ('E',)
dfl_mmtype = 'E'
name = 'ethereum'
base_coin = 'ETH'
@classmethod
def hex2wif(cls,hexpriv,pubkey_type,compressed):
assert compressed == False,'Ethereum does not support compressed pubkeys!'
return str(hexpriv)
@classmethod
def wif2hex(cls,wif):
return { 'hex':str(wif), 'pubkey_type':'std', 'compressed':False }
pubkey_type = 'std' # required by DummyWIF
@classmethod
def verify_addr(cls,addr,hex_width,return_dict=False):
from mmgen.util import is_hex_str_lc
if is_hex_str_lc(addr) and len(addr) == 40:
if is_hex_str_lc(addr) and len(addr) == cls.addr_width:
return { 'hex': addr, 'format': 'ethereum', 'width': cls.addr_width } if return_dict else True
if g.debug: Msg("Invalid address '{}'".format(addr))
return False
@ -324,6 +329,50 @@ class ZcashTestnetProtocol(ZcashProtocol):
'zcash_z': ('16b6','??'),
'viewkey': ('0b2a','??') }
# https://github.com/monero-project/monero/blob/master/src/cryptonote_config.h
class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen):
name = 'monero'
base_coin = 'XMR'
addr_ver_num = { 'monero': ('12','4'), 'monero_sub': ('2a','8') } # 18,42
wif_ver_num = {}
mmtypes = ('M',)
dfl_mmtype = 'M'
addr_width = 95
pubkey_type = 'monero' # required by DummyWIF
@classmethod
def preprocess_key(cls,hexpriv,pubkey_type): # reduce key
try:
from ed25519ll.djbec import l
except:
from mmgen.ed25519 import l
n = int(hexpriv.decode('hex')[::-1].encode('hex'),16) % l
return '{:064x}'.format(n).decode('hex')[::-1].encode('hex')
@classmethod
def verify_addr(cls,addr,hex_width,return_dict=False):
def b58dec(addr_str):
from mmgen.util import baseconv
dec,l = baseconv.tohex,len(addr_str)
a = ''.join([dec(addr_str[i*11:i*11+11],'b58',pad=16) for i in range(l/11)])
b = dec(addr_str[-(l%11):],'b58',pad=10)
return a + b
from mmgen.util import is_b58_str
assert is_b58_str(addr),'Not valid base-58 string'
assert len(addr) == cls.addr_width,'Incorrect width'
ret = b58dec(addr)
import sha3
chk = sha3.keccak_256(ret.decode('hex')[:-4]).hexdigest()[:8]
assert chk == ret[-8:],'Incorrect checksum'
return { 'hex': ret, 'format': 'monero', 'width': cls.addr_width } if return_dict else True
class MoneroTestnetProtocol(MoneroProtocol):
addr_ver_num = { 'monero': ('35','4'), 'monero_sub': ('3f','8') } # 53,63
class CoinProtocol(MMGenObject):
coins = {
# mainnet testnet trustlevel (None == skip)
@ -333,6 +382,7 @@ class CoinProtocol(MMGenObject):
'eth': (EthereumProtocol,EthereumTestnetProtocol,2),
'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol,2),
'zec': (ZcashProtocol,ZcashTestnetProtocol,2),
'xmr': (MoneroProtocol,MoneroTestnetProtocol,2)
}
def __new__(cls,coin,testnet):
coin = coin.lower()
@ -412,5 +462,6 @@ def make_init_genonly_altcoins_str(data):
return out
def init_coin(coin):
coin = coin.upper()
g.coin = coin
g.proto = CoinProtocol(coin,g.testnet)

View file

@ -93,7 +93,8 @@ cmd_data = OrderedDict([
('Encrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
('Decrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
('Bytespec', ['<bytespec> [str]']),
('Regtest_setup',[]),
('Keyaddrlist2monerowallet',['<{} XMR key-address file> [str]'.format(pnm),'blockheight [int=(current height)]']),
])
def usage(command):
@ -315,9 +316,9 @@ def Mn_rand256(wordlist=dfl_wl_id): do_random_mn(32,wordlist)
def Hex2mn(s,wordlist=dfl_wl_id): Msg(' '.join(baseconv.fromhex(s,wordlist)))
def Mn2hex(s,wordlist=dfl_wl_id): Msg(baseconv.tohex(s.split(),wordlist))
def Strtob58(s,pad=None): Msg(''.join(baseconv.fromhex(binascii.hexlify(s),'b58',pad)))
def Hextob58(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b58',pad)))
def Hextob32(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b32',pad)))
def Strtob58(s,pad=None): Msg(baseconv.fromhex(binascii.hexlify(s),'b58',pad,tostr=True))
def Hextob58(s,pad=None): Msg(baseconv.fromhex(s,'b58',pad,tostr=True))
def Hextob32(s,pad=None): Msg(baseconv.fromhex(s,'b32',pad,tostr=True))
def B58tostr(s): Msg(binascii.unhexlify(baseconv.tohex(s,'b58')))
def B58tohex(s,pad=None): Msg(baseconv.tohex(s,'b58',pad))
def B32tohex(s,pad=None): Msg(baseconv.tohex(s.upper(),'b32',pad))
@ -474,12 +475,102 @@ def Rand2file(outfile,nbytes,threads=4,silent=False):
def Bytespec(s): Msg(str(parse_nbytes(s)))
def Regtest_setup():
print 'ok'
return
import subprocess as sp
sp.check_output()
pass
def Keyaddrlist2monerowallet(infile,blockheight=None):
import pexpect
if blockheight != None and int(blockheight) < 0: blockheight = 0
def run_cmd(cmd):
import subprocess as sp
p = sp.Popen(cmd,stdin=sp.PIPE,stdout=sp.PIPE,stderr=sp.PIPE)
return p
def test_rpc():
p = run_cmd(['monero-wallet-cli','--version'])
if p.stdout.read()[:6] != 'Monero':
die(1,"Unable to run 'monero-wallet-cli'!")
p = run_cmd(['monerod','status'])
ret = p.stdout.read()
if ret[:7] != 'Height:':
die(1,'Unable to connect to monerod!')
return int(ret[8:].split('/')[0])
cur_height = test_rpc()
from mmgen.protocol import init_coin
init_coin('xmr')
from mmgen.addr import AddrList
al = KeyAddrList(infile)
sid = al.al_id.sid
os.environ['LANG'] = 'C'
def my_expect(p,m,s,regex=False):
if m: msg_r(' {}...'.format(m))
ret = (p.expect_exact,p.expect)[regex](s)
if not (ret == 0 or (type(s) == list and ret in (0,1))):
die(2,"Expect failed: '{}' (return value: {})".format(s,ret))
if m: msg('OK')
return ret
def my_sendline(p,m,s,usr_ret):
if m: msg_r(' {}...'.format(m))
ret = p.sendline(s)
if ret != usr_ret:
die(2,"Unable to send line '{}' (return value {})".format(s,ret))
if m: msg('OK')
vmsg("sendline: '{}' => {}".format(s,ret))
def create():
gmsg('\nCreating {} wallet{}'.format(dl,suf(dl)))
for n,d in enumerate(al.data):
fn = '{}{}-{}-MoneroWallet'.format(('',opt.outdir+'/')[bool(opt.outdir)],sid,d.idx)
gmsg("\nGenerating wallet {}/{} ({})".format(n+1,dl,fn))
try: os.stat(fn)
except: pass
else: die(1,"Wallet '{}' already exists!".format(fn))
# pmsg([d.sec,d.wallet_passwd,d.viewkey,d.addr,fn])
p = pexpect.spawn('monero-wallet-cli --generate-from-spend-key {}'.format(fn))
my_expect(p,'Awaiting initial prompt','Secret spend key: ')
my_sendline(p,'',d.sec,65)
my_expect(p,'','Enter new wallet password: ')
my_sendline(p,'Sending password',d.wallet_passwd,33)
my_expect(p,'','Confirm password: ')
my_sendline(p,'Sending password again',d.wallet_passwd,33)
my_expect(p,'','of your choice: ')
my_sendline(p,'','1',2)
my_expect(p,'monerod generating wallet','Generated new wallet: ')
my_expect(p,'','\n')
if d.addr not in p.before:
die(3,'Addresses do not match!\n MMGen: {}\n Monero: {}'.format(d.addr,p.before))
my_expect(p,'','View key: ')
my_expect(p,'','\n')
if d.viewkey not in p.before:
die(3,'View keys do not match!\n MMGen: {}\n Monero: {}'.format(d.viewkey,p.before))
my_expect(p,'','(YYYY-MM-DD): ')
h = str(blockheight or cur_height-1)
my_sendline(p,'',h,len(h)+1)
ret = my_expect(p,'',['Starting refresh','Still apply restore height? (Y/Yes/N/No): '])
if ret == 1:
my_sendline(p,'','Y',2)
m = ' Warning: {}: blockheight argument is higher than current blockheight'
ymsg(m.format(blockheight))
elif blockheight != None:
p.logfile = sys.stderr
my_expect(p,'Syncing wallet','\[wallet.*$',regex=True)
p.logfile = None
my_sendline(p,'Exiting','exit',5)
p.read()
dl = len(al.data)
try:
create()
gmsg('\n{} wallet{} created'.format(dl,suf(dl)))
except KeyboardInterrupt:
rdie(1,'\nUser interrupt\n')
except EOFError:
rdie(2,'\nEnd of file\n')
except Exception as e:
rdie(1,'Program died: {!r}'.format(e))
# ================ RPC commands ================== #

View file

@ -736,7 +736,8 @@ class MMGenTX(MMGenObject):
if self.is_in_mempool():
if status:
d = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
r = '{}replaceable'.format(('NOT ','')[d['bip125-replaceable']=='yes'])
brs = 'bip125-replaceable'
r = '{}replaceable'.format(('NOT ','')[brs in d and d[brs]=='yes'])
t = d['timereceived']
m = 'Sent {} ({} h/m/s ago)'
b = m.format(time.strftime('%c',time.gmtime(t)),secs_to_dhms(int(time.time()-t)))

View file

@ -269,7 +269,7 @@ class baseconv(object):
@classmethod
def b58encode(cls,s,pad=None):
pad = cls.get_pad(s,pad,'en',cls.b58pad_lens,[bytes])
return ''.join(cls.fromhex(hexlify(s),'b58',pad=pad))
return cls.fromhex(hexlify(s),'b58',pad=pad,tostr=True)
@classmethod
def b58decode(cls,s,pad=None):
@ -326,7 +326,7 @@ class baseconv(object):
return ('','0')[len(ret) % 2] + ret
@classmethod
def fromhex(cls,hexnum,wl_id,pad=None):
def fromhex(cls,hexnum,wl_id,pad=None,tostr=False):
hexnum = hexnum.strip()
if not is_hex_str(hexnum):
@ -338,7 +338,8 @@ class baseconv(object):
while num:
ret.append(num % base)
num /= base
return [wl[n] for n in [0] * ((pad or 0)-len(ret)) + ret[::-1]]
o = [wl[n] for n in [0] * ((pad or 0)-len(ret)) + ret[::-1]]
return ''.join(o) if tostr else o
baseconv.check_wordlists()
@ -715,6 +716,10 @@ def keypress_confirm(prompt,default_yes=False,verbose=False,no_nl=False):
p = '{} {}: '.format(prompt,q)
nl = ('\n','\r{}\r'.format(' '*len(p)))[no_nl]
if opt.accept_defaults:
msg(p)
return (False,True)[default_yes]
while True:
reply = get_char(p).strip('\n\r')
if not reply:

View file

@ -114,6 +114,7 @@ setup(
'mmgen.color',
'mmgen.common',
'mmgen.crypto',
'mmgen.ed25519',
'mmgen.filename',
'mmgen.globalvars',
'mmgen.license',

View file

@ -0,0 +1,19 @@
# MMGen address file
#
# This file is editable.
# Everything following a hash symbol '#' is a comment and ignored by MMGen.
# A text label of 32 characters 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.
# Address data checksum for 98831F3A-XMR-M[1,31-33,500-501,1010-1011]: 4369 0253 AC2C 0E38
# Record this value to a secure location.
98831F3A XMR:MONERO {
1 41tmwZd2CdXEGtWqGY9fH9FVtQM8VxZASYPQ3VJQhFjtGWYzQFuidD21vJYTi2yy3tXRYXTNXBTaYVLav62rwUUpFFyicZU
31 42R44dGVXu6gspyuWKNieJHmxzpnLxpkVbncKrFs5XniBEifRVYxPpeKJNgGPQkikX8AqjWL1Ysnf5Yc8DALiD6939PJAsb
32 44mhAiVtVuD5yGbT15BG3M1WmFD5R5dZfbwTfmfVDfneRT9keAcUwgGRvtx32WifQiECKVD1zESYaWqwJf5yh5w71WM6BEE
33 47UrLDNHrbiF5Ea6jvpPxcRYfW9ZkPRevPJXfWUmtEApb8p5xvp49d5iWQifspQzDzcBSbroBbbMFFJ6TYNcxmrtFviJ84L
500 428YDXihJ8d8AMSnqtiHbfGt7fA6FsMUEJUCidSB7wN4RjGsMGzC5dBgaKQojevhaxKTZacSmF3xe7wubu9zyZ5b8S79pHG
501 46nYucqe3BCMoASekNxvVT3BMnKuHsr9A8CwPZ6eAwVSJ9v8am5c6zR8St1Wcg2PPdJ6oL6zDM8zVChF15dZhGcgNEwLg2T
1010 43tWyaYaMAt5ZaK5n3SVbSJJigpBnWrVXV3iBPJ5PX7UgvPRnzMw6dLXnrogRu1LzAe1aZrYJFHPBWVw1c7CBYMvLMVHf99
1011 478e1Nnt14zYhinwFCrr9bFjTc7uLNfudCcvGHsYrBnN1Q4xuGUdwubCEScTYzRzs4113ndcFBJSK1xfkPvUNYFB55i5kqV
}

View file

@ -63,12 +63,13 @@ test_data = OrderedDict([
('ltc', ('b11f16632e63ba92','ltc:legacy', '-LTC','LTC', 'LMxB474SVfxeYdqxNrM1WZDZMnifteSMv1')),
('ltc_compressed',('7ccf465d466ee7d3','ltc:compressed', '-LTC-C','LTC:COMPRESSED', 'LdkebBKVXSs6NNoPJWGM8KciDnL8LhXXjb')),
('ltc_segwit', ('9460f5ba15e82768','ltc:segwit', '-LTC-S','LTC:SEGWIT', 'MQrY3vEbqKMBgegXrSaR93R2HoTDE5bKrY')),
('eth', ('213ed116869b19f2','eth', '-ETH', 'ETH','e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35')),
('etc', ('909def37096f5ab8','etc', '-ETC', 'ETC','1a6acbef8c38f52f20d04ecded2992b04d8608d7')),
('dash', ('1319d347b021f952','dash:legacy', '-DASH','DASH','XoK491fppGNZQUUS9uEFkT6L9u8xxVFJNJ')),
('zec', ('0bf9b5b20af7b5a0','zec:legacy', '-ZEC', 'ZEC', 't1URz8BHxV38v3gsaN6oHQNKC16s35R9WkY')),
('zec_zcash_z', ('b15570d033df9b1a','zec:zcash_z', '-ZEC-Z','ZEC:ZCASH_Z','zcLMMsnzfFYZWU4avRBnuc83yh4jTtJXbtP32uWrs3ickzu1krMU4ppZCQPTwwfE9hLnRuFDSYF8VFW13aT9eeQK8aov3Ge')),
('emc', ('7e1a29853d2db875','emc:legacy', '-EMC', 'EMC','EU4L6x2b5QUb2gRQsBAAuB8TuPEwUxCNZU')),
('eth', ('213ed116869b19f2','eth', '-ETH', 'ETH', 'e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35')),
('etc', ('909def37096f5ab8','etc', '-ETC', 'ETC', '1a6acbef8c38f52f20d04ecded2992b04d8608d7')),
('dash', ('1319d347b021f952','dash:legacy', '-DASH', 'DASH','XoK491fppGNZQUUS9uEFkT6L9u8xxVFJNJ')),
('emc', ('7e1a29853d2db875','emc:legacy', '-EMC', 'EMC', 'EU4L6x2b5QUb2gRQsBAAuB8TuPEwUxCNZU')),
('zec', ('0bf9b5b20af7b5a0','zec:legacy', '-ZEC', 'ZEC', 't1URz8BHxV38v3gsaN6oHQNKC16s35R9WkY')),
('zec_zcash_z', ('b15570d033df9b1a','zec:zcash_z', '-ZEC-Z','ZEC:ZCASH_Z','zcLMMsnzfFYZWU4avRBnuc83yh4jTtJXbtP32uWrs3ickzu1krMU4ppZCQPTwwfE9hLnRuFDSYF8VFW13aT9eeQK8aov3Ge')),
('xmr', ('c76af3b088da3364','xmr:monero', '-XMR-M','XMR:MONERO','41tmwZd2CdXEGtWqGY9fH9FVtQM8VxZASYPQ3VJQhFjtGWYzQFuidD21vJYTi2yy3tXRYXTNXBTaYVLav62rwUUpFFyicZU')),
])
def run_tests():

View file

@ -327,6 +327,7 @@ cfgs = {
},
'passfile_chk': 'EB29 DC4F 924B 289F',
'passfile32_chk': '37B6 C218 2ABC 7508',
'passfilehex_chk': '523A F547 0E69 8323',
'wpasswd': 'reference password',
'ref_wallet': 'FE3C6545-D782B529[128,1].mmdat',
'ic_wallet': 'FE3C6545-E29303EA-5E229E30[128,1].mmincog',
@ -378,6 +379,7 @@ cfgs = {
},
'passfile_chk': 'ADEA 0083 094D 489A',
'passfile32_chk': '2A28 C5C7 36EC 217A',
'passfilehex_chk': 'B11C AC6A 1464 608D',
'wpasswd': 'reference password',
'ref_wallet': '1378FC64-6F0F9BB4[192,1].mmdat',
'ic_wallet': '1378FC64-2907DE97-F980D21F[192,1].mmincog',
@ -429,6 +431,7 @@ cfgs = {
},
'passfile_chk': '2D6D 8FBA 422E 1315',
'passfile32_chk': 'F6C1 CDFB 97D9 FCAE',
'passfilehex_chk': 'BD4F A0AC 8628 4BE4',
'wpasswd': 'reference password',
'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]),
'ref_addrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs',
@ -449,11 +452,13 @@ cfgs = {
},
'ref_addrfile_chksum_zec': '903E 7225 DD86 6E01',
'ref_addrfile_chksum_zec_z': '9C7A 72DC 3D4A B3AF',
'ref_addrfile_chksum_xmr': '4369 0253 AC2C 0E38',
'ref_addrfile_chksum_dash':'FBC1 6B6A 0988 4403',
'ref_addrfile_chksum_eth': 'E554 076E 7AF6 66A3',
'ref_addrfile_chksum_etc': 'E97A D796 B495 E8BC',
'ref_keyaddrfile_chksum_zec': 'F05A 5A5C 0C8E 2617',
'ref_keyaddrfile_chksum_zec_z': '220F 5F23 CC9B EC1F',
'ref_keyaddrfile_chksum_zec_z': '4ADB 5AA4 4590 B60A',
'ref_keyaddrfile_chksum_xmr': 'E0D7 9612 3D67 404A',
'ref_keyaddrfile_chksum_dash': 'E83D 2C63 FEA2 4142',
'ref_keyaddrfile_chksum_eth': '3635 4DCF B752 8772',
'ref_keyaddrfile_chksum_etc': '9BAC 38E7 5C8E 42E0',
@ -602,6 +607,7 @@ cmd_group['ref'] = (
('refkeyaddrgen_compressed', (['mmdat',pwfile],'new refwallet key-addr chksum (compressed)')),
('refpasswdgen', (['mmdat',pwfile],'new refwallet passwd file chksum')),
('ref_b32passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
('ref_hexpasswdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
)
# misc. saved reference data
@ -715,15 +721,17 @@ cmd_group['altcoin_ref'] = (
('ref_addrfile_chk_etc', 'reference address file (ETC)'),
('ref_addrfile_chk_dash','reference address file (DASH)'),
('ref_addrfile_chk_zec', 'reference address file (ZEC-T)'),
('ref_addrfile_chk_xmr', 'reference address file (XMR)'),
('ref_addrfile_chk_zec_z','reference address file (ZEC-Z)'),
('ref_keyaddrfile_chk_eth', 'reference key-address file (ETH)'),
('ref_keyaddrfile_chk_etc', 'reference key-address file (ETC)'),
('ref_keyaddrfile_chk_dash','reference key-address file (DASH)'),
('ref_keyaddrfile_chk_zec', 'reference key-address file (ZEC-T)'),
('ref_keyaddrfile_chk_zec_z','reference key-address file (ZEC-Z)'),
('ref_keyaddrfile_chk_xmr', 'reference key-address file (XMR)'),
)
# undocumented admin cmds
# undocumented admin cmds - precede with 'admin'
cmd_group_admin = OrderedDict()
cmd_group_admin['create_ref_tx'] = (
('ref_tx_setup', 'regtest (Bob and Alice) mode setup'),
@ -1428,8 +1436,9 @@ class MMGenTestSuite(object):
chk = t.expect_getend(r'Checksum for {} data .*?: '.format(desc),regex=True)
if check_ref:
k = 'passfile32_chk' if ftype == 'pass32' \
else 'passfile_chk' if ftype == 'pass' \
else '{}file{}_chk'.format(ftype,'_'+mmtype if mmtype else '')
else 'passfilehex_chk' if ftype == 'passhex' \
else 'passfile_chk' if ftype == 'pass' \
else '{}file{}_chk'.format(ftype,'_'+mmtype if mmtype else '')
chk_ref = cfg[k] if ftype[:4] == 'pass' else cfg[k][fork][g.testnet]
refcheck('address data checksum',chk,chk_ref)
return
@ -1739,6 +1748,10 @@ class MMGenTestSuite(object):
ea = ['--base32','--passwd-len','17']
self.addrgen(name,wf,pf,check_ref=True,ftype='pass32',id_str='фубар@crypto.org',extra_args=ea)
def ref_hexpasswdgen(self,name,wf,pf):
ea = ['--hex']
self.addrgen(name,wf,pf,check_ref=True,ftype='passhex',id_str='фубар@crypto.org',extra_args=ea)
def txsign_keyaddr(self,name,keyaddr_file,txfile):
t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],'-M',keyaddr_file,txfile])
t.license()
@ -2060,6 +2073,9 @@ class MMGenTestSuite(object):
self.ref_addrfile_chk(name,ftype='addr',coin='ZEC',subdir='zcash',pfx='-ZEC-Z',
mmtype='z',add_args=['mmtype=zcash_z'])
def ref_addrfile_chk_xmr(self,name):
self.ref_addrfile_chk(name,ftype='addr',coin='XMR',subdir='monero',pfx='-XMR-M')
def ref_addrfile_chk_dash(self,name):
self.ref_addrfile_chk(name,ftype='addr',coin='DASH',subdir='dash',pfx='-DASH-C')
@ -2076,6 +2092,9 @@ class MMGenTestSuite(object):
self.ref_addrfile_chk(name,ftype='keyaddr',coin='ZEC',subdir='zcash',pfx='-ZEC-Z',
mmtype='z',add_args=['mmtype=zcash_z'])
def ref_keyaddrfile_chk_xmr(self,name):
self.ref_addrfile_chk(name,ftype='keyaddr',coin='XMR',subdir='monero',pfx='-XMR-M')
def ref_keyaddrfile_chk_dash(self,name):
self.ref_addrfile_chk(name,ftype='keyaddr',coin='DASH',subdir='dash',pfx='-DASH-C')
@ -2715,7 +2734,8 @@ class MMGenTestSuite(object):
'refaddrgen_compressed',
'refkeyaddrgen_compressed',
'refpasswdgen',
'ref_b32passwdgen'
'ref_b32passwdgen',
'ref_hexpasswdgen'
):
for i in ('1','2','3'):
locals()[k+i] = locals()[k]