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:
parent
279e8872ef
commit
df0385160b
18 changed files with 442 additions and 99 deletions
|
|
@ -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
|
||||
|
|
|
|||
135
mmgen/addr.py
135
mmgen/addr.py
|
|
@ -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 = {
|
||||
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()])))
|
||||
|
||||
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
|
||||
if self.pw_fmt == 'hex':
|
||||
return hex_sec
|
||||
else:
|
||||
# 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):
|
||||
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
57
mmgen/ed25519.py
Normal 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)])
|
||||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')]
|
||||
|
||||
|
|
|
|||
49
mmgen/obj.py
49
mmgen/obj.py
|
|
@ -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
|
||||
|
|
@ -584,6 +596,7 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
|
|||
me.compressed = w2h['compressed']
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
109
mmgen/tool.py
109
mmgen/tool.py
|
|
@ -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
|
||||
def Keyaddrlist2monerowallet(infile,blockheight=None):
|
||||
import pexpect
|
||||
|
||||
if blockheight != None and int(blockheight) < 0: blockheight = 0
|
||||
|
||||
def run_cmd(cmd):
|
||||
import subprocess as sp
|
||||
sp.check_output()
|
||||
pass
|
||||
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 ================== #
|
||||
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -114,6 +114,7 @@ setup(
|
|||
'mmgen.color',
|
||||
'mmgen.common',
|
||||
'mmgen.crypto',
|
||||
'mmgen.ed25519',
|
||||
'mmgen.filename',
|
||||
'mmgen.globalvars',
|
||||
'mmgen.license',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -66,9 +66,10 @@ test_data = OrderedDict([
|
|||
('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')),
|
||||
('emc', ('7e1a29853d2db875','emc:legacy', '-EMC', 'EMC','EU4L6x2b5QUb2gRQsBAAuB8TuPEwUxCNZU')),
|
||||
('xmr', ('c76af3b088da3364','xmr:monero', '-XMR-M','XMR:MONERO','41tmwZd2CdXEGtWqGY9fH9FVtQM8VxZASYPQ3VJQhFjtGWYzQFuidD21vJYTi2yy3tXRYXTNXBTaYVLav62rwUUpFFyicZU')),
|
||||
])
|
||||
|
||||
def run_tests():
|
||||
|
|
|
|||
26
test/test.py
26
test/test.py
|
|
@ -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,6 +1436,7 @@ 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 '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]
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue