modularize coin protocols
- protocols are now in individual modules under `proto`
This commit is contained in:
parent
38260eece6
commit
7558c539a1
22 changed files with 491 additions and 373 deletions
|
|
@ -20,7 +20,7 @@
|
|||
addrgen.py: Address and view key generation classes for the MMGen suite
|
||||
"""
|
||||
|
||||
from .protocol import hash160,_b58chk_encode
|
||||
from .proto.btc import hash160,_b58chk_encode
|
||||
from .addr import CoinAddr,MMGenAddrType,MoneroViewKey,ZcashViewKey
|
||||
|
||||
# decorator for to_addr() and to_viewkey()
|
||||
|
|
|
|||
|
|
@ -157,9 +157,9 @@ class AddrList(MMGenObject): # Address info for a single seed ID
|
|||
mmtype = mmtype or proto.dfl_mmtype
|
||||
assert mmtype in MMGenAddrType.mmtypes, f'{mmtype}: mmtype not in {MMGenAddrType.mmtypes!r}'
|
||||
|
||||
from .protocol import CoinProtocol
|
||||
from .proto.btc import mainnet
|
||||
self.bitcoin_addrtypes = tuple(
|
||||
MMGenAddrType(CoinProtocol.Bitcoin,key).name for key in CoinProtocol.Bitcoin.mmtypes)
|
||||
MMGenAddrType(mainnet,key).name for key in mainnet.mmtypes )
|
||||
|
||||
if seed and addr_idxs and mmtype: # data from seed + idxs
|
||||
self.al_id,src = AddrListID(seed.sid,mmtype),'gen'
|
||||
|
|
@ -281,7 +281,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID
|
|||
return out
|
||||
|
||||
def gen_wallet_passwd(self,privbytes):
|
||||
from .protocol import hash256
|
||||
from .proto.btc import hash256
|
||||
return WalletPassword( hash256(privbytes)[:16].hex() )
|
||||
|
||||
def check_format(self,addr):
|
||||
|
|
|
|||
|
|
@ -556,7 +556,7 @@ class CoinInfo(object):
|
|||
return '1'
|
||||
|
||||
def phash2addr(ver_num,pk_hash):
|
||||
from .protocol import _b58chk_encode
|
||||
from .proto.btc import _b58chk_encode
|
||||
bl = ver_num.bit_length()
|
||||
ver_bytes = int.to_bytes(ver_num,bl//8 + bool(bl%8),'big')
|
||||
return _b58chk_encode(ver_bytes + pk_hash)
|
||||
|
|
@ -736,6 +736,7 @@ def init_genonly_altcoins(usr_coin=None,testnet=False):
|
|||
def create_altcoin_protos(data):
|
||||
|
||||
from .protocol import CoinProtocol
|
||||
from .proto.btc import mainnet
|
||||
|
||||
def make_proto(e,testnet=False):
|
||||
|
||||
|
|
@ -751,8 +752,8 @@ def create_altcoin_protos(data):
|
|||
CoinProtocol,
|
||||
proto,
|
||||
type(
|
||||
'CoinProtocol.' + proto,
|
||||
(CoinProtocol.Bitcoin,),
|
||||
proto,
|
||||
(mainnet,),
|
||||
{
|
||||
'base_coin': e.symbol,
|
||||
'addr_ver_bytes': dict(
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
13.1.dev011
|
||||
13.1.dev012
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ one address with no amount on the command line.
|
|||
"""
|
||||
|
||||
def txsign():
|
||||
from .protocol import CoinProtocol
|
||||
from .proto.btc import mainnet
|
||||
return """
|
||||
Transactions may contain both {pnm} or non-{pnm} input addresses.
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ source. Therefore, seed files or a key-address file for all {pnm} outputs
|
|||
must also be supplied on the command line if the data can't be found in the
|
||||
default wallet.
|
||||
""".format(
|
||||
wd = (f'{coind_exec()} wallet dump or ' if isinstance(proto,CoinProtocol.Bitcoin) else ''),
|
||||
wd = (f'{coind_exec()} wallet dump or ' if isinstance(proto,mainnet) else ''),
|
||||
pnm = g.proj_name,
|
||||
pnu = proto.name,
|
||||
pnl = g.proj_name.lower() )
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ class keygen_backend:
|
|||
|
||||
def __init__(self):
|
||||
|
||||
from .protocol import CoinProtocol
|
||||
self.proto_cls = CoinProtocol.Monero
|
||||
from .proto.xmr import mainnet
|
||||
self.proto_cls = mainnet
|
||||
|
||||
from .util import get_keccak
|
||||
self.keccak_256 = get_keccak()
|
||||
|
|
|
|||
0
mmgen/proto/__init__.py
Executable file
0
mmgen/proto/__init__.py
Executable file
37
mmgen/proto/bch.py
Executable file
37
mmgen/proto/bch.py
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen
|
||||
# https://gitlab.com/mmgen/mmgen
|
||||
|
||||
"""
|
||||
Bitcoin Cash protocol
|
||||
"""
|
||||
|
||||
from .btc import mainnet,_finfo
|
||||
|
||||
class mainnet(mainnet):
|
||||
is_fork_of = 'Bitcoin'
|
||||
mmtypes = ('L','C')
|
||||
sighash_type = 'ALL|FORKID'
|
||||
forks = [
|
||||
_finfo(478559,'000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec','BTC',False)
|
||||
]
|
||||
caps = ()
|
||||
coin_amt = 'BCHAmt'
|
||||
max_tx_fee = '0.1'
|
||||
ignore_daemon_version = False
|
||||
|
||||
def pubkey2redeem_script(self,pubkey): raise NotImplementedError
|
||||
def pubkey2segwitaddr(self,pubkey): raise NotImplementedError
|
||||
|
||||
class testnet(mainnet):
|
||||
addr_ver_bytes = { '6f': 'p2pkh', 'c4': 'p2sh' }
|
||||
wif_ver_num = { 'std': 'ef' }
|
||||
|
||||
class regtest(testnet):
|
||||
halving_interval = 150
|
||||
160
mmgen/proto/btc.py
Executable file
160
mmgen/proto/btc.py
Executable file
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen
|
||||
# https://gitlab.com/mmgen/mmgen
|
||||
|
||||
"""
|
||||
Bitcoin protocol
|
||||
"""
|
||||
|
||||
from ..protocol import CoinProtocol,parsed_wif,parsed_addr,_finfo,_b58a,_nw
|
||||
import hashlib
|
||||
|
||||
def hash160(in_bytes): # OP_HASH160
|
||||
return hashlib.new('ripemd160',hashlib.sha256(in_bytes).digest()).digest()
|
||||
|
||||
def hash256(in_bytes): # OP_HASH256
|
||||
return hashlib.sha256(hashlib.sha256(in_bytes).digest()).digest()
|
||||
|
||||
# From en.bitcoin.it:
|
||||
# The Base58 encoding used is home made, and has some differences.
|
||||
# Especially, leading zeroes are kept as single zeroes when conversion happens.
|
||||
# Test: 5JbQQTs3cnoYN9vDYaGY6nhQ1DggVsY4FJNBUfEfpSQqrEp3srk
|
||||
# The 'zero address':
|
||||
# 1111111111111111111114oLvT2 (pubkeyhash = '\0'*20)
|
||||
|
||||
def _b58chk_encode(in_bytes):
|
||||
lzeroes = len(in_bytes) - len(in_bytes.lstrip(b'\x00'))
|
||||
def do_enc(n):
|
||||
while n:
|
||||
yield _b58a[n % 58]
|
||||
n //= 58
|
||||
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:] != hash256(out[:-4])[:4]:
|
||||
raise ValueError('_b58chk_decode(): incorrect checksum')
|
||||
return out[:-4]
|
||||
|
||||
class mainnet(CoinProtocol.Secp256k1): # chainparams.cpp
|
||||
"""
|
||||
All Bitcoin code and chain forks inherit from this class
|
||||
"""
|
||||
mod_clsname = 'Bitcoin'
|
||||
network_names = _nw('mainnet','testnet','regtest')
|
||||
addr_ver_bytes = { '00': 'p2pkh', '05': 'p2sh' }
|
||||
addr_len = 20
|
||||
wif_ver_num = { 'std': '80' }
|
||||
mmtypes = ('L','C','S','B')
|
||||
dfl_mmtype = 'L'
|
||||
coin_amt = 'BTCAmt'
|
||||
max_tx_fee = '0.003'
|
||||
sighash_type = 'ALL'
|
||||
block0 = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
|
||||
forks = [
|
||||
_finfo(478559,'00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148','BCH',False),
|
||||
]
|
||||
caps = ('rbf','segwit')
|
||||
mmcaps = ('key','addr','rpc','tx')
|
||||
base_coin = 'BTC'
|
||||
base_proto = 'Bitcoin'
|
||||
# From BIP173: witness version 'n' is stored as 'OP_n'. OP_0 is encoded as 0x00,
|
||||
# but OP_1 through OP_16 are encoded as 0x51 though 0x60 (81 to 96 in decimal).
|
||||
witness_vernum_hex = '00'
|
||||
witness_vernum = int(witness_vernum_hex,16)
|
||||
bech32_hrp = 'bc'
|
||||
sign_mode = 'daemon'
|
||||
avg_bdi = int(9.7 * 60) # average block discovery interval (historical)
|
||||
halving_interval = 210000
|
||||
max_halvings = 64
|
||||
start_subsidy = 50
|
||||
ignore_daemon_version = False
|
||||
max_int = 0xffffffff
|
||||
|
||||
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])
|
||||
+ privbytes
|
||||
+ (b'',b'\x01')[bool(compressed)])
|
||||
|
||||
def parse_wif(self,wif):
|
||||
key = _b58chk_decode(wif)
|
||||
|
||||
for k,v in self.wif_ver_num.items():
|
||||
v = bytes.fromhex(v)
|
||||
if key[:len(v)] == v:
|
||||
pubkey_type = k
|
||||
key = key[len(v):]
|
||||
break
|
||||
else:
|
||||
raise ValueError('Invalid WIF version number')
|
||||
|
||||
if len(key) == self.privkey_len + 1:
|
||||
assert key[-1] == 0x01, f'{key[-1]!r}: invalid compressed key suffix byte'
|
||||
compressed = True
|
||||
elif len(key) == self.privkey_len:
|
||||
compressed = False
|
||||
else:
|
||||
raise ValueError(f'{len(key)}: invalid key length')
|
||||
|
||||
return parsed_wif(
|
||||
sec = key[:self.privkey_len],
|
||||
pubkey_type = pubkey_type,
|
||||
compressed = compressed )
|
||||
|
||||
def parse_addr(self,addr):
|
||||
|
||||
if 'B' in self.mmtypes and addr[:len(self.bech32_hrp)] == self.bech32_hrp:
|
||||
import mmgen.bech32 as bech32
|
||||
ret = bech32.decode(self.bech32_hrp,addr)
|
||||
|
||||
if ret[0] != self.witness_vernum:
|
||||
from ..util import msg
|
||||
msg(f'{ret[0]}: Invalid witness version number')
|
||||
return False
|
||||
|
||||
return parsed_addr( bytes(ret[1]), 'bech32' ) if ret[1] else False
|
||||
|
||||
return self.parse_addr_bytes(_b58chk_decode(addr))
|
||||
|
||||
def pubhash2addr(self,pubkey_hash,p2sh):
|
||||
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 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 bytes.fromhex(self.witness_vernum_hex + '14') + hash160(pubkey)
|
||||
|
||||
def pubkey2segwitaddr(self,pubkey):
|
||||
return self.pubhash2addr(
|
||||
hash160( self.pubkey2redeem_script(pubkey)), p2sh=True )
|
||||
|
||||
def pubhash2bech32addr(self,pubhash):
|
||||
d = list(pubhash)
|
||||
import mmgen.bech32 as bech32
|
||||
return bech32.bech32_encode(self.bech32_hrp,[self.witness_vernum]+bech32.convertbits(d,8,5))
|
||||
|
||||
class testnet(mainnet):
|
||||
addr_ver_bytes = { '6f': 'p2pkh', 'c4': 'p2sh' }
|
||||
wif_ver_num = { 'std': 'ef' }
|
||||
bech32_hrp = 'tb'
|
||||
|
||||
class regtest(testnet):
|
||||
bech32_hrp = 'bcrt'
|
||||
halving_interval = 150
|
||||
26
mmgen/proto/etc.py
Executable file
26
mmgen/proto/etc.py
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen
|
||||
# https://gitlab.com/mmgen/mmgen
|
||||
|
||||
"""
|
||||
Ethereum Classic protocol
|
||||
"""
|
||||
|
||||
from .eth import mainnet
|
||||
|
||||
class mainnet(mainnet):
|
||||
chain_names = ['classic','ethereum_classic']
|
||||
max_tx_fee = '0.005'
|
||||
ignore_daemon_version = False
|
||||
|
||||
class testnet(mainnet):
|
||||
chain_names = ['morden','morden_testnet','classic-testnet']
|
||||
|
||||
class regtest(testnet):
|
||||
chain_names = ['developmentchain']
|
||||
76
mmgen/proto/eth.py
Executable file
76
mmgen/proto/eth.py
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen
|
||||
# https://gitlab.com/mmgen/mmgen
|
||||
|
||||
"""
|
||||
Ethereum protocol
|
||||
"""
|
||||
|
||||
from ..protocol import CoinProtocol,_nw,parsed_addr
|
||||
from ..util import is_hex_str_lc,Msg
|
||||
|
||||
class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1):
|
||||
|
||||
network_names = _nw('mainnet','testnet','devnet')
|
||||
addr_len = 20
|
||||
mmtypes = ('E',)
|
||||
dfl_mmtype = 'E'
|
||||
mod_clsname = 'Ethereum'
|
||||
base_coin = 'ETH'
|
||||
pubkey_type = 'std' # required by DummyWIF
|
||||
|
||||
coin_amt = 'ETHAmt'
|
||||
max_tx_fee = '0.005'
|
||||
chain_names = ['ethereum','foundation']
|
||||
sign_mode = 'standalone'
|
||||
caps = ('token',)
|
||||
mmcaps = ('key','addr','rpc','tx')
|
||||
base_proto = 'Ethereum'
|
||||
avg_bdi = 15
|
||||
ignore_daemon_version = False
|
||||
|
||||
chain_ids = {
|
||||
1: 'ethereum', # ethereum mainnet
|
||||
2: 'morden', # morden testnet (deprecated)
|
||||
3: 'ropsten', # ropsten testnet
|
||||
4: 'rinkeby', # rinkeby testnet
|
||||
5: 'goerli', # goerli testnet
|
||||
42: 'kovan', # kovan testnet
|
||||
61: 'classic', # ethereum classic mainnet
|
||||
62: 'morden', # ethereum classic testnet
|
||||
17: 'developmentchain', # parity dev chain
|
||||
1337: 'developmentchain', # geth dev chain
|
||||
}
|
||||
|
||||
@property
|
||||
def dcoin(self):
|
||||
return self.tokensym or self.coin
|
||||
|
||||
def parse_addr(self,addr):
|
||||
if is_hex_str_lc(addr) and len(addr) == self.addr_len * 2:
|
||||
return parsed_addr( bytes.fromhex(addr), 'ethereum' )
|
||||
if g.debug:
|
||||
Msg(f'Invalid address: {addr}')
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def checksummed_addr(cls,addr):
|
||||
h = self.keccak_256(addr.encode()).digest().hex()
|
||||
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) == 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.hex()
|
||||
|
||||
class testnet(mainnet):
|
||||
chain_names = ['kovan','goerli','rinkeby']
|
||||
|
||||
class regtest(testnet):
|
||||
chain_names = ['developmentchain']
|
||||
39
mmgen/proto/ltc.py
Executable file
39
mmgen/proto/ltc.py
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen
|
||||
# https://gitlab.com/mmgen/mmgen
|
||||
|
||||
"""
|
||||
Litecoin protocol
|
||||
"""
|
||||
|
||||
from .btc import mainnet
|
||||
|
||||
class mainnet(mainnet):
|
||||
block0 = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2'
|
||||
addr_ver_bytes = { '30': 'p2pkh', '32': 'p2sh', '05': 'p2sh' } # new p2sh ver 0x32 must come first
|
||||
wif_ver_num = { 'std': 'b0' }
|
||||
mmtypes = ('L','C','S','B')
|
||||
coin_amt = 'LTCAmt'
|
||||
max_tx_fee = '0.3'
|
||||
base_coin = 'LTC'
|
||||
forks = []
|
||||
bech32_hrp = 'ltc'
|
||||
avg_bdi = 150
|
||||
halving_interval = 840000
|
||||
ignore_daemon_version = False
|
||||
|
||||
class testnet(mainnet):
|
||||
# addr ver nums same as Bitcoin testnet, except for 'p2sh'
|
||||
addr_ver_bytes = { '6f':'p2pkh', '3a':'p2sh', 'c4':'p2sh' }
|
||||
wif_ver_num = { 'std': 'ef' } # same as Bitcoin testnet
|
||||
bech32_hrp = 'tltc'
|
||||
|
||||
class regtest(testnet):
|
||||
bech32_hrp = 'rltc'
|
||||
halving_interval = 150
|
||||
62
mmgen/proto/xmr.py
Executable file
62
mmgen/proto/xmr.py
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen
|
||||
# https://gitlab.com/mmgen/mmgen
|
||||
|
||||
"""
|
||||
Monero protocol
|
||||
"""
|
||||
|
||||
from ..protocol import CoinProtocol,_nw
|
||||
|
||||
# https://github.com/monero-project/monero/blob/master/src/cryptonote_config.h
|
||||
class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Base):
|
||||
|
||||
network_names = _nw('mainnet','stagenet',None)
|
||||
base_coin = 'XMR'
|
||||
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
|
||||
avg_bdi = 120
|
||||
privkey_len = 32
|
||||
mmcaps = ('key','addr')
|
||||
ignore_daemon_version = False
|
||||
coin_amt = 'XMRAmt'
|
||||
|
||||
def preprocess_key(self,sec,pubkey_type): # reduce key
|
||||
from ..ed25519 import l
|
||||
return int.to_bytes(
|
||||
int.from_bytes( sec[::-1], 'big' ) % l,
|
||||
self.privkey_len,
|
||||
'big' )[::-1]
|
||||
|
||||
def parse_addr(self,addr):
|
||||
|
||||
from ..baseconv import baseconv
|
||||
|
||||
def b58dec(addr_str):
|
||||
bc = baseconv('b58')
|
||||
l = len(addr_str)
|
||||
a = b''.join([bc.tobytes( addr_str[i*11:i*11+11], pad=8 ) for i in range(l//11)])
|
||||
b = bc.tobytes( addr_str[-(l%11):], pad=5 )
|
||||
return a + b
|
||||
|
||||
ret = b58dec(addr)
|
||||
|
||||
chk = self.keccak_256(ret[:-4]).digest()[:4]
|
||||
|
||||
assert ret[-4:] == chk, f'{ret[-4:].hex()}: incorrect checksum. Correct value: {chk.hex()}'
|
||||
|
||||
return self.parse_addr_bytes(ret)
|
||||
|
||||
class testnet(mainnet): # use stagenet for testnet
|
||||
addr_ver_bytes = { '18': 'monero', '24': 'monero_sub' } # testnet is ('35','3f')
|
||||
52
mmgen/proto/zec.py
Executable file
52
mmgen/proto/zec.py
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
|
||||
# Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen
|
||||
# https://gitlab.com/mmgen/mmgen
|
||||
|
||||
"""
|
||||
Zcash protocol
|
||||
"""
|
||||
|
||||
from .btc import mainnet
|
||||
|
||||
class mainnet(mainnet):
|
||||
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'
|
||||
avg_bdi = 75
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
super().__init__(*args,**kwargs)
|
||||
from ..opts import opt
|
||||
self.coin_id = 'ZEC-Z' if opt.type in ('zcash_z','Z') else 'ZEC-T'
|
||||
|
||||
def get_addr_len(self,addr_fmt):
|
||||
return (20,64)[addr_fmt in ('zcash_z','viewkey')]
|
||||
|
||||
def preprocess_key(self,sec,pubkey_type):
|
||||
if pubkey_type == 'zcash_z': # zero the first four bits
|
||||
return bytes([sec[0] & 0x0f]) + sec[1:]
|
||||
else:
|
||||
return super().preprocess_key(sec,pubkey_type)
|
||||
|
||||
def pubhash2addr(self,pubkey_hash,p2sh):
|
||||
hash_len = len(pubkey_hash)
|
||||
if hash_len == 20:
|
||||
return super().pubhash2addr(pubkey_hash,p2sh)
|
||||
elif hash_len == 64:
|
||||
raise NotImplementedError('Zcash z-addresses do not support pubhash2addr()')
|
||||
else:
|
||||
raise ValueError(f'{hash_len}: incorrect pubkey_hash length')
|
||||
|
||||
class testnet(mainnet):
|
||||
wif_ver_num = { 'std': 'ef', 'zcash_z': 'ac08' }
|
||||
addr_ver_bytes = { '1d25': 'p2pkh', '1cba': 'p2sh', '16b6': 'zcash_z', 'a8ac0c': 'viewkey' }
|
||||
|
|
@ -17,10 +17,9 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
protocol.py: Coin protocol functions, classes and methods
|
||||
protocol.py: Coin protocol base classes and initializer
|
||||
"""
|
||||
|
||||
import sys,os,hashlib
|
||||
from collections import namedtuple
|
||||
|
||||
from .devtools import *
|
||||
|
|
@ -29,41 +28,11 @@ from .globalvars import g
|
|||
parsed_wif = namedtuple('parsed_wif',['sec','pubkey_type','compressed'])
|
||||
parsed_addr = namedtuple('parsed_addr',['bytes','fmt'])
|
||||
|
||||
def hash160(in_bytes): # OP_HASH160
|
||||
return hashlib.new('ripemd160',hashlib.sha256(in_bytes).digest()).digest()
|
||||
|
||||
def hash256(in_bytes): # OP_HASH256
|
||||
return hashlib.sha256(hashlib.sha256(in_bytes).digest()).digest()
|
||||
|
||||
_b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
|
||||
# From en.bitcoin.it:
|
||||
# The Base58 encoding used is home made, and has some differences.
|
||||
# Especially, leading zeroes are kept as single zeroes when conversion happens.
|
||||
# Test: 5JbQQTs3cnoYN9vDYaGY6nhQ1DggVsY4FJNBUfEfpSQqrEp3srk
|
||||
# The 'zero address':
|
||||
# 1111111111111111111114oLvT2 (pubkeyhash = '\0'*20)
|
||||
|
||||
def _b58chk_encode(in_bytes):
|
||||
lzeroes = len(in_bytes) - len(in_bytes.lstrip(b'\x00'))
|
||||
def do_enc(n):
|
||||
while n:
|
||||
yield _b58a[n % 58]
|
||||
n //= 58
|
||||
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:] != hash256(out[:-4])[:4]:
|
||||
raise ValueError('_b58chk_decode(): incorrect checksum')
|
||||
return out[:-4]
|
||||
|
||||
_finfo = namedtuple('fork_info',['height','hash','name','replayable'])
|
||||
_nw = namedtuple('coin_networks',['mainnet','testnet','regtest'])
|
||||
|
||||
_b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' # shared by Bitcoin and Monero
|
||||
|
||||
class CoinProtocol(MMGenObject):
|
||||
|
||||
proto_info = namedtuple('proto_info',['name','trust_level']) # trust levels: see altcoin.py
|
||||
|
|
@ -107,7 +76,7 @@ class CoinProtocol(MMGenObject):
|
|||
self.chain_names = [self.network]
|
||||
|
||||
if self.tokensym:
|
||||
assert isinstance(self,CoinProtocol.Ethereum), 'CoinProtocol.Base_chk1'
|
||||
assert self.name.startswith('Ethereum'), 'CoinProtocol.Base_chk1'
|
||||
|
||||
if self.base_coin in ('ETH','XMR'):
|
||||
from .util import get_keccak
|
||||
|
|
@ -212,168 +181,10 @@ class CoinProtocol(MMGenObject):
|
|||
ymsg(f'Warning: private key is greater than secp256k1 group order!:\n {hexpriv}')
|
||||
return (pk % self.secp256k1_ge).to_bytes(self.privkey_len,'big')
|
||||
|
||||
class Bitcoin(Secp256k1): # chainparams.cpp
|
||||
"""
|
||||
All Bitcoin code and chain forks inherit from this class
|
||||
"""
|
||||
mod_clsname = 'Bitcoin'
|
||||
network_names = _nw('mainnet','testnet','regtest')
|
||||
addr_ver_bytes = { '00': 'p2pkh', '05': 'p2sh' }
|
||||
addr_len = 20
|
||||
wif_ver_num = { 'std': '80' }
|
||||
mmtypes = ('L','C','S','B')
|
||||
dfl_mmtype = 'L'
|
||||
coin_amt = 'BTCAmt'
|
||||
max_tx_fee = '0.003'
|
||||
sighash_type = 'ALL'
|
||||
block0 = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
|
||||
forks = [
|
||||
_finfo(478559,'00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148','BCH',False),
|
||||
]
|
||||
caps = ('rbf','segwit')
|
||||
mmcaps = ('key','addr','rpc','tx')
|
||||
base_coin = 'BTC'
|
||||
base_proto = 'Bitcoin'
|
||||
# From BIP173: witness version 'n' is stored as 'OP_n'. OP_0 is encoded as 0x00,
|
||||
# but OP_1 through OP_16 are encoded as 0x51 though 0x60 (81 to 96 in decimal).
|
||||
witness_vernum_hex = '00'
|
||||
witness_vernum = int(witness_vernum_hex,16)
|
||||
bech32_hrp = 'bc'
|
||||
sign_mode = 'daemon'
|
||||
avg_bdi = int(9.7 * 60) # average block discovery interval (historical)
|
||||
halving_interval = 210000
|
||||
max_halvings = 64
|
||||
start_subsidy = 50
|
||||
ignore_daemon_version = False
|
||||
max_int = 0xffffffff
|
||||
|
||||
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])
|
||||
+ privbytes
|
||||
+ (b'',b'\x01')[bool(compressed)])
|
||||
|
||||
def parse_wif(self,wif):
|
||||
key = _b58chk_decode(wif)
|
||||
|
||||
for k,v in self.wif_ver_num.items():
|
||||
v = bytes.fromhex(v)
|
||||
if key[:len(v)] == v:
|
||||
pubkey_type = k
|
||||
key = key[len(v):]
|
||||
break
|
||||
else:
|
||||
raise ValueError('Invalid WIF version number')
|
||||
|
||||
if len(key) == self.privkey_len + 1:
|
||||
assert key[-1] == 0x01, f'{key[-1]!r}: invalid compressed key suffix byte'
|
||||
compressed = True
|
||||
elif len(key) == self.privkey_len:
|
||||
compressed = False
|
||||
else:
|
||||
raise ValueError(f'{len(key)}: invalid key length')
|
||||
|
||||
return parsed_wif(
|
||||
sec = key[:self.privkey_len],
|
||||
pubkey_type = pubkey_type,
|
||||
compressed = compressed )
|
||||
|
||||
def parse_addr(self,addr):
|
||||
|
||||
if 'B' in self.mmtypes and addr[:len(self.bech32_hrp)] == self.bech32_hrp:
|
||||
import mmgen.bech32 as bech32
|
||||
ret = bech32.decode(self.bech32_hrp,addr)
|
||||
|
||||
if ret[0] != self.witness_vernum:
|
||||
from .util import msg
|
||||
msg(f'{ret[0]}: Invalid witness version number')
|
||||
return False
|
||||
|
||||
return parsed_addr( bytes(ret[1]), 'bech32' ) if ret[1] else False
|
||||
|
||||
return self.parse_addr_bytes(_b58chk_decode(addr))
|
||||
|
||||
def pubhash2addr(self,pubkey_hash,p2sh):
|
||||
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 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 bytes.fromhex(self.witness_vernum_hex + '14') + hash160(pubkey)
|
||||
|
||||
def pubkey2segwitaddr(self,pubkey):
|
||||
return self.pubhash2addr(
|
||||
hash160( self.pubkey2redeem_script(pubkey)), p2sh=True )
|
||||
|
||||
def pubhash2bech32addr(self,pubhash):
|
||||
d = list(pubhash)
|
||||
import mmgen.bech32 as bech32
|
||||
return bech32.bech32_encode(self.bech32_hrp,[self.witness_vernum]+bech32.convertbits(d,8,5))
|
||||
|
||||
class BitcoinTestnet(Bitcoin):
|
||||
addr_ver_bytes = { '6f': 'p2pkh', 'c4': 'p2sh' }
|
||||
wif_ver_num = { 'std': 'ef' }
|
||||
bech32_hrp = 'tb'
|
||||
|
||||
class BitcoinRegtest(BitcoinTestnet):
|
||||
bech32_hrp = 'bcrt'
|
||||
halving_interval = 150
|
||||
|
||||
class BitcoinCash(Bitcoin):
|
||||
is_fork_of = 'Bitcoin'
|
||||
mmtypes = ('L','C')
|
||||
sighash_type = 'ALL|FORKID'
|
||||
forks = [
|
||||
_finfo(478559,'000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec','BTC',False)
|
||||
]
|
||||
caps = ()
|
||||
coin_amt = 'BCHAmt'
|
||||
max_tx_fee = '0.1'
|
||||
ignore_daemon_version = False
|
||||
|
||||
def pubkey2redeem_script(self,pubkey): raise NotImplementedError
|
||||
def pubkey2segwitaddr(self,pubkey): raise NotImplementedError
|
||||
|
||||
class BitcoinCashTestnet(BitcoinCash):
|
||||
addr_ver_bytes = { '6f': 'p2pkh', 'c4': 'p2sh' }
|
||||
wif_ver_num = { 'std': 'ef' }
|
||||
|
||||
class BitcoinCashRegtest(BitcoinCashTestnet):
|
||||
halving_interval = 150
|
||||
|
||||
class Litecoin(Bitcoin):
|
||||
block0 = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2'
|
||||
addr_ver_bytes = { '30': 'p2pkh', '32': 'p2sh', '05': 'p2sh' } # new p2sh ver 0x32 must come first
|
||||
wif_ver_num = { 'std': 'b0' }
|
||||
mmtypes = ('L','C','S','B')
|
||||
coin_amt = 'LTCAmt'
|
||||
max_tx_fee = '0.3'
|
||||
base_coin = 'LTC'
|
||||
forks = []
|
||||
bech32_hrp = 'ltc'
|
||||
avg_bdi = 150
|
||||
halving_interval = 840000
|
||||
ignore_daemon_version = False
|
||||
|
||||
class LitecoinTestnet(Litecoin):
|
||||
# addr ver nums same as Bitcoin testnet, except for 'p2sh'
|
||||
addr_ver_bytes = { '6f':'p2pkh', '3a':'p2sh', 'c4':'p2sh' }
|
||||
wif_ver_num = { 'std': 'ef' } # same as Bitcoin testnet
|
||||
bech32_hrp = 'tltc'
|
||||
|
||||
class LitecoinRegtest(LitecoinTestnet):
|
||||
bech32_hrp = 'rltc'
|
||||
halving_interval = 150
|
||||
|
||||
class DummyWIF:
|
||||
|
||||
"""
|
||||
Ethereum and Monero protocols inherit from this class
|
||||
"""
|
||||
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!'
|
||||
|
|
@ -385,163 +196,6 @@ class CoinProtocol(MMGenObject):
|
|||
pubkey_type = self.pubkey_type,
|
||||
compressed = False )
|
||||
|
||||
class Ethereum(DummyWIF,Secp256k1):
|
||||
|
||||
network_names = _nw('mainnet','testnet','devnet')
|
||||
addr_len = 20
|
||||
mmtypes = ('E',)
|
||||
dfl_mmtype = 'E'
|
||||
mod_clsname = 'Ethereum'
|
||||
base_coin = 'ETH'
|
||||
pubkey_type = 'std' # required by DummyWIF
|
||||
|
||||
coin_amt = 'ETHAmt'
|
||||
max_tx_fee = '0.005'
|
||||
chain_names = ['ethereum','foundation']
|
||||
sign_mode = 'standalone'
|
||||
caps = ('token',)
|
||||
mmcaps = ('key','addr','rpc','tx')
|
||||
base_proto = 'Ethereum'
|
||||
avg_bdi = 15
|
||||
ignore_daemon_version = False
|
||||
|
||||
chain_ids = {
|
||||
1: 'ethereum', # ethereum mainnet
|
||||
2: 'morden', # morden testnet (deprecated)
|
||||
3: 'ropsten', # ropsten testnet
|
||||
4: 'rinkeby', # rinkeby testnet
|
||||
5: 'goerli', # goerli testnet
|
||||
42: 'kovan', # kovan testnet
|
||||
61: 'classic', # ethereum classic mainnet
|
||||
62: 'morden', # ethereum classic testnet
|
||||
17: 'developmentchain', # parity dev chain
|
||||
1337: 'developmentchain', # geth dev chain
|
||||
}
|
||||
|
||||
@property
|
||||
def dcoin(self):
|
||||
return self.tokensym or self.coin
|
||||
|
||||
def parse_addr(self,addr):
|
||||
from .util import is_hex_str_lc
|
||||
if is_hex_str_lc(addr) and len(addr) == self.addr_len * 2:
|
||||
return parsed_addr( bytes.fromhex(addr), 'ethereum' )
|
||||
if g.debug:
|
||||
from .util import Msg
|
||||
Msg(f'Invalid address: {addr}')
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def checksummed_addr(cls,addr):
|
||||
h = self.keccak_256(addr.encode()).digest().hex()
|
||||
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) == 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.hex()
|
||||
|
||||
class EthereumTestnet(Ethereum):
|
||||
chain_names = ['kovan','goerli','rinkeby']
|
||||
|
||||
class EthereumRegtest(EthereumTestnet):
|
||||
chain_names = ['developmentchain']
|
||||
|
||||
class EthereumClassic(Ethereum):
|
||||
chain_names = ['classic','ethereum_classic']
|
||||
max_tx_fee = '0.005'
|
||||
ignore_daemon_version = False
|
||||
|
||||
class EthereumClassicTestnet(EthereumClassic):
|
||||
chain_names = ['morden','morden_testnet','classic-testnet']
|
||||
|
||||
class EthereumClassicRegtest(EthereumClassicTestnet):
|
||||
chain_names = ['developmentchain']
|
||||
|
||||
class Zcash(Bitcoin):
|
||||
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'
|
||||
avg_bdi = 75
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
super().__init__(*args,**kwargs)
|
||||
from .opts import opt
|
||||
self.coin_id = 'ZEC-Z' if opt.type in ('zcash_z','Z') else 'ZEC-T'
|
||||
|
||||
def get_addr_len(self,addr_fmt):
|
||||
return (20,64)[addr_fmt in ('zcash_z','viewkey')]
|
||||
|
||||
def preprocess_key(self,sec,pubkey_type):
|
||||
if pubkey_type == 'zcash_z': # zero the first four bits
|
||||
return bytes([sec[0] & 0x0f]) + sec[1:]
|
||||
else:
|
||||
return super().preprocess_key(sec,pubkey_type)
|
||||
|
||||
def pubhash2addr(self,pubkey_hash,p2sh):
|
||||
hash_len = len(pubkey_hash)
|
||||
if hash_len == 20:
|
||||
return super().pubhash2addr(pubkey_hash,p2sh)
|
||||
elif hash_len == 64:
|
||||
raise NotImplementedError('Zcash z-addresses do not support pubhash2addr()')
|
||||
else:
|
||||
raise ValueError(f'{hash_len}: incorrect pubkey_hash length')
|
||||
|
||||
class ZcashTestnet(Zcash):
|
||||
wif_ver_num = { 'std': 'ef', 'zcash_z': 'ac08' }
|
||||
addr_ver_bytes = { '1d25': 'p2pkh', '1cba': 'p2sh', '16b6': 'zcash_z', 'a8ac0c': 'viewkey' }
|
||||
|
||||
# https://github.com/monero-project/monero/blob/master/src/cryptonote_config.h
|
||||
class Monero(DummyWIF,Base):
|
||||
|
||||
network_names = _nw('mainnet','stagenet',None)
|
||||
base_coin = 'XMR'
|
||||
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
|
||||
avg_bdi = 120
|
||||
privkey_len = 32
|
||||
mmcaps = ('key','addr')
|
||||
ignore_daemon_version = False
|
||||
coin_amt = 'XMRAmt'
|
||||
|
||||
def preprocess_key(self,sec,pubkey_type): # reduce key
|
||||
from .ed25519 import l
|
||||
return int.to_bytes(
|
||||
int.from_bytes( sec[::-1], 'big' ) % l,
|
||||
self.privkey_len,
|
||||
'big' )[::-1]
|
||||
|
||||
def parse_addr(self,addr):
|
||||
|
||||
from .baseconv import baseconv
|
||||
|
||||
def b58dec(addr_str):
|
||||
bc = baseconv('b58')
|
||||
l = len(addr_str)
|
||||
a = b''.join([bc.tobytes( addr_str[i*11:i*11+11], pad=8 ) for i in range(l//11)])
|
||||
b = bc.tobytes( addr_str[-(l%11):], pad=5 )
|
||||
return a + b
|
||||
|
||||
ret = b58dec(addr)
|
||||
|
||||
chk = self.keccak_256(ret[:-4]).digest()[:4]
|
||||
|
||||
assert ret[-4:] == chk, f'{ret[-4:].hex()}: incorrect checksum. Correct value: {chk.hex()}'
|
||||
|
||||
return self.parse_addr_bytes(ret)
|
||||
|
||||
class MoneroTestnet(Monero): # use stagenet for testnet
|
||||
addr_ver_bytes = { '18': 'monero', '24': 'monero_sub' } # testnet is ('35','3f')
|
||||
|
||||
def init_proto(coin=None,testnet=False,regtest=False,network=None,network_id=None,tokensym=None,need_amt=False):
|
||||
|
||||
assert type(testnet) == bool, 'init_proto_chk1'
|
||||
|
|
@ -567,6 +221,14 @@ def init_proto(coin=None,testnet=False,regtest=False,network=None,network_id=Non
|
|||
name = CoinProtocol.coins[coin].name
|
||||
proto_name = name + ('' if network == 'mainnet' else network.capitalize())
|
||||
|
||||
if not hasattr(CoinProtocol,proto_name):
|
||||
import importlib
|
||||
setattr(
|
||||
CoinProtocol,
|
||||
proto_name,
|
||||
getattr(importlib.import_module(f'mmgen.proto.{coin}'),network)
|
||||
)
|
||||
|
||||
return getattr(CoinProtocol,proto_name)(
|
||||
coin = coin,
|
||||
name = name,
|
||||
|
|
@ -622,4 +284,5 @@ def warn_trustlevel(coinsym):
|
|||
return
|
||||
|
||||
if not keypress_confirm(warning,default_yes=True):
|
||||
import sys
|
||||
sys.exit(0)
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class tool_cmd(tool_cmd_base):
|
|||
if self.mmtype.name == 'segwit':
|
||||
return self.proto.pubkey2segwitaddr( pubkey )
|
||||
else:
|
||||
from ..protocol import hash160
|
||||
from ..proto.btc import hash160
|
||||
return self.pubhash2addr( hash160(pubkey).hex() )
|
||||
|
||||
def pubhex2redeem_script(self,pubkeyhex:'sstr'): # new
|
||||
|
|
@ -146,7 +146,7 @@ class tool_cmd(tool_cmd_base):
|
|||
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'
|
||||
from ..protocol import hash160
|
||||
from ..proto.btc import hash160
|
||||
return self.pubhash2addr( hash160(bytes.fromhex(redeem_scripthex)).hex() )
|
||||
|
||||
def pubhash2addr(self,pubhashhex:'sstr'):
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class tool_cmd(tool_cmd_base):
|
|||
|
||||
def hash160(self,hexstr:'sstr'):
|
||||
"compute ripemd160(sha256(data)) (convert hex pubkey to hex addr)"
|
||||
from ..protocol import hash160
|
||||
from ..proto.btc import hash160
|
||||
return hash160( bytes.fromhex(hexstr) ).hex()
|
||||
|
||||
def hash256(self,string_or_bytes:str,file_input=False,hex_input=False): # TODO: handle stdin
|
||||
|
|
@ -143,12 +143,12 @@ class tool_cmd(tool_cmd_base):
|
|||
|
||||
def hextob58chk(self,hexstr:'sstr'):
|
||||
"convert a hexadecimal number to base58-check encoding"
|
||||
from ..protocol import _b58chk_encode
|
||||
from ..proto.btc import _b58chk_encode
|
||||
return _b58chk_encode( bytes.fromhex(hexstr) )
|
||||
|
||||
def b58chktohex(self,b58chk_num:'sstr'):
|
||||
"convert a base58-check encoded number to hexadecimal"
|
||||
from ..protocol import _b58chk_decode
|
||||
from ..proto.btc import _b58chk_decode
|
||||
return _b58chk_decode(b58chk_num).hex()
|
||||
|
||||
def hextob32(self,hexstr:'sstr',pad=0):
|
||||
|
|
|
|||
|
|
@ -674,8 +674,8 @@ def altcoin_subclass(cls,proto,mod_dir):
|
|||
"""
|
||||
magic module loading and class retrieval
|
||||
"""
|
||||
from .protocol import CoinProtocol
|
||||
if isinstance(proto,CoinProtocol.Bitcoin):
|
||||
|
||||
if proto.base_coin != 'ETH':
|
||||
return cls
|
||||
|
||||
modname = f'mmgen.altcoins.{proto.base_coin.lower()}.{mod_dir}'
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from .common import *
|
|||
from .objmethods import Hilite,InitErrors
|
||||
from .obj import CoinTxID
|
||||
from .seed import SeedID
|
||||
from .protocol import _b58a,init_proto
|
||||
from .protocol import init_proto,_b58a
|
||||
from .addr import CoinAddr,AddrIdx
|
||||
from .addrlist import KeyAddrList,AddrIdxList
|
||||
from .rpc import MoneroRPCClientRaw,MoneroWalletRPCClient,json_encoder
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ install_requires =
|
|||
packages =
|
||||
mmgen
|
||||
mmgen.share
|
||||
mmgen.proto
|
||||
mmgen.tool
|
||||
mmgen.altcoins
|
||||
mmgen.altcoins.eth
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ def run_test():
|
|||
tool.usr_randchars = 0 # setter
|
||||
check_equal(tool.usr_randchars,0)
|
||||
|
||||
check_equal(f'{tool.coin} {tool.proto.cls_name} {tool.addrtype}', 'BTC Bitcoin L' )
|
||||
check_equal(f'{tool.coin} {tool.proto.cls_name} {tool.addrtype}', 'BTC mainnet L' )
|
||||
|
||||
# test vectors from tooltest2.py:
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ def overlay_setup(repo_root):
|
|||
'mmgen.data',
|
||||
'mmgen.share',
|
||||
'mmgen.tool',
|
||||
'mmgen.proto',
|
||||
'mmgen.altcoins',
|
||||
'mmgen.altcoins.eth',
|
||||
'mmgen.altcoins.eth.pyethereum',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue