overhaul public key and address generation code
- pubkey generation code has been rewritten and moved from addr.py to
keygen.py
- address generation code has been rewritten and moved from addr.py to
addrgen.py
- keygen/addrgen classes now present a consistent API across all pubkey and
address types
- key/address operations and related data objects now use bytes internally
instead of hex strings
- pubkey generator backends are now selected using the `--keygen-backend`
option
- for Monero pubkeys, the new `nacl` backend has replaced `ed25519ll_djbec`
as the default
- a minimal unit test has been added
Examples:
# Generate a random Monero keypair using the unoptimized 'ed25519' backend:
$ mmgen-tool --coin=xmr --keygen-backend=3 randpair
# Generate an LTC Bech32 address list from the default wallet using the
# 'python-ecdsa' backend:
$ mmgen-addrgen --coin=ltc --type=bech32 --keygen-backend=2 1-10
Testing:
# Run the minimal unit test:
$ test/unit_tests_py gen
# Compare BTC Segwit addresses from default 'libsecp256k1' backend to
# 'pycoin' library, with edge cases and 10,000 random rounds:
$ test/gentest.py --type=segwit 1:pycoin 10000
# Test all configured Monero backends against 'moneropy', with edge cases
# and 10 random rounds:
$ test/gentest.py --coin=xmr all:moneropy 10
# Test the 'nacl' and 'ed25519ll_djbec' backends against each other, with
# edge cases and 1000 random rounds:
$ test/gentest.py --coin=xmr 1:2 1000
# Test the speed of the Monero 'nacl' backend using 10,000 rounds:
$ test/gentest.py --coin=xmr 1 10000
# Same for Zcash:
$ test/gentest.py --coin=zec --type=zcash_z 1 10000
This commit is contained in:
parent
2807c628ee
commit
32c522c039
31 changed files with 735 additions and 466 deletions
279
mmgen/addr.py
279
mmgen/addr.py
|
|
@ -17,7 +17,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
addr.py: Address generation/display routines for the MMGen suite
|
||||
addr.py: MMGen address-related types
|
||||
"""
|
||||
|
||||
from string import ascii_letters,digits
|
||||
|
|
@ -179,260 +179,43 @@ class MoneroViewKey(HexStr):
|
|||
class ZcashViewKey(CoinAddr):
|
||||
hex_width = 128
|
||||
|
||||
from .opts import opt
|
||||
from .util import qmsg
|
||||
from .protocol import hash160
|
||||
from .key import PrivKey,PubKey
|
||||
from .baseconv import baseconv
|
||||
def KeyGenerator(proto,pubkey_type,backend=None,silent=False):
|
||||
"""
|
||||
factory function returning a key generator backend for the specified pubkey type
|
||||
"""
|
||||
assert pubkey_type in proto.pubkey_types, f'{pubkey_type!r}: invalid pubkey type for coin {proto.coin}'
|
||||
|
||||
class AddrGenerator(MMGenObject):
|
||||
def __new__(cls,proto,addr_type):
|
||||
from .keygen import keygen_backend,_check_backend
|
||||
|
||||
if type(addr_type) == str:
|
||||
addr_type = MMGenAddrType(proto=proto,id_str=addr_type)
|
||||
elif type(addr_type) == MMGenAddrType:
|
||||
assert addr_type in proto.mmtypes, f'{addr_type}: invalid address type for coin {proto.coin}'
|
||||
else:
|
||||
raise TypeError(f'{type(addr_type)}: incorrect argument type for {cls.__name__}()')
|
||||
pubkey_type_cls = getattr(keygen_backend,pubkey_type)
|
||||
|
||||
addr_generators = {
|
||||
'p2pkh': AddrGeneratorP2PKH,
|
||||
'segwit': AddrGeneratorSegwit,
|
||||
'bech32': AddrGeneratorBech32,
|
||||
'ethereum': AddrGeneratorEthereum,
|
||||
'zcash_z': AddrGeneratorZcashZ,
|
||||
'monero': AddrGeneratorMonero,
|
||||
}
|
||||
me = super(cls,cls).__new__(addr_generators[addr_type.gen_method])
|
||||
me.desc = type(me).__name__
|
||||
me.proto = proto
|
||||
me.addr_type = addr_type
|
||||
me.pubkey_type = addr_type.pubkey_type
|
||||
return me
|
||||
from .opts import opt
|
||||
backend = backend or getattr(opt,'keygen_backend',None)
|
||||
|
||||
class AddrGeneratorP2PKH(AddrGenerator):
|
||||
def to_addr(self,pubhex):
|
||||
assert pubhex.privkey.pubkey_type == self.pubkey_type
|
||||
return CoinAddr(self.proto,self.proto.pubhash2addr(hash160(pubhex),p2sh=False))
|
||||
if backend:
|
||||
_check_backend(backend,pubkey_type)
|
||||
|
||||
def to_segwit_redeem_script(self,pubhex):
|
||||
raise NotImplementedError('Segwit redeem script not supported by this address type')
|
||||
backend_id = pubkey_type_cls.backends[int(backend) - 1 if backend else 0]
|
||||
|
||||
class AddrGeneratorSegwit(AddrGenerator):
|
||||
def to_addr(self,pubhex):
|
||||
assert pubhex.privkey.pubkey_type == self.pubkey_type
|
||||
assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
|
||||
return CoinAddr(self.proto,self.proto.pubhex2segwitaddr(pubhex))
|
||||
if backend_id == 'libsecp256k1':
|
||||
if not pubkey_type_cls.libsecp256k1.test_avail(silent=silent):
|
||||
backend_id = 'python-ecdsa'
|
||||
if not backend:
|
||||
qmsg('Using (slow) native Python ECDSA library for public key generation')
|
||||
|
||||
def to_segwit_redeem_script(self,pubhex):
|
||||
assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
|
||||
return HexStr(self.proto.pubhex2redeem_script(pubhex))
|
||||
return getattr(pubkey_type_cls,backend_id.replace('-','_'))()
|
||||
|
||||
class AddrGeneratorBech32(AddrGenerator):
|
||||
def to_addr(self,pubhex):
|
||||
assert pubhex.privkey.pubkey_type == self.pubkey_type
|
||||
assert pubhex.compressed,'Uncompressed public keys incompatible with Segwit'
|
||||
return CoinAddr(self.proto,self.proto.pubhash2bech32addr(hash160(pubhex)))
|
||||
def AddrGenerator(proto,addr_type):
|
||||
"""
|
||||
factory function returning an address generator for the specified address type
|
||||
"""
|
||||
if type(addr_type) == str:
|
||||
addr_type = MMGenAddrType(proto=proto,id_str=addr_type)
|
||||
elif type(addr_type) == MMGenAddrType:
|
||||
assert addr_type in proto.mmtypes, f'{addr_type}: invalid address type for coin {proto.coin}'
|
||||
else:
|
||||
raise TypeError(f'{type(addr_type)}: incorrect argument type for {cls.__name__}()')
|
||||
|
||||
def to_segwit_redeem_script(self,pubhex):
|
||||
raise NotImplementedError('Segwit redeem script not supported by this address type')
|
||||
from .addrgen import addr_generator
|
||||
|
||||
class AddrGeneratorEthereum(AddrGenerator):
|
||||
|
||||
def __init__(self,proto,addr_type):
|
||||
|
||||
from .util import get_keccak
|
||||
self.keccak_256 = get_keccak()
|
||||
|
||||
from .protocol import hash256
|
||||
self.hash256 = hash256
|
||||
|
||||
def to_addr(self,pubhex):
|
||||
assert pubhex.privkey.pubkey_type == self.pubkey_type
|
||||
return CoinAddr(self.proto,self.keccak_256(bytes.fromhex(pubhex[2:])).hexdigest()[24:])
|
||||
|
||||
def to_wallet_passwd(self,sk_hex):
|
||||
return WalletPassword(self.hash256(sk_hex)[:32])
|
||||
|
||||
def to_segwit_redeem_script(self,pubhex):
|
||||
raise NotImplementedError('Segwit redeem script not supported by this address type')
|
||||
|
||||
# github.com/FiloSottile/zcash-mini/zcash/address.go
|
||||
class AddrGeneratorZcashZ(AddrGenerator):
|
||||
|
||||
def zhash256(self,s,t):
|
||||
s = bytearray(s + bytes(32))
|
||||
s[0] |= 0xc0
|
||||
s[32] = t
|
||||
from .sha2 import Sha256
|
||||
return Sha256(s,preprocess=False).digest()
|
||||
|
||||
def to_addr(self,pubhex): # pubhex is really privhex
|
||||
assert pubhex.privkey.pubkey_type == self.pubkey_type
|
||||
key = bytes.fromhex(pubhex)
|
||||
assert len(key) == 32, f'{len(key)}: incorrect privkey length'
|
||||
from nacl.bindings import crypto_scalarmult_base
|
||||
p2 = crypto_scalarmult_base(self.zhash256(key,1))
|
||||
from .protocol import _b58chk_encode
|
||||
ver_bytes = self.proto.addr_fmt_to_ver_bytes('zcash_z')
|
||||
ret = _b58chk_encode(ver_bytes + self.zhash256(key,0) + p2)
|
||||
return CoinAddr(self.proto,ret)
|
||||
|
||||
def to_viewkey(self,pubhex): # pubhex is really privhex
|
||||
key = bytes.fromhex(pubhex)
|
||||
assert len(key) == 32, f'{len(key)}: incorrect privkey length'
|
||||
vk = bytearray(self.zhash256(key,0)+self.zhash256(key,1))
|
||||
vk[32] &= 0xf8
|
||||
vk[63] &= 0x7f
|
||||
vk[63] |= 0x40
|
||||
from .protocol import _b58chk_encode
|
||||
ver_bytes = self.proto.addr_fmt_to_ver_bytes('viewkey')
|
||||
ret = _b58chk_encode(ver_bytes + vk)
|
||||
return ZcashViewKey(self.proto,ret)
|
||||
|
||||
def to_segwit_redeem_script(self,pubhex):
|
||||
raise NotImplementedError('Zcash z-addresses incompatible with Segwit')
|
||||
|
||||
class AddrGeneratorMonero(AddrGenerator):
|
||||
|
||||
def __init__(self,proto,addr_type):
|
||||
|
||||
from .util import get_keccak
|
||||
self.keccak_256 = get_keccak()
|
||||
|
||||
from .protocol import hash256
|
||||
self.hash256 = hash256
|
||||
|
||||
if getattr(opt,'use_old_ed25519',False):
|
||||
from .ed25519 import edwards,encodepoint,B,scalarmult
|
||||
else:
|
||||
from .ed25519ll_djbec import scalarmult
|
||||
from .ed25519 import edwards,encodepoint,B
|
||||
|
||||
self.edwards = edwards
|
||||
self.encodepoint = encodepoint
|
||||
self.scalarmult = scalarmult
|
||||
self.B = B
|
||||
|
||||
def b58enc(self,addr_bytes):
|
||||
enc = baseconv.frombytes
|
||||
l = len(addr_bytes)
|
||||
a = ''.join([enc(addr_bytes[i*8:i*8+8],'b58',pad=11,tostr=True) for i in range(l//8)])
|
||||
b = enc(addr_bytes[l-l%8:],'b58',pad=7,tostr=True)
|
||||
return a + b
|
||||
|
||||
def to_addr(self,sk_hex): # sk_hex instead of pubhex
|
||||
assert sk_hex.privkey.pubkey_type == self.pubkey_type
|
||||
|
||||
# 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 = self.scalarmult(self.B, e//2)
|
||||
Q = self.edwards(Q, Q)
|
||||
if e & 1: Q = self.edwards(Q, self.B)
|
||||
return Q
|
||||
|
||||
def hex2int_le(hexstr):
|
||||
return int((bytes.fromhex(hexstr)[::-1]).hex(),16)
|
||||
|
||||
vk_hex = self.to_viewkey(sk_hex)
|
||||
pk_str = self.encodepoint(scalarmultbase(hex2int_le(sk_hex)))
|
||||
pvk_str = self.encodepoint(scalarmultbase(hex2int_le(vk_hex)))
|
||||
addr_p1 = self.proto.addr_fmt_to_ver_bytes('monero') + pk_str + pvk_str
|
||||
|
||||
return CoinAddr(
|
||||
proto = self.proto,
|
||||
addr = self.b58enc(addr_p1 + self.keccak_256(addr_p1).digest()[:4]) )
|
||||
|
||||
def to_wallet_passwd(self,sk_hex):
|
||||
return WalletPassword(self.hash256(sk_hex)[:32])
|
||||
|
||||
def to_viewkey(self,sk_hex):
|
||||
assert len(sk_hex) == 64, f'{len(sk_hex)}: incorrect privkey length'
|
||||
return MoneroViewKey(
|
||||
self.proto.preprocess_key(self.keccak_256(bytes.fromhex(sk_hex)).digest(),None).hex() )
|
||||
|
||||
def to_segwit_redeem_script(self,sk_hex):
|
||||
raise NotImplementedError('Monero addresses incompatible with Segwit')
|
||||
|
||||
class KeyGenerator(MMGenObject):
|
||||
|
||||
def __new__(cls,proto,addr_type,generator=None,silent=False):
|
||||
if type(addr_type) == str: # allow override w/o check
|
||||
pubkey_type = addr_type
|
||||
elif type(addr_type) == MMGenAddrType:
|
||||
assert addr_type in proto.mmtypes, f'{address}: invalid address type for coin {proto.coin}'
|
||||
pubkey_type = addr_type.pubkey_type
|
||||
else:
|
||||
raise TypeError(f'{type(addr_type)}: incorrect argument type for {cls.__name__}()')
|
||||
if pubkey_type == 'std':
|
||||
if cls.test_for_secp256k1(silent=silent) and generator != 1:
|
||||
if not opt.key_generator or opt.key_generator == 2 or generator == 2:
|
||||
me = super(cls,cls).__new__(KeyGeneratorSecp256k1)
|
||||
else:
|
||||
qmsg('Using (slow) native Python ECDSA library for address generation')
|
||||
me = super(cls,cls).__new__(KeyGeneratorPython)
|
||||
elif pubkey_type in ('zcash_z','monero'):
|
||||
me = super(cls,cls).__new__(KeyGeneratorDummy)
|
||||
me.desc = 'mmgen-'+pubkey_type
|
||||
else:
|
||||
raise ValueError(f'{pubkey_type}: invalid pubkey_type argument')
|
||||
|
||||
me.proto = proto
|
||||
return me
|
||||
|
||||
@classmethod
|
||||
def test_for_secp256k1(self,silent=False):
|
||||
try:
|
||||
from .secp256k1 import priv2pub
|
||||
m = 'Unable to execute priv2pub() from secp256k1 extension module'
|
||||
assert priv2pub(bytes.fromhex('deadbeef'*8),1),m
|
||||
return True
|
||||
except Exception as e:
|
||||
if not silent:
|
||||
ymsg(str(e))
|
||||
return False
|
||||
|
||||
class KeyGeneratorPython(KeyGenerator):
|
||||
|
||||
desc = 'mmgen-python-ecdsa'
|
||||
|
||||
# devdoc/guide_wallets.md:
|
||||
# Uncompressed public keys start with 0x04; compressed public keys begin with 0x03 or
|
||||
# 0x02 depending on whether they're greater or less than the midpoint of the curve.
|
||||
def privnum2pubhex(self,numpriv,compressed=False):
|
||||
import ecdsa
|
||||
pko = ecdsa.SigningKey.from_secret_exponent(numpriv,curve=ecdsa.SECP256k1)
|
||||
# pubkey = x (32 bytes) + y (32 bytes) (unsigned big-endian)
|
||||
pubkey = pko.get_verifying_key().to_string().hex()
|
||||
if compressed: # discard Y coord, replace with appropriate version byte
|
||||
# even y: <0, odd y: >0 -- https://bitcointalk.org/index.php?topic=129652.0
|
||||
return ('03','02')[pubkey[-1] in '02468ace'] + pubkey[:64]
|
||||
else:
|
||||
return '04' + pubkey
|
||||
|
||||
def to_pubhex(self,privhex):
|
||||
assert type(privhex) == PrivKey
|
||||
return PubKey(
|
||||
s = self.privnum2pubhex(int(privhex,16),compressed=privhex.compressed),
|
||||
privkey = privhex )
|
||||
|
||||
class KeyGeneratorSecp256k1(KeyGenerator):
|
||||
desc = 'mmgen-secp256k1'
|
||||
def to_pubhex(self,privhex):
|
||||
assert type(privhex) == PrivKey
|
||||
from .secp256k1 import priv2pub
|
||||
return PubKey(
|
||||
s = priv2pub(bytes.fromhex(privhex),int(privhex.compressed)).hex(),
|
||||
privkey = privhex )
|
||||
|
||||
class KeyGeneratorDummy(KeyGenerator):
|
||||
desc = 'mmgen-dummy'
|
||||
def to_pubhex(self,privhex):
|
||||
assert type(privhex) == PrivKey
|
||||
return PubKey(
|
||||
s = privhex,
|
||||
privkey = privhex )
|
||||
return getattr(addr_generator,addr_type.name)(proto,addr_type)
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class AddrFile(MMGenObject):
|
|||
if p.has_keys:
|
||||
from .opts import opt
|
||||
if opt.b16:
|
||||
out.append(fs.format( '', f'orig_hex: {e.sec.orig_hex()}', c ))
|
||||
out.append(fs.format( '', f'orig_hex: {e.sec.orig_bytes.hex()}', c ))
|
||||
out.append(fs.format( '', f'{p.al_id.mmtype.wif_label}: {e.sec.wif}', c ))
|
||||
for k in ('viewkey','wallet_passwd'):
|
||||
v = getattr(e,k)
|
||||
|
|
@ -174,7 +174,7 @@ class AddrFile(MMGenObject):
|
|||
llen = len(ret)
|
||||
for n,e in enumerate(ret):
|
||||
qmsg_r(f'\rVerifying keys {n+1}/{llen}')
|
||||
assert e.addr == ag.to_addr(kg.to_pubhex(e.sec)),(
|
||||
assert e.addr == ag.to_addr(kg.gen_data(e.sec)),(
|
||||
f'Key doesn’t match address!\n {e.sec.wif}\n {e.addr}')
|
||||
qmsg(' - done')
|
||||
|
||||
|
|
|
|||
131
mmgen/addrgen.py
Executable file
131
mmgen/addrgen.py
Executable file
|
|
@ -0,0 +1,131 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
addrgen.py: Address and view key generation classes for the MMGen suite
|
||||
"""
|
||||
|
||||
from .protocol import hash160,_b58chk_encode
|
||||
from .addr import CoinAddr,MMGenAddrType,MoneroViewKey,ZcashViewKey
|
||||
|
||||
# decorator for to_addr() and to_viewkey()
|
||||
def check_data(orig_func):
|
||||
def f(self,data):
|
||||
assert data.pubkey_type == self.pubkey_type, 'addrgen.py:check_data() pubkey_type mismatch'
|
||||
assert data.compressed == self.compressed,(
|
||||
f'addrgen.py:check_data() expected compressed={self.compressed} but got compressed={data.compressed}'
|
||||
)
|
||||
return orig_func(self,data)
|
||||
return f
|
||||
|
||||
class addr_generator:
|
||||
"""
|
||||
provide a generator for each supported address format
|
||||
"""
|
||||
class base:
|
||||
|
||||
def __init__(self,proto,addr_type):
|
||||
self.proto = proto
|
||||
self.pubkey_type = addr_type.pubkey_type
|
||||
self.compressed = addr_type.compressed
|
||||
desc = f'AddrGenerator {type(self).__name__!r}'
|
||||
|
||||
def to_segwit_redeem_script(self,data):
|
||||
raise NotImplementedError('Segwit redeem script not supported by this address type')
|
||||
|
||||
class p2pkh(base):
|
||||
|
||||
@check_data
|
||||
def to_addr(self,data):
|
||||
return CoinAddr(
|
||||
self.proto,
|
||||
self.proto.pubhash2addr( hash160(data.pubkey), p2sh=False ))
|
||||
|
||||
class legacy(p2pkh): pass
|
||||
class compressed(p2pkh): pass
|
||||
|
||||
class segwit(base):
|
||||
|
||||
@check_data
|
||||
def to_addr(self,data):
|
||||
return CoinAddr(
|
||||
self.proto,
|
||||
self.proto.pubkey2segwitaddr(data.pubkey) )
|
||||
|
||||
def to_segwit_redeem_script(self,data): # NB: returns hex
|
||||
return self.proto.pubkey2redeem_script(data.pubkey).hex()
|
||||
|
||||
class bech32(base):
|
||||
|
||||
@check_data
|
||||
def to_addr(self,data):
|
||||
return CoinAddr(
|
||||
self.proto,
|
||||
self.proto.pubhash2bech32addr( hash160(data.pubkey)) )
|
||||
|
||||
class keccak(base):
|
||||
|
||||
def __init__(self,proto,addr_type):
|
||||
super().__init__(proto,addr_type)
|
||||
from .util import get_keccak
|
||||
self.keccak_256 = get_keccak()
|
||||
|
||||
class ethereum(keccak):
|
||||
|
||||
@check_data
|
||||
def to_addr(self,data):
|
||||
return CoinAddr(
|
||||
self.proto,
|
||||
self.keccak_256(data.pubkey[1:]).hexdigest()[24:] )
|
||||
|
||||
class monero(keccak):
|
||||
|
||||
def b58enc(self,addr_bytes):
|
||||
from .baseconv import baseconv
|
||||
enc = baseconv.frombytes
|
||||
l = len(addr_bytes)
|
||||
a = ''.join([enc( addr_bytes[i*8:i*8+8], 'b58', pad=11, tostr=True ) for i in range(l//8)])
|
||||
b = enc( addr_bytes[l-l%8:], 'b58', pad=7, tostr=True )
|
||||
return a + b
|
||||
|
||||
@check_data
|
||||
def to_addr(self,data):
|
||||
step1 = self.proto.addr_fmt_to_ver_bytes('monero') + data.pubkey
|
||||
return CoinAddr(
|
||||
proto = self.proto,
|
||||
addr = self.b58enc( step1 + self.keccak_256(step1).digest()[:4]) )
|
||||
|
||||
@check_data
|
||||
def to_viewkey(self,data):
|
||||
return MoneroViewKey( data.viewkey_bytes.hex() )
|
||||
|
||||
class zcash_z(base):
|
||||
|
||||
@check_data
|
||||
def to_addr(self,data):
|
||||
ret = _b58chk_encode(
|
||||
self.proto.addr_fmt_to_ver_bytes('zcash_z')
|
||||
+ data.pubkey )
|
||||
return CoinAddr( self.proto, ret )
|
||||
|
||||
@check_data
|
||||
def to_viewkey(self,data):
|
||||
ret = _b58chk_encode(
|
||||
self.proto.addr_fmt_to_ver_bytes('viewkey')
|
||||
+ data.viewkey_bytes )
|
||||
return ZcashViewKey( self.proto, ret )
|
||||
|
|
@ -227,7 +227,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID
|
|||
|
||||
if self.gen_addrs:
|
||||
from .addr import KeyGenerator,AddrGenerator
|
||||
kg = KeyGenerator( self.proto, mmtype )
|
||||
kg = KeyGenerator( self.proto, mmtype.pubkey_type )
|
||||
ag = AddrGenerator( self.proto, mmtype )
|
||||
|
||||
t_addrs,out = ( len(addr_idxs), AddrListData() )
|
||||
|
|
@ -258,12 +258,12 @@ class AddrList(MMGenObject): # Address info for a single seed ID
|
|||
pubkey_type = mmtype.pubkey_type )
|
||||
|
||||
if self.gen_addrs:
|
||||
pubhex = kg.to_pubhex(e.sec)
|
||||
e.addr = ag.to_addr(pubhex)
|
||||
data = kg.gen_data(e.sec)
|
||||
e.addr = ag.to_addr(data)
|
||||
if gen_viewkey:
|
||||
e.viewkey = ag.to_viewkey(pubhex)
|
||||
e.viewkey = ag.to_viewkey(data)
|
||||
if gen_wallet_passwd:
|
||||
e.wallet_passwd = ag.to_wallet_passwd(e.sec)
|
||||
e.wallet_passwd = self.gen_wallet_passwd(e.sec)
|
||||
elif self.gen_passwds:
|
||||
e.passwd = self.gen_passwd(e.sec) # TODO - own type
|
||||
|
||||
|
|
@ -356,9 +356,9 @@ class AddrList(MMGenObject): # Address info for a single seed ID
|
|||
def gen_addr(pk,t):
|
||||
at = self.proto.addr_type(t)
|
||||
from .addr import KeyGenerator,AddrGenerator
|
||||
kg = KeyGenerator(self.proto,at)
|
||||
kg = KeyGenerator(self.proto,at.pubkey_type)
|
||||
ag = AddrGenerator(self.proto,at)
|
||||
return ag.to_addr(kg.to_pubhex(pk))
|
||||
return ag.to_addr(kg.gen_data(pk))
|
||||
|
||||
compressed_types = set(self.proto.mmtypes) - {'L','E'}
|
||||
uncompressed_types = set(self.proto.mmtypes) & {'L','E'}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
13.1.dev002
|
||||
13.1.dev003
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class BaseConversionPadError(Exception): mmcode = 2
|
|||
class TransactionChainMismatch(Exception):mmcode = 2
|
||||
class ObjectInitError(Exception): mmcode = 2
|
||||
class ClassFlagsError(Exception): mmcode = 2
|
||||
class ExtensionModuleError(Exception): mmcode = 2
|
||||
|
||||
# 3: yellow hl, 'MMGen Error' + exception + message
|
||||
class RPCFailure(Exception): mmcode = 3
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ class GlobalContext(Lockable):
|
|||
required_opts = (
|
||||
'quiet','verbose','debug','outdir','echo_passphrase','passwd_file','stdout',
|
||||
'show_hash_presets','label','keep_passphrase','keep_hash_preset','yes',
|
||||
'brain_params','b16','usr_randchars','coin','bob','alice','key_generator',
|
||||
'brain_params','b16','usr_randchars','coin','bob','alice',
|
||||
'hidden_incog_input_params','in_fmt','hash_preset','seed_len',
|
||||
)
|
||||
incompatible_opts = (
|
||||
|
|
@ -271,9 +271,6 @@ class GlobalContext(Lockable):
|
|||
aesctr_dfl_iv = int.to_bytes(1,aesctr_iv_len,'big')
|
||||
hincog_chk_len = 8
|
||||
|
||||
key_generators = ('python-ecdsa','libsecp256k1') # '1','2'
|
||||
key_generator = 2 # libsecp256k1 is default
|
||||
|
||||
force_standalone_scrypt_module = False
|
||||
# Scrypt params: 'id_num': [N, r, p] (N is an exponent of two)
|
||||
# NB: hashlib.scrypt in Python (>=v3.6) supports max N value of 14. This means that
|
||||
|
|
|
|||
|
|
@ -39,6 +39,17 @@ def help_notes_func(proto,po,k):
|
|||
|
||||
class help_notes:
|
||||
|
||||
def coin_id():
|
||||
return proto.coin_id
|
||||
|
||||
def keygen_backends():
|
||||
from .keygen import get_backends
|
||||
from .addr import MMGenAddrType
|
||||
backends = get_backends(
|
||||
MMGenAddrType(proto,po.user_opts.get('type') or proto.dfl_mmtype).pubkey_type
|
||||
)
|
||||
return ' '.join( f'{n}:{k}{" [default]" if n==1 else ""}' for n,k in enumerate(backends,1) )
|
||||
|
||||
def coind_exec():
|
||||
return coind_exec()
|
||||
|
||||
|
|
|
|||
33
mmgen/key.py
33
mmgen/key.py
|
|
@ -22,7 +22,7 @@ key.py: MMGen public and private key objects
|
|||
|
||||
from string import ascii_letters,digits
|
||||
from .objmethods import Hilite,InitErrors,MMGenObject
|
||||
from .obj import ImmutableAttr,get_obj,HexStr
|
||||
from .obj import ImmutableAttr,get_obj
|
||||
|
||||
class WifKey(str,Hilite,InitErrors):
|
||||
"""
|
||||
|
|
@ -44,26 +44,26 @@ class WifKey(str,Hilite,InitErrors):
|
|||
def is_wif(proto,s):
|
||||
return get_obj( WifKey, proto=proto, wif=s, silent=True, return_bool=True )
|
||||
|
||||
class PubKey(HexStr,MMGenObject): # TODO: add some real checks
|
||||
class PubKey(bytes,InitErrors,MMGenObject): # TODO: add some real checks
|
||||
|
||||
def __new__(cls,s,privkey):
|
||||
def __new__(cls,s,compressed):
|
||||
try:
|
||||
me = HexStr.__new__(cls,s,case='lower')
|
||||
me.privkey = privkey
|
||||
me.compressed = privkey.compressed
|
||||
assert isinstance(s,bytes)
|
||||
me = bytes.__new__(cls,s)
|
||||
me.compressed = compressed
|
||||
return me
|
||||
except Exception as e:
|
||||
return cls.init_fail(e,s)
|
||||
|
||||
class PrivKey(str,Hilite,InitErrors,MMGenObject):
|
||||
class PrivKey(bytes,Hilite,InitErrors,MMGenObject):
|
||||
"""
|
||||
Input: a) raw, non-preprocessed bytes; or b) WIF key.
|
||||
Output: preprocessed hexadecimal key, plus WIF key in 'wif' attribute
|
||||
Output: preprocessed key bytes, plus WIF key in 'wif' attribute
|
||||
For coins without a WIF format, 'wif' contains the preprocessed hex.
|
||||
The numeric validity of the resulting key is always checked.
|
||||
"""
|
||||
color = 'red'
|
||||
width = 64
|
||||
width = 32
|
||||
trunc_ok = False
|
||||
|
||||
compressed = ImmutableAttr(bool,typeconv=False)
|
||||
|
|
@ -78,11 +78,11 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
|
|||
assert s == None,"'wif' and key hex args are mutually exclusive"
|
||||
assert set(wif) <= set(ascii_letters+digits),'not an ascii alphanumeric string'
|
||||
k = proto.parse_wif(wif) # raises exception on error
|
||||
me = str.__new__(cls,k.sec.hex())
|
||||
me = bytes.__new__(cls,k.sec)
|
||||
me.compressed = k.compressed
|
||||
me.pubkey_type = k.pubkey_type
|
||||
me.wif = str.__new__(WifKey,wif) # check has been done
|
||||
me.orig_hex = None
|
||||
me.orig_bytes = None
|
||||
if k.sec != proto.preprocess_key(k.sec,k.pubkey_type):
|
||||
from .exception import PrivateKeyError
|
||||
raise PrivateKeyError(
|
||||
|
|
@ -94,19 +94,20 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
|
|||
else:
|
||||
try:
|
||||
assert s,'private key bytes data missing'
|
||||
assert isinstance(s,bytes),'input is not bytes'
|
||||
assert pubkey_type is not None,"'pubkey_type' arg missing"
|
||||
assert len(s) == cls.width // 2, f'key length must be {cls.width // 2} bytes'
|
||||
assert len(s) == cls.width, f'key length must be {cls.width} bytes'
|
||||
if pubkey_type == 'password': # skip WIF creation and pre-processing for passwds
|
||||
me = str.__new__(cls,s.hex())
|
||||
me = bytes.__new__(cls,s)
|
||||
else:
|
||||
assert compressed is not None, "'compressed' arg missing"
|
||||
assert type(compressed) == bool,(
|
||||
f"'compressed' must be of type bool, not {type(compressed).__name__}" )
|
||||
me = str.__new__(cls,proto.preprocess_key(s,pubkey_type).hex())
|
||||
me.wif = WifKey(proto,proto.hex2wif(me,pubkey_type,compressed))
|
||||
me = bytes.__new__( cls, proto.preprocess_key(s,pubkey_type) )
|
||||
me.wif = WifKey( proto, proto.bytes2wif(me,pubkey_type,compressed) )
|
||||
me.compressed = compressed
|
||||
me.pubkey_type = pubkey_type
|
||||
me.orig_hex = s.hex() # save the non-preprocessed key
|
||||
me.orig_bytes = s # save the non-preprocessed key
|
||||
me.proto = proto
|
||||
return me
|
||||
except Exception as e:
|
||||
|
|
|
|||
239
mmgen/keygen.py
Executable file
239
mmgen/keygen.py
Executable file
|
|
@ -0,0 +1,239 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
keygen.py: Public key generation classes for the MMGen suite
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
from .key import PubKey,PrivKey
|
||||
|
||||
keygen_public_data = namedtuple(
|
||||
'keygen_public_data', [
|
||||
'pubkey',
|
||||
'viewkey_bytes',
|
||||
'pubkey_type',
|
||||
'compressed' ])
|
||||
|
||||
class keygen_base:
|
||||
|
||||
def gen_data(self,privkey):
|
||||
assert isinstance(privkey,PrivKey)
|
||||
return keygen_public_data(
|
||||
self.to_pubkey(privkey),
|
||||
self.to_viewkey(privkey),
|
||||
privkey.pubkey_type,
|
||||
privkey.compressed )
|
||||
|
||||
def to_viewkey(self,privkey):
|
||||
return None
|
||||
|
||||
class keygen_backend:
|
||||
|
||||
class std:
|
||||
backends = ('libsecp256k1','python-ecdsa')
|
||||
|
||||
class libsecp256k1(keygen_base):
|
||||
|
||||
def __init__(self):
|
||||
from .secp256k1 import priv2pub
|
||||
self.priv2pub = priv2pub
|
||||
|
||||
def to_pubkey(self,privkey):
|
||||
return PubKey(
|
||||
s = self.priv2pub( privkey, int(privkey.compressed) ),
|
||||
compressed = privkey.compressed )
|
||||
|
||||
@classmethod
|
||||
def test_avail(cls,silent=False):
|
||||
try:
|
||||
from .secp256k1 import priv2pub
|
||||
if not priv2pub(bytes.fromhex('deadbeef'*8),1):
|
||||
from .exception import ExtensionModuleError
|
||||
raise ExtensionModuleError('Unable to execute priv2pub() from secp256k1 extension module')
|
||||
return True
|
||||
except Exception as e:
|
||||
if not silent:
|
||||
from .util import ymsg
|
||||
ymsg(str(e))
|
||||
return False
|
||||
|
||||
class python_ecdsa(keygen_base):
|
||||
|
||||
def __init__(self):
|
||||
import ecdsa
|
||||
self.ecdsa = ecdsa
|
||||
|
||||
def to_pubkey(self,privkey):
|
||||
"""
|
||||
devdoc/guide_wallets.md:
|
||||
Uncompressed public keys start with 0x04; compressed public keys begin with 0x03 or
|
||||
0x02 depending on whether they're greater or less than the midpoint of the curve.
|
||||
"""
|
||||
def privnum2pubkey(numpriv,compressed=False):
|
||||
pko = self.ecdsa.SigningKey.from_secret_exponent(numpriv,curve=self.ecdsa.SECP256k1)
|
||||
# pubkey = x (32 bytes) + y (32 bytes) (unsigned big-endian)
|
||||
pubkey = pko.get_verifying_key().to_string()
|
||||
if compressed: # discard Y coord, replace with appropriate version byte
|
||||
# even y: <0, odd y: >0 -- https://bitcointalk.org/index.php?topic=129652.0
|
||||
return (b'\x02',b'\x03')[pubkey[-1] & 1] + pubkey[:32]
|
||||
else:
|
||||
return b'\x04' + pubkey
|
||||
|
||||
return PubKey(
|
||||
s = privnum2pubkey( int.from_bytes(privkey,'big'), compressed=privkey.compressed ),
|
||||
compressed = privkey.compressed )
|
||||
|
||||
class monero:
|
||||
backends = ('nacl','ed25519ll_djbec','ed25519')
|
||||
|
||||
class base(keygen_base):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
from .protocol import CoinProtocol
|
||||
self.proto_cls = CoinProtocol.Monero
|
||||
|
||||
from .util import get_keccak
|
||||
self.keccak_256 = get_keccak()
|
||||
|
||||
def to_viewkey(self,privkey):
|
||||
return self.proto_cls.preprocess_key(
|
||||
self.proto_cls,
|
||||
self.keccak_256(privkey).digest(),
|
||||
None )
|
||||
|
||||
class nacl(base):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
from nacl.bindings import crypto_scalarmult_ed25519_base_noclamp
|
||||
self.scalarmultbase = crypto_scalarmult_ed25519_base_noclamp
|
||||
|
||||
def to_pubkey(self,privkey):
|
||||
return PubKey(
|
||||
self.scalarmultbase( privkey ) +
|
||||
self.scalarmultbase( self.to_viewkey(privkey) ),
|
||||
compressed = privkey.compressed
|
||||
)
|
||||
|
||||
class ed25519(base):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
from .ed25519 import edwards,encodepoint,B,scalarmult
|
||||
self.edwards = edwards
|
||||
self.encodepoint = encodepoint
|
||||
self.B = B
|
||||
self.scalarmult = scalarmult
|
||||
|
||||
def scalarmultbase(self,privnum):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
if privnum == 0:
|
||||
return [0, 1]
|
||||
Q = self.scalarmult(self.B, privnum//2)
|
||||
Q = self.edwards(Q, Q)
|
||||
if privnum & 1:
|
||||
Q = self.edwards(Q, self.B)
|
||||
return Q
|
||||
|
||||
@staticmethod
|
||||
def rev_bytes2int(in_bytes):
|
||||
return int.from_bytes( in_bytes[::-1], 'big' )
|
||||
|
||||
def to_pubkey(self,privkey):
|
||||
return PubKey(
|
||||
self.encodepoint( self.scalarmultbase( self.rev_bytes2int(privkey) )) +
|
||||
self.encodepoint( self.scalarmultbase( self.rev_bytes2int(self.to_viewkey(privkey)) )),
|
||||
compressed = privkey.compressed
|
||||
)
|
||||
|
||||
class ed25519ll_djbec(ed25519):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
from .ed25519ll_djbec import scalarmult
|
||||
self.scalarmult = scalarmult
|
||||
|
||||
class zcash_z:
|
||||
backends = ('nacl',)
|
||||
|
||||
class nacl(keygen_base):
|
||||
|
||||
def __init__(self):
|
||||
from nacl.bindings import crypto_scalarmult_base
|
||||
self.crypto_scalarmult_base = crypto_scalarmult_base
|
||||
from .sha2 import Sha256
|
||||
self.Sha256 = Sha256
|
||||
|
||||
def zhash256(self,s,t):
|
||||
s = bytearray(s + bytes(32))
|
||||
s[0] |= 0xc0
|
||||
s[32] = t
|
||||
return self.Sha256(s,preprocess=False).digest()
|
||||
|
||||
def to_pubkey(self,privkey):
|
||||
return PubKey(
|
||||
self.zhash256(privkey,0)
|
||||
+ self.crypto_scalarmult_base(self.zhash256(privkey,1)),
|
||||
compressed = privkey.compressed
|
||||
)
|
||||
|
||||
def to_viewkey(self,privkey):
|
||||
vk = bytearray( self.zhash256(privkey,0) + self.zhash256(privkey,1) )
|
||||
vk[32] &= 0xf8
|
||||
vk[63] &= 0x7f
|
||||
vk[63] |= 0x40
|
||||
return vk
|
||||
|
||||
def get_backends(pubkey_type):
|
||||
return getattr(keygen_backend,pubkey_type).backends
|
||||
|
||||
def _check_backend(backend,pubkey_type,desc='keygen backend'):
|
||||
|
||||
from .util import is_int,qmsg,die
|
||||
|
||||
assert is_int(backend), f'illegal value for {desc} (must be an integer)'
|
||||
|
||||
backends = get_backends(pubkey_type)
|
||||
|
||||
if not (1 <= int(backend) <= len(backends)):
|
||||
die(1,
|
||||
f'{backend}: {desc} out of range\n' +
|
||||
f'Configured backends: ' +
|
||||
' '.join( f'{n}:{k}' for n,k in enumerate(backends,1) )
|
||||
)
|
||||
|
||||
qmsg(f'Using backend {backends[int(backend)-1]!r} for public key generation')
|
||||
|
||||
return True
|
||||
|
||||
def check_backend(proto,backend,addr_type):
|
||||
|
||||
from .addr import MMGenAddrType
|
||||
pubkey_type = MMGenAddrType(proto,addr_type or proto.dfl_mmtype).pubkey_type
|
||||
|
||||
return _check_backend(
|
||||
backend,
|
||||
pubkey_type,
|
||||
desc = '--keygen-backend parameter' )
|
||||
|
|
@ -56,15 +56,13 @@ opts_data = {
|
|||
-c, --print-checksum Print address list checksum and exit
|
||||
-d, --outdir= d Output files to directory 'd' instead of working dir
|
||||
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
|
||||
-E, --use-old-ed25519 Use original (and slow) ed25519 module for Monero
|
||||
address generation instead of ed25519ll_djbec
|
||||
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
|
||||
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
|
||||
'f' at offset 'o' (comma-separated)
|
||||
-O, --old-incog-fmt Specify old-format incognito input
|
||||
-k, --use-internal-keccak-module Force use of the internal keccak module
|
||||
-K, --key-generator=m Use method 'm' for public key generation
|
||||
Options: {kgs} (default: {kg})
|
||||
-K, --keygen-backend=n Use backend 'n' for public key generation. Options
|
||||
for {coin_id}: {kgs}
|
||||
-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.dfl_seed_len}-bit) seed lengths
|
||||
|
|
@ -106,11 +104,11 @@ FMT CODES:
|
|||
"""
|
||||
},
|
||||
'code': {
|
||||
'options': lambda proto,s: s.format(
|
||||
'options': lambda proto,help_notes,s: s.format(
|
||||
seed_lens=', '.join(map(str,g.seed_lens)),
|
||||
dmat="'{}' or '{}'".format(proto.dfl_mmtype,MMGenAddrType.mmtypes[proto.dfl_mmtype].name),
|
||||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||
kg=g.key_generator,
|
||||
kgs=help_notes('keygen_backends'),
|
||||
coin_id=help_notes('coin_id'),
|
||||
pnm=g.proj_name,
|
||||
what=gen_what,
|
||||
g=g,
|
||||
|
|
@ -142,8 +140,9 @@ addr_type = MMGenAddrType(
|
|||
if len(cmd_args) < 1:
|
||||
opts.usage()
|
||||
|
||||
if getattr(opt,'use_old_ed25519',False):
|
||||
msg('Using old (slow) ed25519 module by user request')
|
||||
if opt.keygen_backend:
|
||||
from .keygen import check_backend
|
||||
check_backend( proto, opt.keygen_backend, opt.type )
|
||||
|
||||
idxs = AddrIdxList(fmt_str=cmd_args.pop())
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ opts_data = {
|
|||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-k, --use-internal-keccak-module Force use of the internal keccak module
|
||||
-K, --keygen-backend=n Use backend 'n' for public key generation. Options
|
||||
for {coin_id}: {kgs}
|
||||
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
|
||||
for password hashing (default: '{g.dfl_hash_preset}')
|
||||
-P, --passwd-file= f Get passphrase from file 'f'.
|
||||
|
|
@ -84,7 +86,11 @@ Type '{pn} help <command>' for help on a particular command
|
|||
"""
|
||||
},
|
||||
'code': {
|
||||
'options': lambda s: s.format(g=g),
|
||||
'options': lambda s, help_notes: s.format(
|
||||
kgs=help_notes('keygen_backends'),
|
||||
coin_id=help_notes('coin_id'),
|
||||
g=g,
|
||||
),
|
||||
'notes': lambda s: s.format(
|
||||
ch=make_cmd_help(),
|
||||
pn=g.prog_name)
|
||||
|
|
|
|||
|
|
@ -51,9 +51,8 @@ opts_data = {
|
|||
is required only for brainwallet and incognito inputs
|
||||
with non-standard (< {g.dfl_seed_len}-bit) seed lengths.
|
||||
-k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
|
||||
-K, --key-generator= m Use method 'm' for public key generation
|
||||
Options: {kgs}
|
||||
(default: {kg})
|
||||
-K, --keygen-backend=n Use backend 'n' for public key generation. Options
|
||||
for {coin_id}: {kgs}
|
||||
-M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
|
||||
address file (output of '{pnl}-keygen'). Permits
|
||||
online signing without an {pnm} seed source. The
|
||||
|
|
@ -87,8 +86,8 @@ column below:
|
|||
pnl=g.proj_name.lower(),
|
||||
fu=help_notes('rel_fee_desc'),
|
||||
fl=help_notes('fee_spec_letters'),
|
||||
kgs=' '.join([f'{n}:{k}' for n,k in enumerate(g.key_generators,1)]),
|
||||
kg=g.key_generator,
|
||||
kgs=help_notes('keygen_backends'),
|
||||
coin_id=help_notes('coin_id'),
|
||||
cu=proto.coin),
|
||||
'notes': lambda help_notes,s: s.format(
|
||||
help_notes('fee'),
|
||||
|
|
|
|||
|
|
@ -58,9 +58,8 @@ opts_data = {
|
|||
is required only for brainwallet and incognito inputs
|
||||
with non-standard (< {g.dfl_seed_len}-bit) seed lengths.
|
||||
-k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
|
||||
-K, --key-generator= m Use method 'm' for public key generation
|
||||
Options: {kgs}
|
||||
(default: {kg})
|
||||
-K, --keygen-backend=n Use backend 'n' for public key generation. Options
|
||||
for {coin_id}: {kgs}
|
||||
-L, --locktime= t Lock time (block height or unix seconds) (default: 0)
|
||||
-m, --minconf=n Minimum number of confirmations required to spend
|
||||
outputs (default: 1)
|
||||
|
|
@ -95,14 +94,14 @@ column below:
|
|||
'code': {
|
||||
'options': lambda proto,help_notes,s: s.format(
|
||||
g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),
|
||||
kgs=' '.join([f'{n}:{k}' for n,k in enumerate(g.key_generators,1)]),
|
||||
kgs=help_notes('keygen_backends'),
|
||||
coin_id=help_notes('coin_id'),
|
||||
fu=help_notes('rel_fee_desc'),
|
||||
fl=help_notes('fee_spec_letters'),
|
||||
ss=g.subseeds,
|
||||
ss_max=SubSeedIdxRange.max_idx,
|
||||
fe_all=fmt_list(g.autoset_opts['fee_estimate_mode'].choices,fmt='no_spc'),
|
||||
fe_dfl=g.autoset_opts['fee_estimate_mode'].choices[0],
|
||||
kg=g.key_generator,
|
||||
cu=proto.coin),
|
||||
'notes': lambda help_notes,s: s.format(
|
||||
help_notes('txcreate'),
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ opts_data = {
|
|||
for password hashing (default: '{g.dfl_hash_preset}')
|
||||
-z, --show-hash-presets Show information on available hash presets
|
||||
-k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
|
||||
-K, --key-generator=m Use method 'm' for public key generation
|
||||
Options: {kgs} (default: {kg})
|
||||
-K, --keygen-backend=n Use backend 'n' for public key generation. Options
|
||||
for {coin_id}: {kgs}
|
||||
-M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
|
||||
address file (output of '{pnl}-keygen'). Permits
|
||||
online signing without an {pnm} seed source. The
|
||||
|
|
@ -77,12 +77,12 @@ column below:
|
|||
"""
|
||||
},
|
||||
'code': {
|
||||
'options': lambda proto,s: s.format(
|
||||
'options': lambda proto,help_notes,s: s.format(
|
||||
g=g,
|
||||
pnm=g.proj_name,
|
||||
pnl=g.proj_name.lower(),
|
||||
kgs=' '.join([f'{n}:{k}' for n,k in enumerate(g.key_generators,1)]),
|
||||
kg=g.key_generator,
|
||||
kgs=help_notes('keygen_backends'),
|
||||
coin_id=help_notes('coin_id'),
|
||||
ss=g.subseeds,
|
||||
ss_max=SubSeedIdxRange.max_idx,
|
||||
cu=proto.coin),
|
||||
|
|
|
|||
|
|
@ -544,10 +544,6 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
|
|||
opt_is_float(val,desc)
|
||||
ymsg(f'Adjusting transaction vsize by a factor of {float(val):1.2f}')
|
||||
|
||||
def chk_key_generator(key,val,desc):
|
||||
opt_compares(val,'<=',len(g.key_generators),desc)
|
||||
opt_compares(val,'>',0,desc)
|
||||
|
||||
def chk_coin(key,val,desc):
|
||||
from .protocol import CoinProtocol
|
||||
opt_is_in_list(val.lower(),CoinProtocol.coins,'coin')
|
||||
|
|
|
|||
|
|
@ -185,28 +185,27 @@ class PasswordList(AddrList):
|
|||
default_yes = True ):
|
||||
die(1,'Exiting at user request')
|
||||
|
||||
def gen_passwd(self,hex_sec):
|
||||
def gen_passwd(self,secbytes):
|
||||
assert self.pw_fmt in self.pw_info
|
||||
if self.pw_fmt == 'hex':
|
||||
# take most significant part
|
||||
return hex_sec[:self.pw_len]
|
||||
return secbytes.hex()[:self.pw_len]
|
||||
elif self.pw_fmt == 'bip39':
|
||||
from .bip39 import bip39
|
||||
pw_len_hex = bip39.nwords2seedlen(self.pw_len,in_hex=True)
|
||||
pw_len_bytes = bip39.nwords2seedlen( self.pw_len, in_bytes=True )
|
||||
# take most significant part
|
||||
return ' '.join(bip39.fromhex(hex_sec[:pw_len_hex],wl_id='bip39'))
|
||||
return ' '.join( bip39.fromhex( secbytes[:pw_len_bytes].hex(), wl_id='bip39' ) )
|
||||
elif self.pw_fmt == 'xmrseed':
|
||||
pw_len_hex = baseconv.seedlen_map_rev['xmrseed'][self.pw_len] * 2
|
||||
# take most significant part
|
||||
pw_len_bytes = baseconv.seedlen_map_rev['xmrseed'][self.pw_len]
|
||||
from .protocol import init_proto
|
||||
bytes_preproc = init_proto('xmr').preprocess_key(
|
||||
bytes.fromhex(hex_sec[:pw_len_hex]),
|
||||
secbytes[:pw_len_bytes], # take most significant part
|
||||
None )
|
||||
return ' '.join( baseconv.frombytes( bytes_preproc, wl_id='xmrseed' ) )
|
||||
else:
|
||||
# take least significant part
|
||||
return baseconv.fromhex(
|
||||
hex_sec,
|
||||
return baseconv.frombytes(
|
||||
secbytes,
|
||||
self.pw_fmt,
|
||||
pad = self.pw_len,
|
||||
tostr = True )[-self.pw_len:]
|
||||
|
|
|
|||
|
|
@ -33,14 +33,11 @@ import mmgen.bech32 as bech32
|
|||
parsed_wif = namedtuple('parsed_wif',['sec','pubkey_type','compressed'])
|
||||
parsed_addr = namedtuple('parsed_addr',['bytes','fmt'])
|
||||
|
||||
def hash160(hexnum): # take hex, return hex - OP_HASH160
|
||||
return hashlib.new('ripemd160',hashlib.sha256(bytes.fromhex(hexnum)).digest()).hexdigest()
|
||||
def hash160(in_bytes): # OP_HASH160
|
||||
return hashlib.new('ripemd160',hashlib.sha256(in_bytes).digest()).digest()
|
||||
|
||||
def hash256(hexnum): # take hex, return hex - OP_HASH256
|
||||
return hashlib.sha256(hashlib.sha256(bytes.fromhex(hexnum)).digest()).hexdigest()
|
||||
|
||||
def hash256bytes(bstr): # bytes in, bytes out - OP_HASH256
|
||||
return hashlib.sha256(hashlib.sha256(bstr).digest()).digest()
|
||||
def hash256(in_bytes): # OP_HASH256
|
||||
return hashlib.sha256(hashlib.sha256(in_bytes).digest()).digest()
|
||||
|
||||
_b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
|
||||
|
|
@ -57,14 +54,14 @@ def _b58chk_encode(in_bytes):
|
|||
while n:
|
||||
yield _b58a[n % 58]
|
||||
n //= 58
|
||||
return ('1' * lzeroes) + ''.join(do_enc(int.from_bytes(in_bytes+hash256bytes(in_bytes)[:4],'big')))[::-1]
|
||||
return ('1' * lzeroes) + ''.join(do_enc(int.from_bytes(in_bytes+hash256(in_bytes)[:4],'big')))[::-1]
|
||||
|
||||
def _b58chk_decode(s):
|
||||
lzeroes = len(s) - len(s.lstrip('1'))
|
||||
res = sum(_b58a.index(ch) * 58**n for n,ch in enumerate(s[::-1]))
|
||||
bl = res.bit_length()
|
||||
out = b'\x00' * lzeroes + res.to_bytes(bl//8 + bool(bl%8),'big')
|
||||
if out[-4:] != hash256bytes(out[:-4])[:4]:
|
||||
if out[-4:] != hash256(out[:-4])[:4]:
|
||||
raise ValueError('_b58chk_decode(): incorrect checksum')
|
||||
return out[:-4]
|
||||
|
||||
|
|
@ -190,6 +187,7 @@ class CoinProtocol(MMGenObject):
|
|||
"""
|
||||
secp256k1_ge = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
|
||||
privkey_len = 32
|
||||
pubkey_types = ('std',)
|
||||
|
||||
def preprocess_key(self,sec,pubkey_type):
|
||||
# Key must be non-zero and less than group order of secp256k1 curve
|
||||
|
|
@ -240,13 +238,12 @@ class CoinProtocol(MMGenObject):
|
|||
start_subsidy = 50
|
||||
ignore_daemon_version = False
|
||||
|
||||
def hex2wif(self,hexpriv,pubkey_type,compressed): # input is preprocessed hex
|
||||
sec = bytes.fromhex(hexpriv)
|
||||
assert len(sec) == self.privkey_len, f'{len(sec)} bytes: incorrect private key length!'
|
||||
def bytes2wif(self,privbytes,pubkey_type,compressed): # input is preprocessed hex
|
||||
assert len(privbytes) == self.privkey_len, f'{len(privbytes)} bytes: incorrect private key length!'
|
||||
assert pubkey_type in self.wif_ver_num, f'{pubkey_type!r}: invalid pubkey_type'
|
||||
return _b58chk_encode(
|
||||
bytes.fromhex(self.wif_ver_num[pubkey_type])
|
||||
+ sec
|
||||
+ privbytes
|
||||
+ (b'',b'\x01')[bool(compressed)])
|
||||
|
||||
def parse_wif(self,wif):
|
||||
|
|
@ -288,24 +285,24 @@ class CoinProtocol(MMGenObject):
|
|||
return self.parse_addr_bytes(_b58chk_decode(addr))
|
||||
|
||||
def pubhash2addr(self,pubkey_hash,p2sh):
|
||||
assert len(pubkey_hash) == 40, f'{len(pubkey_hash)}: invalid length for pubkey hash'
|
||||
return _b58chk_encode(bytes.fromhex(
|
||||
self.addr_fmt_to_ver_bytes(('p2pkh','p2sh')[p2sh],return_hex=True) + pubkey_hash
|
||||
))
|
||||
assert len(pubkey_hash) == 20, f'{len(pubkey_hash)}: invalid length for pubkey hash'
|
||||
return _b58chk_encode(
|
||||
self.addr_fmt_to_ver_bytes(('p2pkh','p2sh')[p2sh],return_hex=False) + pubkey_hash
|
||||
)
|
||||
|
||||
# Segwit:
|
||||
def pubhex2redeem_script(self,pubhex):
|
||||
def pubkey2redeem_script(self,pubkey):
|
||||
# https://bitcoincore.org/en/segwit_wallet_dev/
|
||||
# The P2SH redeemScript is always 22 bytes. It starts with a OP_0, followed
|
||||
# by a canonical push of the keyhash (i.e. 0x0014{20-byte keyhash})
|
||||
return self.witness_vernum_hex + '14' + hash160(pubhex)
|
||||
return bytes.fromhex(self.witness_vernum_hex + '14') + hash160(pubkey)
|
||||
|
||||
def pubhex2segwitaddr(self,pubhex):
|
||||
def pubkey2segwitaddr(self,pubkey):
|
||||
return self.pubhash2addr(
|
||||
hash160( self.pubhex2redeem_script(pubhex)), p2sh=True )
|
||||
hash160( self.pubkey2redeem_script(pubkey)), p2sh=True )
|
||||
|
||||
def pubhash2bech32addr(self,pubhash):
|
||||
d = list(bytes.fromhex(pubhash))
|
||||
d = list(pubhash)
|
||||
return bech32.bech32_encode(self.bech32_hrp,[self.witness_vernum]+bech32.convertbits(d,8,5))
|
||||
|
||||
class BitcoinTestnet(Bitcoin):
|
||||
|
|
@ -329,8 +326,8 @@ class CoinProtocol(MMGenObject):
|
|||
max_tx_fee = BCHAmt('0.1')
|
||||
ignore_daemon_version = False
|
||||
|
||||
def pubhex2redeem_script(self,pubhex): raise NotImplementedError
|
||||
def pubhex2segwitaddr(self,pubhex): raise NotImplementedError
|
||||
def pubkey2redeem_script(self,pubkey): raise NotImplementedError
|
||||
def pubkey2segwitaddr(self,pubkey): raise NotImplementedError
|
||||
|
||||
class BitcoinCashTestnet(BitcoinCash):
|
||||
addr_ver_bytes = { '6f': 'p2pkh', 'c4': 'p2sh' }
|
||||
|
|
@ -365,10 +362,10 @@ class CoinProtocol(MMGenObject):
|
|||
|
||||
class DummyWIF:
|
||||
|
||||
def hex2wif(self,hexpriv,pubkey_type,compressed):
|
||||
def bytes2wif(self,privbytes,pubkey_type,compressed):
|
||||
assert pubkey_type == self.pubkey_type, f'{pubkey_type}: invalid pubkey_type for {self.name} protocol!'
|
||||
assert compressed == False, f'{self.name} protocol does not support compressed pubkeys!'
|
||||
return hexpriv
|
||||
return privbytes.hex()
|
||||
|
||||
def parse_wif(self,wif):
|
||||
return parsed_wif(
|
||||
|
|
@ -427,9 +424,9 @@ class CoinProtocol(MMGenObject):
|
|||
return ''.join(addr[i].upper() if int(h[i],16) > 7 else addr[i] for i in range(len(addr)))
|
||||
|
||||
def pubhash2addr(self,pubkey_hash,p2sh):
|
||||
assert len(pubkey_hash) == 40, f'{len(pubkey_hash)}: invalid length for {self.name} pubkey hash'
|
||||
assert len(pubkey_hash) == 20, f'{len(pubkey_hash)}: invalid length for {self.name} pubkey hash'
|
||||
assert not p2sh, f'{self.name} protocol has no P2SH address format'
|
||||
return pubkey_hash
|
||||
return pubkey_hash.hex()
|
||||
|
||||
class EthereumTestnet(Ethereum):
|
||||
chain_names = ['kovan','goerli','rinkeby']
|
||||
|
|
@ -452,6 +449,7 @@ class CoinProtocol(MMGenObject):
|
|||
base_coin = 'ZEC'
|
||||
addr_ver_bytes = { '1cb8': 'p2pkh', '1cbd': 'p2sh', '169a': 'zcash_z', 'a8abd3': 'viewkey' }
|
||||
wif_ver_num = { 'std': '80', 'zcash_z': 'ab36' }
|
||||
pubkey_types = ('std','zcash_z')
|
||||
mmtypes = ('L','C','Z')
|
||||
mmcaps = ('key','addr')
|
||||
dfl_mmtype = 'L'
|
||||
|
|
@ -473,9 +471,9 @@ class CoinProtocol(MMGenObject):
|
|||
|
||||
def pubhash2addr(self,pubkey_hash,p2sh):
|
||||
hash_len = len(pubkey_hash)
|
||||
if hash_len == 40:
|
||||
if hash_len == 20:
|
||||
return super().pubhash2addr(pubkey_hash,p2sh)
|
||||
elif hash_len == 128:
|
||||
elif hash_len == 64:
|
||||
raise NotImplementedError('Zcash z-addresses do not support pubhash2addr()')
|
||||
else:
|
||||
raise ValueError(f'{hash_len}: incorrect pubkey_hash length')
|
||||
|
|
@ -492,6 +490,7 @@ class CoinProtocol(MMGenObject):
|
|||
addr_ver_bytes = { '12': 'monero', '2a': 'monero_sub' }
|
||||
addr_len = 68
|
||||
wif_ver_num = {}
|
||||
pubkey_types = ('monero',)
|
||||
mmtypes = ('M',)
|
||||
dfl_mmtype = 'M'
|
||||
pubkey_type = 'monero' # required by DummyWIF
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ class MMGenToolCmds(metaclass=MMGenToolCmdMeta):
|
|||
else:
|
||||
return gd(
|
||||
at,
|
||||
KeyGenerator(self.proto,at),
|
||||
KeyGenerator(self.proto,at.pubkey_type),
|
||||
AddrGenerator(self.proto,at),
|
||||
)
|
||||
|
||||
|
|
@ -364,7 +364,7 @@ class MMGenToolCmdUtil(MMGenToolCmds):
|
|||
|
||||
def hash160(self,hexstr:'sstr'):
|
||||
"compute ripemd160(sha256(data)) (convert hex pubkey to hex addr)"
|
||||
return hash160(hexstr)
|
||||
return hash160(bytes.fromhex(hexstr)).hex()
|
||||
|
||||
def hash256(self,string_or_bytes:str,file_input=False,hex_input=False): # TODO: handle stdin
|
||||
"compute sha256(sha256(data)) (double sha256)"
|
||||
|
|
@ -458,19 +458,19 @@ class MMGenToolCmdCoin(MMGenToolCmds):
|
|||
def randpair(self):
|
||||
"generate a random private key/address pair"
|
||||
gd = self.init_generators()
|
||||
privhex = PrivKey(
|
||||
privkey = PrivKey(
|
||||
self.proto,
|
||||
get_random(32),
|
||||
pubkey_type = gd.at.pubkey_type,
|
||||
compressed = gd.at.compressed )
|
||||
addr = gd.ag.to_addr(gd.kg.to_pubhex(privhex))
|
||||
return (privhex.wif,addr)
|
||||
addr = gd.ag.to_addr(gd.kg.gen_data(privkey))
|
||||
return ( privkey.wif, addr )
|
||||
|
||||
def wif2hex(self,wifkey:'sstr'):
|
||||
"convert a private key from WIF to hex format"
|
||||
return PrivKey(
|
||||
self.proto,
|
||||
wif = wifkey )
|
||||
wif = wifkey ).hex()
|
||||
|
||||
def hex2wif(self,privhex:'sstr'):
|
||||
"convert a private key from hex to WIF format"
|
||||
|
|
@ -484,31 +484,31 @@ class MMGenToolCmdCoin(MMGenToolCmds):
|
|||
def wif2addr(self,wifkey:'sstr'):
|
||||
"generate a coin address from a key in WIF format"
|
||||
gd = self.init_generators()
|
||||
privhex = PrivKey(
|
||||
privkey = PrivKey(
|
||||
self.proto,
|
||||
wif = wifkey )
|
||||
addr = gd.ag.to_addr(gd.kg.to_pubhex(privhex))
|
||||
addr = gd.ag.to_addr(gd.kg.gen_data(privkey))
|
||||
return addr
|
||||
|
||||
def wif2redeem_script(self,wifkey:'sstr'): # new
|
||||
"convert a WIF private key to a Segwit P2SH-P2WPKH redeem script"
|
||||
assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit'
|
||||
gd = self.init_generators()
|
||||
privhex = PrivKey(
|
||||
privkey = PrivKey(
|
||||
self.proto,
|
||||
wif = wifkey )
|
||||
return gd.ag.to_segwit_redeem_script(gd.kg.to_pubhex(privhex))
|
||||
return gd.ag.to_segwit_redeem_script(gd.kg.gen_data(privkey))
|
||||
|
||||
def wif2segwit_pair(self,wifkey:'sstr'):
|
||||
"generate both a Segwit P2SH-P2WPKH redeem script and address from WIF"
|
||||
assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit'
|
||||
gd = self.init_generators()
|
||||
pubhex = gd.kg.to_pubhex(PrivKey(
|
||||
data = gd.kg.gen_data(PrivKey(
|
||||
self.proto,
|
||||
wif = wifkey ))
|
||||
return (
|
||||
gd.ag.to_segwit_redeem_script(pubhex),
|
||||
gd.ag.to_addr(pubhex) )
|
||||
gd.ag.to_segwit_redeem_script(data),
|
||||
gd.ag.to_addr(data) )
|
||||
|
||||
def privhex2addr(self,privhex:'sstr',output_pubhex=False):
|
||||
"generate coin address from raw private key data in hexadecimal format"
|
||||
|
|
@ -518,8 +518,8 @@ class MMGenToolCmdCoin(MMGenToolCmds):
|
|||
bytes.fromhex(privhex),
|
||||
compressed = gd.at.compressed,
|
||||
pubkey_type = gd.at.pubkey_type )
|
||||
ph = gd.kg.to_pubhex(pk)
|
||||
return ph if output_pubhex else gd.ag.to_addr(ph)
|
||||
data = gd.kg.gen_data(pk)
|
||||
return data.pubkey.hex() if output_pubhex else gd.ag.to_addr(data)
|
||||
|
||||
def privhex2pubhex(self,privhex:'sstr'): # new
|
||||
"generate a hex public key from a hex private key"
|
||||
|
|
@ -527,30 +527,32 @@ class MMGenToolCmdCoin(MMGenToolCmds):
|
|||
|
||||
def pubhex2addr(self,pubkeyhex:'sstr'):
|
||||
"convert a hex pubkey to an address"
|
||||
pubkey = bytes.fromhex(pubkeyhex)
|
||||
if self.mmtype.name == 'segwit':
|
||||
return self.proto.pubhex2segwitaddr(pubkeyhex)
|
||||
return self.proto.pubkey2segwitaddr( pubkey )
|
||||
else:
|
||||
return self.pubhash2addr(hash160(pubkeyhex))
|
||||
return self.pubhash2addr( hash160(pubkey).hex() )
|
||||
|
||||
def pubhex2redeem_script(self,pubkeyhex:'sstr'): # new
|
||||
"convert a hex pubkey to a Segwit P2SH-P2WPKH redeem script"
|
||||
assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit'
|
||||
return self.proto.pubhex2redeem_script(pubkeyhex)
|
||||
return self.proto.pubkey2redeem_script( bytes.fromhex(pubkeyhex) ).hex()
|
||||
|
||||
def redeem_script2addr(self,redeem_scripthex:'sstr'): # new
|
||||
"convert a Segwit P2SH-P2WPKH redeem script to an address"
|
||||
assert self.mmtype.name == 'segwit', 'This command is meaningful only for --type=segwit'
|
||||
assert redeem_scripthex[:4] == '0014', f'{redeem_scripthex!r}: invalid redeem script'
|
||||
assert len(redeem_scripthex) == 44, f'{len(redeem_scripthex)//2} bytes: invalid redeem script length'
|
||||
return self.pubhash2addr(hash160(redeem_scripthex))
|
||||
return self.pubhash2addr( hash160(bytes.fromhex(redeem_scripthex)).hex() )
|
||||
|
||||
def pubhash2addr(self,pubhashhex:'sstr'):
|
||||
"convert public key hash to address"
|
||||
pubhash = bytes.fromhex(pubhashhex)
|
||||
if self.mmtype.name == 'bech32':
|
||||
return self.proto.pubhash2bech32addr(pubhashhex)
|
||||
return self.proto.pubhash2bech32addr( pubhash )
|
||||
else:
|
||||
gd = self.init_generators('addrtype_only')
|
||||
return self.proto.pubhash2addr(pubhashhex,gd.at.addr_fmt=='p2sh')
|
||||
return self.proto.pubhash2addr( pubhash, gd.at.addr_fmt=='p2sh' )
|
||||
|
||||
def addr2pubhash(self,addr:'sstr'):
|
||||
"convert coin address to public key hash"
|
||||
|
|
|
|||
|
|
@ -96,11 +96,11 @@ def addr2scriptPubKey(proto,addr):
|
|||
|
||||
def scriptPubKey2addr(proto,s):
|
||||
if len(s) == 50 and s[:6] == '76a914' and s[-4:] == '88ac':
|
||||
return proto.pubhash2addr(s[6:-4],p2sh=False),'p2pkh'
|
||||
return proto.pubhash2addr(bytes.fromhex(s[6:-4]),p2sh=False),'p2pkh'
|
||||
elif len(s) == 46 and s[:4] == 'a914' and s[-2:] == '87':
|
||||
return proto.pubhash2addr(s[4:-2],p2sh=True),'p2sh'
|
||||
return proto.pubhash2addr(bytes.fromhex(s[4:-2]),p2sh=True),'p2sh'
|
||||
elif len(s) == 44 and s[:4] == proto.witness_vernum_hex + '14':
|
||||
return proto.pubhash2bech32addr(s[4:]),'bech32'
|
||||
return proto.pubhash2bech32addr(bytes.fromhex(s[4:])),'bech32'
|
||||
else:
|
||||
raise NotImplementedError(f'Unknown scriptPubKey ({s})')
|
||||
|
||||
|
|
@ -1260,7 +1260,7 @@ class MMGenTX:
|
|||
e['amount'] = e['amt']
|
||||
del e['amt']
|
||||
if d.mmtype == 'S':
|
||||
e['redeemScript'] = ag.to_segwit_redeem_script(kg.to_pubhex(keydict[d.addr]))
|
||||
e['redeemScript'] = ag.to_segwit_redeem_script(kg.gen_data(keydict[d.addr]))
|
||||
sig_data.append(e)
|
||||
|
||||
msg_r(f'Signing transaction{tx_num_str}...')
|
||||
|
|
|
|||
|
|
@ -155,13 +155,13 @@ class GenTool(object):
|
|||
def run_tool(self,sec):
|
||||
vcoin = 'BTC' if self.proto.coin == 'BCH' else self.proto.coin
|
||||
ret = self.run(sec,vcoin)
|
||||
self.data[sec] = ret._asdict()
|
||||
self.data[sec.hex()] = ret._asdict()
|
||||
return ret
|
||||
|
||||
class GenToolEthkey(GenTool):
|
||||
desc = 'ethkey'
|
||||
def run(self,sec,vcoin):
|
||||
o = get_cmd_output(['ethkey','info',sec])
|
||||
o = get_cmd_output(['ethkey','info',sec.hex()])
|
||||
return gtr(o[0].split()[1],o[-1].split()[1],None)
|
||||
|
||||
class GenToolKeyconv(GenTool):
|
||||
|
|
@ -194,10 +194,10 @@ class GenToolPycoin(GenTool):
|
|||
vcoin = ci.external_tests['testnet']['pycoin'][vcoin]
|
||||
network = self.nfnc(vcoin)
|
||||
key = network.keys.private(
|
||||
secret_exponent = int(sec,16),
|
||||
secret_exponent = int(sec.hex(),16),
|
||||
is_compressed = self.addr_type.name != 'legacy' )
|
||||
if key is None:
|
||||
die(1,f'can’t parse {sec}')
|
||||
die(1,f'can’t parse {sec.hex()}')
|
||||
if self.addr_type.name in ('segwit','bech32'):
|
||||
hash160_c = key.hash160(is_compressed=True)
|
||||
if self.addr_type.name == 'segwit':
|
||||
|
|
@ -221,10 +221,10 @@ class GenToolMoneropy(GenTool):
|
|||
self.mpa = moneropy.account
|
||||
|
||||
def run(self,sec,vcoin):
|
||||
if sec in self.data:
|
||||
return gtr(**self.data[sec])
|
||||
if sec.hex() in self.data:
|
||||
return gtr(**self.data[sec.hex()])
|
||||
else:
|
||||
sk,vk,addr = self.mpa.account_from_spend_key(sec) # VERY slow!
|
||||
sk,vk,addr = self.mpa.account_from_spend_key(sec.hex()) # VERY slow!
|
||||
return gtr(sk,addr,vk)
|
||||
|
||||
def find_or_check_tool(proto,addr_type,toolname):
|
||||
|
|
@ -274,14 +274,14 @@ def do_ab_test(proto,addr_type,kg_b,rounds,backend_num):
|
|||
qmsg_r(f'\rRound {i+1}/{trounds} ')
|
||||
last_t = time.time()
|
||||
sec = PrivKey(proto,in_bytes,compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type)
|
||||
data = kg_a.to_pubhex(sec)
|
||||
data = kg_a.gen_data(sec)
|
||||
ag = AddrGenerator(proto,addr_type)
|
||||
a_addr = ag.to_addr(data)
|
||||
tinfo = (in_bytes,sec,sec.wif,type(kg_a).__name__,type(kg_b).__name__)
|
||||
a_vk = None
|
||||
|
||||
def do_msg():
|
||||
vmsg( fs.format( b=in_bytes.hex(), r=sec, k=sec.wif, v=a_vk, a=a_addr ))
|
||||
vmsg( fs.format( b=in_bytes.hex(), r=sec.hex(), k=sec.wif, v=a_vk, a=a_addr ))
|
||||
|
||||
if isinstance(kg_b,GenTool):
|
||||
def run_tool():
|
||||
|
|
@ -294,12 +294,12 @@ def do_ab_test(proto,addr_type,kg_b,rounds,backend_num):
|
|||
a_vk = run_tool()
|
||||
do_msg()
|
||||
else:
|
||||
test_equal( 'addresses', a_addr, ag.to_addr(kg_b.to_pubhex(sec)), *tinfo )
|
||||
test_equal( 'addresses', a_addr, ag.to_addr(kg_b.gen_data(sec)), *tinfo )
|
||||
do_msg()
|
||||
|
||||
qmsg_r(f'\rRound {n+1}/{trounds} ')
|
||||
|
||||
kg_a = KeyGenerator(proto,addr_type,backend_num)
|
||||
kg_a = KeyGenerator(proto,addr_type.pubkey_type,backend_num)
|
||||
if type(kg_a) == type(kg_b):
|
||||
rdie(1,'Key generators are the same!')
|
||||
|
||||
|
|
@ -349,16 +349,13 @@ def do_ab_test(proto,addr_type,kg_b,rounds,backend_num):
|
|||
def init_tool(proto,addr_type,toolname):
|
||||
return globals()['GenTool'+capfirst(toolname.replace('-','_'))](proto,addr_type)
|
||||
|
||||
def get_backends(proto,foo):
|
||||
return (1,) if isinstance(proto,CoinProtocol.Zcash) else (1,2)
|
||||
|
||||
def ab_test(proto,gen_num,rounds,toolname_or_gen2_num):
|
||||
|
||||
addr_type = MMGenAddrType( proto=proto, id_str=opt.type or proto.dfl_mmtype )
|
||||
|
||||
if is_int(toolname_or_gen2_num):
|
||||
assert gen_num != 'all', "'all' must be used only with external tool"
|
||||
tool = KeyGenerator( proto, addr_type, int(toolname_or_gen2_num) )
|
||||
tool = KeyGenerator( proto, addr_type.pubkey_type, int(toolname_or_gen2_num) )
|
||||
else:
|
||||
toolname = find_or_check_tool( proto, addr_type, toolname_or_gen2_num )
|
||||
if toolname == None:
|
||||
|
|
@ -367,12 +364,12 @@ def ab_test(proto,gen_num,rounds,toolname_or_gen2_num):
|
|||
tool = init_tool( proto, addr_type, toolname )
|
||||
|
||||
if gen_num == 'all': # check all backends against external tool
|
||||
for n in range(len(get_backends(proto,addr_type.pubkey_type))):
|
||||
for n in range(len(get_backends(addr_type.pubkey_type))):
|
||||
do_ab_test( proto, addr_type, tool, rounds, n+1 )
|
||||
else: # check specific backend against external tool or another backend
|
||||
do_ab_test( proto, addr_type, tool, rounds, int(gen_num) )
|
||||
do_ab_test( proto, addr_type, tool, rounds, gen_num )
|
||||
|
||||
def speed_test(proto,addr_type,kg,ag,rounds):
|
||||
def speed_test(proto,kg,ag,rounds):
|
||||
qmsg(green('Testing speed of address generator {!r} for coin {}'.format(
|
||||
type(kg).__name__,
|
||||
proto.coin )))
|
||||
|
|
@ -387,8 +384,8 @@ def speed_test(proto,addr_type,kg,ag,rounds):
|
|||
if time.time() - last_t >= 0.1:
|
||||
qmsg_r(f'\rRound {i+1}/{rounds} ')
|
||||
last_t = time.time()
|
||||
sec = PrivKey( proto, seed+pack('I', i), compressed=addr_type.compressed, pubkey_type=addr_type.pubkey_type )
|
||||
addr = ag.to_addr(kg.to_pubhex(sec))
|
||||
sec = PrivKey( proto, seed+pack('I', i), compressed=ag.compressed, pubkey_type=ag.pubkey_type )
|
||||
addr = ag.to_addr(kg.gen_data(sec))
|
||||
vmsg(f'\nkey: {sec.wif}\naddr: {addr}\n')
|
||||
qmsg(
|
||||
f'\rRound {i+1}/{rounds} ' +
|
||||
|
|
@ -415,9 +412,9 @@ def dump_test(proto,kg,ag,filename):
|
|||
b_sec = PrivKey(proto,wif=b_wif)
|
||||
except:
|
||||
die(2,f'\nInvalid {proto.network} WIF address in dump file: {b_wif}')
|
||||
a_addr = ag.to_addr(kg.to_pubhex(b_sec))
|
||||
a_addr = ag.to_addr(kg.gen_data(b_sec))
|
||||
vmsg(f'\nwif: {b_wif}\naddr: {b_addr}\n')
|
||||
tinfo = (b_sec,b_sec,b_wif,type(kg).__name__,filename)
|
||||
tinfo = (b_sec,b_sec.hex(),b_wif,type(kg).__name__,filename)
|
||||
test_equal('addresses',a_addr,b_addr,*tinfo)
|
||||
|
||||
qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
|
||||
|
|
@ -481,10 +478,10 @@ def main():
|
|||
for proto in protos:
|
||||
ab_test( proto, pa.gen_num, pa.rounds, toolname_or_gen2_num=pa.arg )
|
||||
else:
|
||||
kg = KeyGenerator( proto, addr_type, pa.gen_num )
|
||||
kg = KeyGenerator( proto, addr_type.pubkey_type, pa.gen_num )
|
||||
ag = AddrGenerator( proto, addr_type )
|
||||
if pa.test == 'speed':
|
||||
speed_test( proto, addr_type, kg, ag, pa.rounds )
|
||||
speed_test( proto, kg, ag, pa.rounds )
|
||||
elif pa.test == 'dump':
|
||||
dump_test( proto, kg, ag, filename=pa.arg )
|
||||
|
||||
|
|
@ -499,6 +496,7 @@ from mmgen.protocol import init_proto,init_proto_from_opts,CoinProtocol,init_gen
|
|||
from mmgen.altcoin import CoinInfo as ci
|
||||
from mmgen.key import PrivKey
|
||||
from mmgen.addr import KeyGenerator,AddrGenerator,MMGenAddrType
|
||||
from mmgen.keygen import get_backends
|
||||
|
||||
sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
|
||||
cmd_args = opts.init(opts_data,add_opts=['exact_output'])
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ opts_data = {
|
|||
"""
|
||||
},
|
||||
'code': {
|
||||
'options': lambda s: s.format(
|
||||
'options': lambda help_notes,s: s.format(
|
||||
kgs=help_notes('keygen_backends'),
|
||||
coin_id=help_notes('coin_id'),
|
||||
g=g,
|
||||
),
|
||||
'notes': lambda s: s.format(nn='a note'),
|
||||
|
|
|
|||
|
|
@ -227,8 +227,13 @@ tests = {
|
|||
},
|
||||
'PubKey': {
|
||||
'arg1': 's',
|
||||
'bad': ({'s':1,'privkey':False},{'s':'F00BAA12','privkey':False},),
|
||||
'good': ({'s':'deadbeef','privkey':privkey},) # TODO: add real pubkeys
|
||||
'bad': (
|
||||
{'s':1, 'compressed':True },
|
||||
{'s':'F00BAA12','compressed':False},
|
||||
),
|
||||
'good': ( # TODO: add real pubkeys
|
||||
{'s':bytes.fromhex('deadbeef'),'compressed':True},
|
||||
)
|
||||
},
|
||||
'PrivKey': {
|
||||
'arg1': 'proto',
|
||||
|
|
@ -246,11 +251,11 @@ tests = {
|
|||
),
|
||||
'good': (
|
||||
{'proto':proto, 'wif':'5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb',
|
||||
'ret':'e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c'},
|
||||
'ret':bytes.fromhex('e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c')},
|
||||
{'proto':proto, 'wif':'KwWr9rDh8KK5TtDa3HLChEvQXNYcUXpwhRFUPc5uSNnMtqNKLFhk',
|
||||
'ret':'08d0ed83b64b68d56fa064be48e2385060ed205be2b1e63cd56d218038c3a05f'},
|
||||
{'proto':proto, 's':r32,'compressed':False,'pubkey_type':'std','ret':r32.hex()},
|
||||
{'proto':proto, 's':r32,'compressed':True,'pubkey_type':'std','ret':r32.hex()}
|
||||
'ret':bytes.fromhex('08d0ed83b64b68d56fa064be48e2385060ed205be2b1e63cd56d218038c3a05f')},
|
||||
{'proto':proto, 's':r32,'compressed':False,'pubkey_type':'std','ret':r32},
|
||||
{'proto':proto, 's':r32,'compressed':True,'pubkey_type':'std','ret':r32}
|
||||
)
|
||||
},
|
||||
'AddrListID': { # a rather pointless test, but do it anyway
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ tests = {
|
|||
),
|
||||
'good': (
|
||||
{'proto':proto, 'wif':'93HsQEpH75ibaUJYi3QwwiQxnkW4dUuYFPXZxcbcKds7XrqHkY6',
|
||||
'ret':'e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c'},
|
||||
'ret':bytes.fromhex('e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c')},
|
||||
{'proto':proto, 'wif':'cMsqcmDYZP1LdKgqRh9L4ZRU9br28yvdmTPwW2YQwVSN9aQiMAoR',
|
||||
'ret':'08d0ed83b64b68d56fa064be48e2385060ed205be2b1e63cd56d218038c3a05f'},
|
||||
{'proto':proto, 's':r32,'compressed':False,'pubkey_type':'std','ret':r32.hex()},
|
||||
{'proto':proto, 's':r32,'compressed':True,'pubkey_type':'std','ret':r32.hex()}
|
||||
'ret':bytes.fromhex('08d0ed83b64b68d56fa064be48e2385060ed205be2b1e63cd56d218038c3a05f')},
|
||||
{'proto':proto, 's':r32,'compressed':False,'pubkey_type':'std','ret':r32},
|
||||
{'proto':proto, 's':r32,'compressed':True,'pubkey_type':'std','ret':r32}
|
||||
),
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,11 +63,11 @@ tests = {
|
|||
),
|
||||
'good': (
|
||||
{'proto':proto, 'wif':'6ufJhtQQiRYA3w2QvDuXNXuLgPFp15i3HR1Wp8An2mx1JnhhJAh',
|
||||
'ret':'470a974ffca9fca1299b706b09142077bea3acbab6d6480b87dbba79d5fd279b'},
|
||||
'ret':bytes.fromhex('470a974ffca9fca1299b706b09142077bea3acbab6d6480b87dbba79d5fd279b')},
|
||||
{'proto':proto, 'wif':'T41Fm7J3mtZLKYPMCLVSFARz4QF8nvSDhLAfW97Ds56Zm9hRJgn8',
|
||||
'ret':'1c6feab55a4c3b4ad1823d4ecacd1565c64228c01828cf44fb4db1e2d82c3d56'},
|
||||
{'proto':proto, 's':r32,'compressed':False,'pubkey_type':'std','ret':r32.hex()},
|
||||
{'proto':proto, 's':r32,'compressed':True,'pubkey_type':'std','ret':r32.hex()}
|
||||
'ret':bytes.fromhex('1c6feab55a4c3b4ad1823d4ecacd1565c64228c01828cf44fb4db1e2d82c3d56')},
|
||||
{'proto':proto, 's':r32,'compressed':False,'pubkey_type':'std','ret':r32},
|
||||
{'proto':proto, 's':r32,'compressed':True,'pubkey_type':'std','ret':r32}
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ tests = {
|
|||
),
|
||||
'good': (
|
||||
{'proto':proto, 'wif':'92iqzh6NqiKawyB1ronw66YtEHrU4rxRJ5T4aHniZqvuSVZS21f',
|
||||
'ret':'95b2aa7912550eacdd3844dcc14bee08ce7bc2434ad4858beb136021e945afeb'},
|
||||
'ret':bytes.fromhex('95b2aa7912550eacdd3844dcc14bee08ce7bc2434ad4858beb136021e945afeb')},
|
||||
{'proto':proto, 'wif':'cSaJAXBAm9ooHpVJgoxqjDG3AcareFy29Cz8mhnNTRijjv2HLgta',
|
||||
'ret':'94fa8b90c11fea8fb907c9376b919534b0a75b9a9621edf71a78753544b4101c'},
|
||||
{'proto':proto, 's':r32,'compressed':False,'pubkey_type':'std','ret':r32.hex()},
|
||||
{'proto':proto, 's':r32,'compressed':True,'pubkey_type':'std','ret':r32.hex()}
|
||||
'ret':bytes.fromhex('94fa8b90c11fea8fb907c9376b919534b0a75b9a9621edf71a78753544b4101c')},
|
||||
{'proto':proto, 's':r32,'compressed':False,'pubkey_type':'std','ret':r32},
|
||||
{'proto':proto, 's':r32,'compressed':True,'pubkey_type':'std','ret':r32}
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -303,34 +303,35 @@ i_alts='Gen-only altcoin'
|
|||
s_alts='The following tests will test generation operations for all supported altcoins'
|
||||
t_alts="
|
||||
- # speed tests, no verification:
|
||||
- $gentest_py --coin=etc 2 $rounds
|
||||
- $gentest_py --coin=etc --use-internal-keccak-module 2 $rounds_min
|
||||
- $gentest_py --coin=eth 2 $rounds
|
||||
- $gentest_py --coin=eth --use-internal-keccak-module 2 $rounds_min
|
||||
- $gentest_py --coin=xmr 2 $rounds
|
||||
- $gentest_py --coin=xmr --use-internal-keccak-module 2 $rounds_min
|
||||
- $gentest_py --coin=zec 2 $rounds
|
||||
- $gentest_py --coin=zec --type=zcash_z 2 $rounds_mid
|
||||
- $gentest_py --coin=etc 1 $rounds
|
||||
- $gentest_py --coin=etc --use-internal-keccak-module 1 $rounds_min
|
||||
- $gentest_py --coin=eth 1 $rounds
|
||||
- $gentest_py --coin=eth --use-internal-keccak-module 1 $rounds_min
|
||||
- $gentest_py --coin=xmr 1 $rounds
|
||||
- $gentest_py --coin=xmr --use-internal-keccak-module 1 $rounds_min
|
||||
- $gentest_py --coin=zec 1 $rounds
|
||||
- $gentest_py --coin=zec --type=zcash_z 1 $rounds_mid
|
||||
- # verification against external libraries and tools:
|
||||
- # pycoin
|
||||
- $gentest_py --all --type=legacy 2:pycoin $rounds
|
||||
- $gentest_py --all --type=compressed 2:pycoin $rounds
|
||||
- $gentest_py --all --type=segwit 2:pycoin $rounds
|
||||
- $gentest_py --all --type=bech32 2:pycoin $rounds
|
||||
- $gentest_py --all-coins --type=legacy 1:pycoin $rounds
|
||||
- $gentest_py --all-coins --type=compressed 1:pycoin $rounds
|
||||
- $gentest_py --all-coins --type=segwit 1:pycoin $rounds
|
||||
- $gentest_py --all-coins --type=bech32 1:pycoin $rounds
|
||||
|
||||
- $gentest_py --all --type=legacy --testnet=1 2:pycoin $rounds
|
||||
- $gentest_py --all --type=compressed --testnet=1 2:pycoin $rounds
|
||||
- $gentest_py --all --type=segwit --testnet=1 2:pycoin $rounds
|
||||
- $gentest_py --all --type=bech32 --testnet=1 2:pycoin $rounds
|
||||
- $gentest_py --all-coins --type=legacy --testnet=1 1:pycoin $rounds
|
||||
- $gentest_py --all-coins --type=compressed --testnet=1 1:pycoin $rounds
|
||||
- $gentest_py --all-coins --type=segwit --testnet=1 1:pycoin $rounds
|
||||
- $gentest_py --all-coins --type=bech32 --testnet=1 1:pycoin $rounds
|
||||
- # keyconv
|
||||
- $gentest_py --all --type=legacy 2:keyconv $rounds
|
||||
- $gentest_py --all --type=compressed 2:keyconv $rounds
|
||||
- $gentest_py --all-coins --type=legacy 1:keyconv $rounds
|
||||
- $gentest_py --all-coins --type=compressed 1:keyconv $rounds
|
||||
e # ethkey
|
||||
e $gentest_py --all 2:ethkey $rounds
|
||||
e $gentest_py --coin=eth 1:ethkey $rounds
|
||||
e $gentest_py --coin=eth --use-internal-keccak-module 2:ethkey $rounds_mid
|
||||
m # moneropy
|
||||
m $gentest_py --all --coin=xmr 2:moneropy $rounds_min # very slow, be patient!
|
||||
m $gentest_py --coin=xmr all:moneropy $rounds_mid # very slow, please be patient!
|
||||
z # zcash-mini
|
||||
z $gentest_py --all --coin=zec --type=zcash_z 1:zcash-mini $rounds_mid
|
||||
z $gentest_py --coin=zec --type=zcash_z all:zcash-mini $rounds_mid
|
||||
"
|
||||
|
||||
[ "$MSYS2" ] && t_alts_skip='m z' # no moneropy (pysha3), zcash-mini (golang)
|
||||
|
|
@ -501,23 +502,23 @@ i_gen='Gentest'
|
|||
s_gen="The following tests will run '$gentest_py' for BTC and LTC mainnet and testnet"
|
||||
t_gen="
|
||||
- # speed tests, no verification:
|
||||
- $gentest_py --coin=btc 2 $rounds
|
||||
- $gentest_py --coin=btc --type=compressed 2 $rounds
|
||||
- $gentest_py --coin=btc --type=segwit 2 $rounds
|
||||
- $gentest_py --coin=btc --type=bech32 2 $rounds
|
||||
- $gentest_py --coin=ltc 2 $rounds
|
||||
- $gentest_py --coin=ltc --type=compressed 2 $rounds
|
||||
- $gentest_py --coin=ltc --type=segwit 2 $rounds
|
||||
- $gentest_py --coin=ltc --type=bech32 2 $rounds
|
||||
- $gentest_py --coin=btc 1 $rounds
|
||||
- $gentest_py --coin=btc --type=compressed 1 $rounds
|
||||
- $gentest_py --coin=btc --type=segwit 1 $rounds
|
||||
- $gentest_py --coin=btc --type=bech32 1 $rounds
|
||||
- $gentest_py --coin=ltc 1 $rounds
|
||||
- $gentest_py --coin=ltc --type=compressed 1 $rounds
|
||||
- $gentest_py --coin=ltc --type=segwit 1 $rounds
|
||||
- $gentest_py --coin=ltc --type=bech32 1 $rounds
|
||||
- # wallet dumps:
|
||||
- $gentest_py 2 $REFDIR/btcwallet.dump
|
||||
- $gentest_py --type=segwit 2 $REFDIR/btcwallet-segwit.dump
|
||||
- $gentest_py --type=bech32 2 $REFDIR/btcwallet-bech32.dump
|
||||
- $gentest_py --testnet=1 2 $REFDIR/btcwallet-testnet.dump
|
||||
- $gentest_py --coin=ltc 2 $REFDIR/litecoin/ltcwallet.dump
|
||||
- $gentest_py --coin=ltc --type=segwit 2 $REFDIR/litecoin/ltcwallet-segwit.dump
|
||||
- $gentest_py --coin=ltc --type=bech32 2 $REFDIR/litecoin/ltcwallet-bech32.dump
|
||||
- $gentest_py --coin=ltc --testnet=1 2 $REFDIR/litecoin/ltcwallet-testnet.dump
|
||||
- $gentest_py --type=compressed 1 $REFDIR/btcwallet.dump
|
||||
- $gentest_py --type=segwit 1 $REFDIR/btcwallet-segwit.dump
|
||||
- $gentest_py --type=bech32 1 $REFDIR/btcwallet-bech32.dump
|
||||
- $gentest_py --type=compressed --testnet=1 1 $REFDIR/btcwallet-testnet.dump
|
||||
- $gentest_py --coin=ltc --type=compressed 1 $REFDIR/litecoin/ltcwallet.dump
|
||||
- $gentest_py --coin=ltc --type=segwit 1 $REFDIR/litecoin/ltcwallet-segwit.dump
|
||||
- $gentest_py --coin=ltc --type=bech32 1 $REFDIR/litecoin/ltcwallet-bech32.dump
|
||||
- $gentest_py --coin=ltc --type=compressed --testnet=1 1 $REFDIR/litecoin/ltcwallet-testnet.dump
|
||||
- # libsecp256k1 vs python-ecdsa:
|
||||
- $gentest_py 1:2 $rounds
|
||||
- $gentest_py --type=segwit 1:2 $rounds
|
||||
|
|
@ -528,6 +529,8 @@ t_gen="
|
|||
- $gentest_py --coin=ltc --type=segwit 1:2 $rounds
|
||||
- $gentest_py --coin=ltc --testnet=1 1:2 $rounds
|
||||
- $gentest_py --coin=ltc --testnet=1 --type=segwit 1:2 $rounds
|
||||
- # all backends vs pycoin:
|
||||
- $gentest_py all:pycoin $rounds
|
||||
"
|
||||
f_gen='gentest tests completed'
|
||||
|
||||
|
|
|
|||
|
|
@ -384,7 +384,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
|
|||
rand_coinaddr = AddrGenerator(
|
||||
self.proto,
|
||||
('legacy','compressed')[non_mmgen_input_compressed]
|
||||
).to_addr(KeyGenerator(self.proto,'std').to_pubhex(privkey))
|
||||
).to_addr(KeyGenerator(self.proto,'std').gen_data(privkey))
|
||||
of = joinpath(self.cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn)
|
||||
write_data_to_file(
|
||||
outfile = of,
|
||||
|
|
@ -422,7 +422,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
|
|||
t = ('compressed','segwit')['S' in self.proto.mmtypes]
|
||||
from mmgen.addr import KeyGenerator,AddrGenerator
|
||||
rand_coinaddr = AddrGenerator(self.proto,t).to_addr(
|
||||
KeyGenerator(self.proto,'std').to_pubhex(privkey)
|
||||
KeyGenerator(self.proto,'std').gen_data(privkey)
|
||||
)
|
||||
|
||||
# total of two outputs must be < 10 BTC (<1000 LTC)
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ class TestSuiteRefAltcoin(TestSuiteRef,TestSuiteBase):
|
|||
('ref_addrfile_gen_zec', 'generate address file (ZEC-T)'),
|
||||
('ref_addrfile_gen_zec_z','generate address file (ZEC-Z)'),
|
||||
('ref_addrfile_gen_xmr', 'generate address file (XMR)'),
|
||||
# we test the old ed25519 library in test-release.sh, so skip this
|
||||
# ('ref_addrfile_gen_xmr_old','generate address file (XMR - old (slow) ed25519 library)'),
|
||||
# we test the unoptimized ed25519 mod in unit_tests.py, so skip this
|
||||
# ('ref_addrfile_gen_xmr_slow','generate address file (XMR - unoptimized ed25519 module)'),
|
||||
|
||||
('ref_keyaddrfile_gen_eth', 'generate key-address file (ETH)'),
|
||||
('ref_keyaddrfile_gen_etc', 'generate key-address file (ETC)'),
|
||||
|
|
@ -153,8 +153,8 @@ class TestSuiteRefAltcoin(TestSuiteRef,TestSuiteBase):
|
|||
def ref_addrfile_gen_xmr(self):
|
||||
return self.ref_altcoin_addrgen(coin='XMR',mmtype='monero')
|
||||
|
||||
def ref_addrfile_gen_xmr_old(self):
|
||||
return self.ref_altcoin_addrgen(coin='XMR',mmtype='monero',add_args=['--use-old-ed25519'])
|
||||
def ref_addrfile_gen_xmr_slow(self):
|
||||
return self.ref_altcoin_addrgen(coin='XMR',mmtype='monero',add_args=['--keygen-backend=2'])
|
||||
|
||||
def ref_keyaddrfile_gen_eth(self):
|
||||
return self.ref_altcoin_addrgen(coin='ETH',mmtype='ethereum',gen_what='key')
|
||||
|
|
|
|||
|
|
@ -908,7 +908,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
|
|||
return self.alice_add_label_badaddr( rt_pw,'Invalid coin address for this chain: ')
|
||||
|
||||
def alice_add_label_badaddr2(self):
|
||||
addr = init_proto(self.proto.coin,network='mainnet').pubhash2addr('00'*20,False) # mainnet zero address
|
||||
addr = init_proto(self.proto.coin,network='mainnet').pubhash2addr(bytes(20),False) # mainnet zero address
|
||||
return self.alice_add_label_badaddr( addr, f'Invalid coin address for this chain: {addr}' )
|
||||
|
||||
def alice_add_label_badaddr3(self):
|
||||
|
|
@ -916,7 +916,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
|
|||
return self.alice_add_label_badaddr( addr, f'MMGen address {addr!r} not found in tracking wallet' )
|
||||
|
||||
def alice_add_label_badaddr4(self):
|
||||
addr = self.proto.pubhash2addr('00'*20,False) # regtest (testnet) zero address
|
||||
addr = self.proto.pubhash2addr(bytes(20),False) # regtest (testnet) zero address
|
||||
return self.alice_add_label_badaddr( addr, f'Address {addr!r} not found in tracking wallet' )
|
||||
|
||||
def alice_remove_label1(self):
|
||||
|
|
|
|||
99
test/unit_tests_d/ut_gen.py
Executable file
99
test/unit_tests_d/ut_gen.py
Executable file
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
test.unit_tests_d.ut_gen: key/address generation unit tests for the MMGen suite
|
||||
"""
|
||||
|
||||
from mmgen.common import *
|
||||
from mmgen.protocol import init_proto
|
||||
from mmgen.key import PrivKey
|
||||
from mmgen.addr import MMGenAddrType,KeyGenerator,AddrGenerator
|
||||
from mmgen.keygen import get_backends
|
||||
|
||||
# TODO: add viewkey checks
|
||||
vectors = { # from tooltest2
|
||||
'btc': ( (
|
||||
'5HwzecKMWD82ppJK3qMKpC7ohXXAwcyAN5VgdJ9PLFaAzpBG4sX',
|
||||
'1C5VPtgq9xQ6AcTgMAR3J6GDrs72HC4pS1',
|
||||
'legacy'
|
||||
), (
|
||||
'KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm',
|
||||
'1Kz9fVSUMshzPejpzW9D95kScgA3rY6QxF',
|
||||
'compressed'
|
||||
), (
|
||||
'KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm',
|
||||
'3AhjTiWHhVJAi1s5CfKMcLzYps12x3gZhg',
|
||||
'segwit'
|
||||
), (
|
||||
'KwojSzt1VvW343mQfWQi3J537siAt5ktL2qbuCg1ZyKR8BLQ6UJm',
|
||||
'bc1q6pqnfwwakuuejpm9w52ds342f9d5u36v0qnz7c',
|
||||
'bech32' ),
|
||||
),
|
||||
'eth': ( (
|
||||
'0000000000000000000000000000000000000000000000000000000000000001',
|
||||
'7e5f4552091a69125d5dfcb7b8c2659029395bdf',
|
||||
'ethereum',
|
||||
), ),
|
||||
'xmr': ( (
|
||||
'0000000000000000000000000000000000000000000000000000000000000001',
|
||||
'42nsXK8WbVGTNayQ6Kjw5UdgqbQY5KCCufdxdCgF7NgTfjC69Mna7DJSYyie77hZTQ8H92G2HwgFhgEUYnDzrnLnQdF28r3',
|
||||
'monero',
|
||||
), ),
|
||||
'zec': ( (
|
||||
'SKxny894fJe2rmZjeuoE6GVfNkWoXfPp8337VrLLNWG56FjqVUYR',
|
||||
'zceQDpyNwek7dKqF5ZuFGj7YrNVxh7X1aPkrVxDLVxWSiZAFDEuy5C7XNV8VhyZ3ghTPQ61xjCGiyLT3wqpiN1Yi6mdmaCq',
|
||||
'zcash_z',
|
||||
), ),
|
||||
}
|
||||
|
||||
def do_test(proto,wif,addr_chk,addr_type,internal_keccak):
|
||||
|
||||
if internal_keccak:
|
||||
opt.use_internal_keccak_module = True
|
||||
add_msg = ' (internal keccak module)'
|
||||
else:
|
||||
add_msg = ''
|
||||
|
||||
at = MMGenAddrType(proto,addr_type)
|
||||
privkey = PrivKey(proto,wif=wif)
|
||||
|
||||
for n,backend in enumerate(get_backends(at.pubkey_type)):
|
||||
|
||||
kg = KeyGenerator(proto,at.pubkey_type,n+1)
|
||||
qmsg(blue(f' Testing backend {backend!r} for addr type {addr_type!r}{add_msg}'))
|
||||
|
||||
data = kg.gen_data(privkey)
|
||||
|
||||
for k,v in data._asdict().items():
|
||||
if v and k in ('pubkey','viewkey_bytes'):
|
||||
qmsg(f' {k+":":19} {v.hex()}')
|
||||
|
||||
ag = AddrGenerator(proto,addr_type)
|
||||
addr = ag.to_addr(data)
|
||||
qmsg(f' addr: {addr}\n')
|
||||
|
||||
assert addr == addr_chk, f'{addr} != {addr_chk}'
|
||||
|
||||
opt.use_internal_keccak_module = False
|
||||
|
||||
def do_tests(coin,internal_keccak=False):
|
||||
proto = init_proto(coin)
|
||||
for wif,addr,addr_type in vectors[coin]:
|
||||
do_test(proto,wif,addr,addr_type,internal_keccak)
|
||||
return True
|
||||
|
||||
class unit_tests:
|
||||
|
||||
def btc(self,name,ut):
|
||||
return do_tests('btc')
|
||||
|
||||
def eth(self,name,ut):
|
||||
do_tests('eth')
|
||||
return do_tests('eth',internal_keccak=True)
|
||||
|
||||
def xmr(self,name,ut):
|
||||
if not opt.fast:
|
||||
do_tests('xmr')
|
||||
return do_tests('xmr',internal_keccak=True)
|
||||
|
||||
def zec(self,name,ut):
|
||||
return do_tests('zec')
|
||||
Loading…
Add table
Add a link
Reference in a new issue