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/*.py
include test/ref/* include test/ref/*
include test/ref/litecoin/* include test/ref/litecoin/*
include test/ref/zcash/*
include test/ref/dash/*
include test/ref/ethereum/* include test/ref/ethereum/*
include test/ref/ethereum_classic/* include test/ref/ethereum_classic/*
include test/ref/dash/*
include test/ref/zcash/*
include test/ref/monero/*
include scripts/bitcoind-walletunlock.py include scripts/bitcoind-walletunlock.py
include scripts/compute-file-chksum.py include scripts/compute-file-chksum.py

View file

@ -40,15 +40,16 @@ class AddrGenerator(MMGenObject):
gen_method = addr_type.gen_method gen_method = addr_type.gen_method
else: else:
raise TypeError,'{}: incorrect argument type for {}()'.format(type(addr_type),cls.__name__) raise TypeError,'{}: incorrect argument type for {}()'.format(type(addr_type),cls.__name__)
d = { gen_methods = {
'p2pkh': AddrGeneratorP2PKH, 'p2pkh': AddrGeneratorP2PKH,
'segwit': AddrGeneratorSegwit, 'segwit': AddrGeneratorSegwit,
'ethereum': AddrGeneratorEthereum, 'ethereum': AddrGeneratorEthereum,
'zcash_z': AddrGeneratorZcashZ 'zcash_z': AddrGeneratorZcashZ,
'monero': AddrGeneratorMonero
} }
assert gen_method in d assert gen_method in gen_methods
me = super(cls,cls).__new__(d[gen_method]) me = super(cls,cls).__new__(gen_methods[gen_method])
me.desc = d me.desc = gen_methods
return me return me
class AddrGeneratorP2PKH(AddrGenerator): class AddrGeneratorP2PKH(AddrGenerator):
@ -113,6 +114,57 @@ class AddrGeneratorZcashZ(AddrGenerator):
def to_segwit_redeem_script(self,pubhex): def to_segwit_redeem_script(self,pubhex):
raise NotImplementedError,'Zcash z-addresses incompatible with Segwit' 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): class KeyGenerator(MMGenObject):
def __new__(cls,addr_type,generator=None,silent=False): def __new__(cls,addr_type,generator=None,silent=False):
@ -130,7 +182,7 @@ class KeyGenerator(MMGenObject):
else: else:
msg('Using (slow) native Python ECDSA library for address generation') msg('Using (slow) native Python ECDSA library for address generation')
return super(cls,cls).__new__(KeyGeneratorPython) return super(cls,cls).__new__(KeyGeneratorPython)
elif pubkey_type == 'zcash_z': elif pubkey_type in ('zcash_z','monero'):
g.proto.addr_width = 95 g.proto.addr_width = 95
me = super(cls,cls).__new__(KeyGeneratorDummy) me = super(cls,cls).__new__(KeyGeneratorDummy)
me.desc = 'mmgen-'+pubkey_type me.desc = 'mmgen-'+pubkey_type
@ -198,10 +250,11 @@ class KeyGeneratorDummy(KeyGenerator):
class AddrListEntry(MMGenListItem): class AddrListEntry(MMGenListItem):
addr = MMGenListItemAttr('addr','CoinAddr') addr = MMGenListItemAttr('addr','CoinAddr')
viewkey = MMGenListItemAttr('viewkey','ZcashViewKey')
idx = MMGenListItemAttr('idx','AddrIdx') # not present in flat addrlists idx = MMGenListItemAttr('idx','AddrIdx') # not present in flat addrlists
label = MMGenListItemAttr('label','TwComment',reassign_ok=True) label = MMGenListItemAttr('label','TwComment',reassign_ok=True)
sec = MMGenListItemAttr('sec',PrivKey,typeconv=False) sec = MMGenListItemAttr('sec',PrivKey,typeconv=False)
viewkey = MMGenListItemAttr('viewkey','ViewKey')
wallet_passwd = MMGenListItemAttr('wallet_passwd','WalletPassword')
class PasswordListEntry(MMGenListItem): class PasswordListEntry(MMGenListItem):
passwd = MMGenImmutableAttr('passwd',unicode,typeconv=False) # TODO: create Password type passwd = MMGenImmutableAttr('passwd',unicode,typeconv=False) # TODO: create Password type
@ -214,7 +267,11 @@ class AddrListChksum(str,Hilite):
trunc_ok = False trunc_ok = False
def __new__(cls,addrlist): 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)) return str.__new__(cls,make_chksum_N(' '.join(lines), nchars=16, sep=True))
class AddrListIDStr(unicode,Hilite): 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 compressed = self.al_id.mmtype.compressed
pubkey_type = self.al_id.mmtype.pubkey_type 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: if self.gen_addrs:
kg = KeyGenerator(self.al_id.mmtype) 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: if self.gen_addrs:
ph = kg.to_pubhex(e.sec) ph = kg.to_pubhex(e.sec)
e.addr = ag.to_addr(ph) e.addr = ag.to_addr(ph)
if has_viewkey: if gen_viewkey:
e.viewkey = ag.to_viewkey(ph) e.viewkey = ag.to_viewkey(ph)
if gen_wallet_passwd:
e.wallet_passwd = ag.to_wallet_passwd(ph)
if type(self) == PasswordList: if type(self) == PasswordList:
e.passwd = unicode(self.make_passwd(e.sec)) # TODO - own type 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: for e in self.data:
c = ' '+e.label if enable_comments and e.label else '' c = ' '+e.label if enable_comments and e.label else ''
if type(self) == KeyList: 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: elif type(self) == PasswordList:
out.append(fs.format(e.idx,e.passwd,c)) out.append(fs.format(e.idx,e.passwd,c))
else: # First line with idx else: # First line with idx
out.append(fs.format(e.idx,e.addr,c)) out.append(fs.format(e.idx,e.addr,c))
if self.has_keys: if self.has_keys:
if self.al_id.mmtype.has_viewkey: if opt.b16: out.append(fs.format('', 'orig_hex: '+e.sec.orig_hex,c))
out.append(fs.format('','view: '+e.viewkey,c)) out.append(fs.format('','{} {}'.format(self.al_id.mmtype.wif_label,e.sec.wif),c))
if opt.b16: out.append(fs.format('', 'hex: '+e.sec,c)) for k in ('viewkey','wallet_passwd'):
out.append(fs.format('','wif: '+e.sec.wif,c)) v = getattr(e,k)
if v: out.append(fs.format('','{}: {}'.format(k,v),c))
out.append('}') out.append('}')
self.fmt_data = '\n'.join([l.rstrip() for l in out]) + '\n' 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() ret = AddrListList()
le = self.entry_type le = self.entry_type
while lines: def get_line():
l = lines.pop(0) ret = lines.pop(0).split(None,2)
d = l.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) assert self.check_format(d[1]),"'{}': invalid {}".format(d[1],self.data_desc)
if len(d) != 3: d.append('') if len(d) != 3: d.append('')
a = le(**{'idx':int(d[0]),self.main_attr:d[1],'label':d[2]}) a = le(**{'idx':int(d[0]),self.main_attr:d[1],'label':d[2]})
if self.has_keys: if self.has_keys: # order: wif,(orig_hex),viewkey,wallet_passwd
if self.al_id.mmtype.has_viewkey: d = get_line()
d = lines.pop(0).split(None,2) assert d[0] == self.al_id.mmtype.wif_label,"Invalid line in file: '{}'".format(' '.join(d))
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))
a.sec = PrivKey(wif=d[1]) 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) 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: if not al_mmtype:
mmtype = MMGenAddrType('E' if al_coin in ('ETH','ETC') else 'L',on_fail='raise') mmtype = MMGenAddrType('E' if al_coin in ('ETH','ETC') else 'L',on_fail='raise')
else: else:
try:
mmtype = MMGenAddrType(al_mmtype,on_fail='raise') 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()])))
from mmgen.protocol import CoinProtocol from mmgen.protocol import CoinProtocol
base_coin = CoinProtocol(al_coin or 'BTC',testnet=False).base_coin 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_fmt = None
pw_info = { pw_info = {
'b58': { 'min_len': 8 , 'max_len': 36 ,'dfl_len': 20, 'desc': 'base-58 password' }, '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) 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): def make_passwd(self,hex_sec):
assert self.pw_fmt in self.pw_info assert self.pw_fmt in self.pw_info
if self.pw_fmt == 'hex':
return hex_sec
else:
# we take least significant part # we take least significant part
return ''.join(baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len))[-self.pw_len:] return baseconv.fromhex(hex_sec,self.pw_fmt,pad=self.pw_len,tostr=True)[-self.pw_len:]
def check_format(self,pw): 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)) msg('Password is not a valid {} string'.format(self.pw_fmt))
return False return False
if len(pw) != self.pw_len: 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_name = 'MMGen'
proj_url = 'https://github.com/mmgen/mmgen' proj_url = 'https://github.com/mmgen/mmgen'
prog_name = os.path.basename(sys.argv[0]) prog_name = os.path.basename(sys.argv[0])
author = 'Philemon' author = 'The MMGen Project'
email = '<mmgen@tuta.io>' email = '<mmgen@tuta.io>'
Cdates = '2013-2017' 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' 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 force_256_color = False
testnet = False testnet = False
regtest = False regtest = False
accept_defaults = False
chain = None # set by first call to rpc_init() chain = None # set by first call to rpc_init()
chains = 'mainnet','testnet','regtest' chains = 'mainnet','testnet','regtest'
daemon_version = None # set by first call to rpc_init() daemon_version = None # set by first call to rpc_init()
@ -108,7 +109,8 @@ class g(object):
# User opt sets global var: # User opt sets global var:
common_opts = ( common_opts = (
'color','no_license','rpc_host','rpc_port','testnet','rpc_user','rpc_password', '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 = ( required_opts = (
'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout', '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' 'brain_params','b16','usr_randchars','coin','bob','alice','key_generator'
) )
incompatible_opts = ( incompatible_opts = (
('base32','hex'), # mmgen-passgen
('bob','alice'), ('bob','alice'),
('quiet','verbose'), ('quiet','verbose'),
('label','keep_label'), ('label','keep_label'),

View file

@ -57,5 +57,5 @@ def launch(what):
else: else:
try: m = u'{}\n'.format(e[0]) try: m = u'{}\n'.format(e[0])
except: m = u'{!r}\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) sys.exit(2)

View file

@ -29,7 +29,8 @@ from mmgen.obj import MMGenPWIDString
dfl_len = { dfl_len = {
'b58': PasswordList.pw_info['b58']['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: { opts_data = lambda: {
@ -41,6 +42,7 @@ opts_data = lambda: {
-h, --help Print this help message -h, --help Print this help message
--, --longhelp Print help message for long options (common options) --, --longhelp Print help message for long options (common options)
-b, --base32 Generate passwords in Base32 format instead of Base58 -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 -d, --outdir= d Output files to directory 'd' instead of working dir
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry -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) -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) 'f' at offset 'o' (comma-separated)
-O, --old-incog-fmt Specify old-format incognito input -O, --old-incog-fmt Specify old-format incognito input
-L, --passwd-len= l Specify length of generated passwords -L, --passwd-len= l Specify length of generated passwords
(default: {d58} chars [base58], {d32} chars [base32]). (default: {d58} chars [base58], {d32} chars [base32],
An argument of 'h' will generate passwords of half {dhex} chars [hex]). An argument of 'h' will generate
the default length. passwords of half the default length.
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option -l, --seed-len= l Specify wallet seed length of 'l' bits. This option
is required only for brainwallet and incognito inputs is required only for brainwallet and incognito inputs
with non-standard (< {g.seed_len}-bit) seed lengths with non-standard (< {g.seed_len}-bit) seed lengths
@ -65,7 +67,7 @@ opts_data = lambda: {
-v, --verbose Produce more verbose output -v, --verbose Produce more verbose output
""".format( """.format(
seed_lens=', '.join([str(i) for i in g.seed_lens]), 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)]) kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)])
), ),
'notes': """ 'notes': """
@ -124,7 +126,7 @@ pw_id_str = cmd_args.pop()
sf = get_seed_file(cmd_args,1) 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')] 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 obj.py: MMGen native classes
""" """
import sys import sys,os
from decimal import * from decimal import *
from mmgen.color import * from mmgen.color import *
from string import hexdigits,ascii_letters,digits 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_addrlist_id(s): return AddrListID(s,on_fail='silent')
def is_tw_label(s): return TwLabel(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_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): class MMGenObject(object):
@ -111,9 +111,11 @@ class InitErrors(object):
@staticmethod @staticmethod
def init_fail(m,on_fail,silent=False): def init_fail(m,on_fail,silent=False):
if silent: m = ''
from mmgen.util import die,msg 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': elif on_fail == 'return':
if m: msg(m) if m: msg(m)
return None # TODO: change to False return None # TODO: change to False
@ -404,6 +406,16 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
d = rpc_init().validateaddress(self) d = rpc_init().validateaddress(self)
return d['iswatchonly'] and 'account' in d 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 ZcashViewKey(CoinAddr): hex_width = 128
class SeedID(str,Hilite,InitErrors): class SeedID(str,Hilite,InitErrors):
@ -513,11 +525,11 @@ class HexStr(str,Hilite,InitErrors):
m = "{!r}: value cannot be converted to {} (value is {})" m = "{!r}: value cannot be converted to {} (value is {})"
return cls.init_fail(m.format(s,cls.__name__,e[0]),on_fail) return cls.init_fail(m.format(s,cls.__name__,e[0]),on_fail)
class MMGenTxID(HexStr,Hilite,InitErrors): class HexStrWithWidth(HexStr):
color = 'red' color = 'nocolor'
width = 6
trunc_ok = False trunc_ok = False
hexcase = 'upper' hexcase = 'lower'
width = None
def __new__(cls,s,on_fail='die'): def __new__(cls,s,on_fail='die'):
cls.arg_chk(cls,on_fail) cls.arg_chk(cls,on_fail)
try: try:
@ -528,10 +540,10 @@ class MMGenTxID(HexStr,Hilite,InitErrors):
m = "{}\n{!r}: value cannot be converted to {}" m = "{}\n{!r}: value cannot be converted to {}"
return cls.init_fail(m.format(e[0],s,cls.__name__),on_fail) return cls.init_fail(m.format(e[0],s,cls.__name__),on_fail)
class CoinTxID(MMGenTxID): class MMGenTxID(HexStrWithWidth): color,width,hexcase = 'red',6,'upper'
color = 'purple' class MoneroViewKey(HexStrWithWidth): color,width,hexcase = 'cyan',64,'lower'
width = 64 class WalletPassword(HexStrWithWidth): color,width,hexcase = 'blue',32,'lower'
hexcase = 'lower' class CoinTxID(HexStrWithWidth): color,width,hexcase = 'purple',64,'lower'
class WifKey(str,Hilite,InitErrors): class WifKey(str,Hilite,InitErrors):
width = 53 width = 53
@ -584,6 +596,7 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
me.compressed = w2h['compressed'] me.compressed = w2h['compressed']
me.pubkey_type = w2h['pubkey_type'] me.pubkey_type = w2h['pubkey_type']
me.wif = str.__new__(WifKey,wif) # check has been done me.wif = str.__new__(WifKey,wif) # check has been done
me.orig_hex = None
return me return me
except Exception as e: except Exception as e:
fs = "Value {!r} cannot be converted to {} WIF key ({})" 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 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) 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 = 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.compressed = compressed
me.pubkey_type = pubkey_type me.pubkey_type = pubkey_type
if pubkey_type != 'password': # skip WIF creation for passwds if pubkey_type != 'password': # skip WIF creation for passwds
@ -705,6 +719,12 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
'gen_method':'zcash_z', 'gen_method':'zcash_z',
'addr_fmt':'zcash_z', 'addr_fmt':'zcash_z',
'desc':'Zcash z-address' }, '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): def __new__(cls,s,on_fail='die',errmsg=None):
if type(s) == cls: return s if type(s) == cls: return s
@ -719,7 +739,10 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
setattr(me,k,v[k]) setattr(me,k,v[k])
assert me in g.proto.mmtypes + ('P',), ( assert me in g.proto.mmtypes + ('P',), (
"'{}': invalid address type for {}".format(me.name,g.proto.__name__)) "'{}': 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 return me
raise ValueError,'not found' raise ValueError,'not found'
except Exception as e: 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) """.strip().format(g.coin,tl[trust_level],pn=g.proj_name)
if os.getenv('MMGEN_TEST_SUITE'): if os.getenv('MMGEN_TEST_SUITE'):
msg(m); return msg(m); return
if not keypress_confirm(m): if not keypress_confirm(m,default_yes=True):
sys.exit(0) sys.exit(0)
def init(opts_f,add_opts=[],opt_filter=None): 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 # most, but not all, of these set the corresponding global var
common_opts_data = """ common_opts_data = """
--, --accept-defaults Accept defaults at all prompts
--, --coin=c Choose coin unit. Default: {cu_dfl}. Options: {cu_all} --, --coin=c Choose coin unit. Default: {cu_dfl}. Options: {cu_all}
--, --color=0|1 Disable or enable color output --, --color=0|1 Disable or enable color output
--, --force-256-color Force 256-color output when color is enabled --, --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 BitcoinProtocolAddrgen(BitcoinProtocol): mmcaps = ('key','addr')
class BitcoinTestnetProtocolAddrgen(BitcoinTestnetProtocol): 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 addr_width = 40
mmtypes = ('E',) mmtypes = ('E',)
dfl_mmtype = 'E' dfl_mmtype = 'E'
name = 'ethereum' name = 'ethereum'
base_coin = 'ETH' base_coin = 'ETH'
pubkey_type = 'std' # required by DummyWIF
@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 }
@classmethod @classmethod
def verify_addr(cls,addr,hex_width,return_dict=False): def verify_addr(cls,addr,hex_width,return_dict=False):
from mmgen.util import is_hex_str_lc 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 return { 'hex': addr, 'format': 'ethereum', 'width': cls.addr_width } if return_dict else True
if g.debug: Msg("Invalid address '{}'".format(addr)) if g.debug: Msg("Invalid address '{}'".format(addr))
return False return False
@ -324,6 +329,50 @@ class ZcashTestnetProtocol(ZcashProtocol):
'zcash_z': ('16b6','??'), 'zcash_z': ('16b6','??'),
'viewkey': ('0b2a','??') } '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): class CoinProtocol(MMGenObject):
coins = { coins = {
# mainnet testnet trustlevel (None == skip) # mainnet testnet trustlevel (None == skip)
@ -333,6 +382,7 @@ class CoinProtocol(MMGenObject):
'eth': (EthereumProtocol,EthereumTestnetProtocol,2), 'eth': (EthereumProtocol,EthereumTestnetProtocol,2),
'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol,2), 'etc': (EthereumClassicProtocol,EthereumClassicTestnetProtocol,2),
'zec': (ZcashProtocol,ZcashTestnetProtocol,2), 'zec': (ZcashProtocol,ZcashTestnetProtocol,2),
'xmr': (MoneroProtocol,MoneroTestnetProtocol,2)
} }
def __new__(cls,coin,testnet): def __new__(cls,coin,testnet):
coin = coin.lower() coin = coin.lower()
@ -412,5 +462,6 @@ def make_init_genonly_altcoins_str(data):
return out return out
def init_coin(coin): def init_coin(coin):
coin = coin.upper()
g.coin = coin g.coin = coin
g.proto = CoinProtocol(coin,g.testnet) g.proto = CoinProtocol(coin,g.testnet)

View file

@ -93,7 +93,8 @@ cmd_data = OrderedDict([
('Encrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]), ('Encrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
('Decrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]), ('Decrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
('Bytespec', ['<bytespec> [str]']), ('Bytespec', ['<bytespec> [str]']),
('Regtest_setup',[]),
('Keyaddrlist2monerowallet',['<{} XMR key-address file> [str]'.format(pnm),'blockheight [int=(current height)]']),
]) ])
def usage(command): 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 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 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 Strtob58(s,pad=None): Msg(baseconv.fromhex(binascii.hexlify(s),'b58',pad,tostr=True))
def Hextob58(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b58',pad))) def Hextob58(s,pad=None): Msg(baseconv.fromhex(s,'b58',pad,tostr=True))
def Hextob32(s,pad=None): Msg(''.join(baseconv.fromhex(s,'b32',pad))) def Hextob32(s,pad=None): Msg(baseconv.fromhex(s,'b32',pad,tostr=True))
def B58tostr(s): Msg(binascii.unhexlify(baseconv.tohex(s,'b58'))) def B58tostr(s): Msg(binascii.unhexlify(baseconv.tohex(s,'b58')))
def B58tohex(s,pad=None): Msg(baseconv.tohex(s,'b58',pad)) def B58tohex(s,pad=None): Msg(baseconv.tohex(s,'b58',pad))
def B32tohex(s,pad=None): Msg(baseconv.tohex(s.upper(),'b32',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 Bytespec(s): Msg(str(parse_nbytes(s)))
def Regtest_setup(): def Keyaddrlist2monerowallet(infile,blockheight=None):
print 'ok' import pexpect
return
if blockheight != None and int(blockheight) < 0: blockheight = 0
def run_cmd(cmd):
import subprocess as sp import subprocess as sp
sp.check_output() p = sp.Popen(cmd,stdin=sp.PIPE,stdout=sp.PIPE,stderr=sp.PIPE)
pass 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 ================== # # ================ RPC commands ================== #

View file

@ -736,7 +736,8 @@ class MMGenTX(MMGenObject):
if self.is_in_mempool(): if self.is_in_mempool():
if status: if status:
d = g.rpch.gettransaction(self.coin_txid,on_fail='silent') 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'] t = d['timereceived']
m = 'Sent {} ({} h/m/s ago)' m = 'Sent {} ({} h/m/s ago)'
b = m.format(time.strftime('%c',time.gmtime(t)),secs_to_dhms(int(time.time()-t))) 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 @classmethod
def b58encode(cls,s,pad=None): def b58encode(cls,s,pad=None):
pad = cls.get_pad(s,pad,'en',cls.b58pad_lens,[bytes]) 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 @classmethod
def b58decode(cls,s,pad=None): def b58decode(cls,s,pad=None):
@ -326,7 +326,7 @@ class baseconv(object):
return ('','0')[len(ret) % 2] + ret return ('','0')[len(ret) % 2] + ret
@classmethod @classmethod
def fromhex(cls,hexnum,wl_id,pad=None): def fromhex(cls,hexnum,wl_id,pad=None,tostr=False):
hexnum = hexnum.strip() hexnum = hexnum.strip()
if not is_hex_str(hexnum): if not is_hex_str(hexnum):
@ -338,7 +338,8 @@ class baseconv(object):
while num: while num:
ret.append(num % base) ret.append(num % base)
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() baseconv.check_wordlists()
@ -715,6 +716,10 @@ def keypress_confirm(prompt,default_yes=False,verbose=False,no_nl=False):
p = '{} {}: '.format(prompt,q) p = '{} {}: '.format(prompt,q)
nl = ('\n','\r{}\r'.format(' '*len(p)))[no_nl] nl = ('\n','\r{}\r'.format(' '*len(p)))[no_nl]
if opt.accept_defaults:
msg(p)
return (False,True)[default_yes]
while True: while True:
reply = get_char(p).strip('\n\r') reply = get_char(p).strip('\n\r')
if not reply: if not reply:

View file

@ -114,6 +114,7 @@ setup(
'mmgen.color', 'mmgen.color',
'mmgen.common', 'mmgen.common',
'mmgen.crypto', 'mmgen.crypto',
'mmgen.ed25519',
'mmgen.filename', 'mmgen.filename',
'mmgen.globalvars', 'mmgen.globalvars',
'mmgen.license', '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', ('b11f16632e63ba92','ltc:legacy', '-LTC','LTC', 'LMxB474SVfxeYdqxNrM1WZDZMnifteSMv1')),
('ltc_compressed',('7ccf465d466ee7d3','ltc:compressed', '-LTC-C','LTC:COMPRESSED', 'LdkebBKVXSs6NNoPJWGM8KciDnL8LhXXjb')), ('ltc_compressed',('7ccf465d466ee7d3','ltc:compressed', '-LTC-C','LTC:COMPRESSED', 'LdkebBKVXSs6NNoPJWGM8KciDnL8LhXXjb')),
('ltc_segwit', ('9460f5ba15e82768','ltc:segwit', '-LTC-S','LTC:SEGWIT', 'MQrY3vEbqKMBgegXrSaR93R2HoTDE5bKrY')), ('ltc_segwit', ('9460f5ba15e82768','ltc:segwit', '-LTC-S','LTC:SEGWIT', 'MQrY3vEbqKMBgegXrSaR93R2HoTDE5bKrY')),
('eth', ('213ed116869b19f2','eth', '-ETH', 'ETH','e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35')), ('eth', ('213ed116869b19f2','eth', '-ETH', 'ETH', 'e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35')),
('etc', ('909def37096f5ab8','etc', '-ETC', 'ETC','1a6acbef8c38f52f20d04ecded2992b04d8608d7')), ('etc', ('909def37096f5ab8','etc', '-ETC', 'ETC', '1a6acbef8c38f52f20d04ecded2992b04d8608d7')),
('dash', ('1319d347b021f952','dash:legacy', '-DASH','DASH','XoK491fppGNZQUUS9uEFkT6L9u8xxVFJNJ')), ('dash', ('1319d347b021f952','dash:legacy', '-DASH', 'DASH','XoK491fppGNZQUUS9uEFkT6L9u8xxVFJNJ')),
('emc', ('7e1a29853d2db875','emc:legacy', '-EMC', 'EMC', 'EU4L6x2b5QUb2gRQsBAAuB8TuPEwUxCNZU')),
('zec', ('0bf9b5b20af7b5a0','zec:legacy', '-ZEC', 'ZEC', 't1URz8BHxV38v3gsaN6oHQNKC16s35R9WkY')), ('zec', ('0bf9b5b20af7b5a0','zec:legacy', '-ZEC', 'ZEC', 't1URz8BHxV38v3gsaN6oHQNKC16s35R9WkY')),
('zec_zcash_z', ('b15570d033df9b1a','zec:zcash_z', '-ZEC-Z','ZEC:ZCASH_Z','zcLMMsnzfFYZWU4avRBnuc83yh4jTtJXbtP32uWrs3ickzu1krMU4ppZCQPTwwfE9hLnRuFDSYF8VFW13aT9eeQK8aov3Ge')), ('zec_zcash_z', ('b15570d033df9b1a','zec:zcash_z', '-ZEC-Z','ZEC:ZCASH_Z','zcLMMsnzfFYZWU4avRBnuc83yh4jTtJXbtP32uWrs3ickzu1krMU4ppZCQPTwwfE9hLnRuFDSYF8VFW13aT9eeQK8aov3Ge')),
('emc', ('7e1a29853d2db875','emc:legacy', '-EMC', 'EMC','EU4L6x2b5QUb2gRQsBAAuB8TuPEwUxCNZU')), ('xmr', ('c76af3b088da3364','xmr:monero', '-XMR-M','XMR:MONERO','41tmwZd2CdXEGtWqGY9fH9FVtQM8VxZASYPQ3VJQhFjtGWYzQFuidD21vJYTi2yy3tXRYXTNXBTaYVLav62rwUUpFFyicZU')),
]) ])
def run_tests(): def run_tests():

View file

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