protocol.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. protocol.py: Coin protocol functions, classes and methods
  20. """
  21. import sys,os,hashlib
  22. from collections import namedtuple
  23. from .util import msg,ymsg,Msg,ydie
  24. from .devtools import *
  25. from .obj import BTCAmt,LTCAmt,BCHAmt,B2XAmt,ETHAmt,CoinAddr,MMGenAddrType,PrivKey
  26. from .globalvars import g
  27. import mmgen.bech32 as bech32
  28. parsed_wif = namedtuple('parsed_wif',['sec','pubkey_type','compressed'])
  29. parsed_addr = namedtuple('parsed_addr',['bytes','fmt'])
  30. def hash160(hexnum): # take hex, return hex - OP_HASH160
  31. return hashlib.new('ripemd160',hashlib.sha256(bytes.fromhex(hexnum)).digest()).hexdigest()
  32. def hash256(hexnum): # take hex, return hex - OP_HASH256
  33. return hashlib.sha256(hashlib.sha256(bytes.fromhex(hexnum)).digest()).hexdigest()
  34. def hash256bytes(bstr): # bytes in, bytes out - OP_HASH256
  35. return hashlib.sha256(hashlib.sha256(bstr).digest()).digest()
  36. _b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
  37. # From en.bitcoin.it:
  38. # The Base58 encoding used is home made, and has some differences.
  39. # Especially, leading zeroes are kept as single zeroes when conversion happens.
  40. # Test: 5JbQQTs3cnoYN9vDYaGY6nhQ1DggVsY4FJNBUfEfpSQqrEp3srk
  41. # The 'zero address':
  42. # 1111111111111111111114oLvT2 (pubkeyhash = '\0'*20)
  43. def _b58chk_encode(bstr):
  44. lzeroes = len(bstr) - len(bstr.lstrip(b'\x00'))
  45. def do_enc(n):
  46. while n:
  47. yield _b58a[n % 58]
  48. n //= 58
  49. return ('1' * lzeroes) + ''.join(do_enc(int.from_bytes(bstr+hash256bytes(bstr)[:4],'big')))[::-1]
  50. def _b58chk_decode(s):
  51. lzeroes = len(s) - len(s.lstrip('1'))
  52. res = sum(_b58a.index(ch) * 58**n for n,ch in enumerate(s[::-1]))
  53. bl = res.bit_length()
  54. out = b'\x00' * lzeroes + res.to_bytes(bl//8 + bool(bl%8),'big')
  55. if out[-4:] != hash256bytes(out[:-4])[:4]:
  56. raise ValueError('_b58chk_decode(): incorrect checksum')
  57. return out[:-4]
  58. finfo = namedtuple('fork_info',['height','hash','name','replayable'])
  59. class CoinProtocol(MMGenObject):
  60. proto_info = namedtuple('proto_info',['name','trust_level']) # trust levels: see altcoin.py
  61. coins = {
  62. 'btc': proto_info('Bitcoin', 5),
  63. 'bch': proto_info('BitcoinCash', 5),
  64. 'ltc': proto_info('Litecoin', 5),
  65. 'eth': proto_info('Ethereum', 4),
  66. 'etc': proto_info('EthereumClassic', 4),
  67. 'zec': proto_info('Zcash', 2),
  68. 'xmr': proto_info('Monero', 4)
  69. }
  70. core_coins = tuple(coins.keys()) # coins may be added by init_genonly_altcoins(), so save
  71. class Base(MMGenObject):
  72. is_fork_of = None
  73. networks = ('mainnet','testnet','regtest')
  74. def __init__(self,coin,name,network,tokensym=None):
  75. self.coin = coin.upper()
  76. self.name = name
  77. self.network = network
  78. self.tokensym = tokensym
  79. self.cls_name = type(self).__name__
  80. self.testnet = network in ('testnet','regtest')
  81. self.regtest = network == 'regtest'
  82. self.network_id = coin.lower() + {
  83. 'mainnet': '',
  84. 'testnet': '_tn',
  85. 'regtest': '_rt',
  86. }[network]
  87. if not hasattr(self,'chain_name'):
  88. self.chain_name = self.network
  89. if self.tokensym:
  90. assert isinstance(self,CoinProtocol.Ethereum), 'CoinProtocol.Base_chk1'
  91. @property
  92. def dcoin(self):
  93. return self.coin
  94. @classmethod
  95. def chain_name_to_network(cls,coin,chain_name):
  96. """
  97. The generic networks 'mainnet', 'testnet' and 'regtest' are required for all coins
  98. that support transaction operations.
  99. For protocols that have specific names for chains corresponding to these networks,
  100. the attribute 'chain_name' is used, while 'network' retains the generic name.
  101. For Bitcoin and Bitcoin forks, 'network' and 'chain_name' are equivalent.
  102. """
  103. for network,suf in (
  104. ('mainnet',''),
  105. ('testnet','Testnet'),
  106. ('regtest','Regtest' ),
  107. ):
  108. name = CoinProtocol.coins[coin.lower()].name + suf
  109. proto = getattr(CoinProtocol,name)
  110. proto_chain_name = getattr(proto,'chain_name',None) or network
  111. if chain_name == proto_chain_name:
  112. return network
  113. raise ValueError(f'{chain_name}: unrecognized chain name for coin {coin}')
  114. @staticmethod
  115. def parse_network_id(network_id):
  116. nid = namedtuple('parsed_network_id',['coin','network'])
  117. if network_id.endswith('_tn'):
  118. return nid(network_id[:-3],'testnet')
  119. elif network_id.endswith('_rt'):
  120. return nid(network_id[:-3],'regtest')
  121. else:
  122. return nid(network_id,'mainnet')
  123. def cap(self,s):
  124. return s in self.caps
  125. def addr_fmt_to_ver_bytes(self,req_fmt,return_hex=False):
  126. for ver_hex,fmt in self.addr_ver_bytes.items():
  127. if req_fmt == fmt:
  128. return ver_hex if return_hex else bytes.fromhex(ver_hex)
  129. return False
  130. def get_addr_len(self,addr_fmt):
  131. return self.addr_len
  132. def parse_addr_bytes(self,addr_bytes):
  133. for ver_hex,addr_fmt in self.addr_ver_bytes.items():
  134. ver_bytes = bytes.fromhex(ver_hex)
  135. vlen = len(ver_bytes)
  136. if addr_bytes[:vlen] == ver_bytes:
  137. if len(addr_bytes[vlen:]) == self.get_addr_len(addr_fmt):
  138. return parsed_addr( addr_bytes[vlen:], addr_fmt )
  139. return False
  140. def coin_addr(self,addr):
  141. return CoinAddr(proto=self,addr=addr)
  142. def addr_type(self,id_str,on_fail='die'):
  143. return MMGenAddrType(proto=self,id_str=id_str,on_fail=on_fail)
  144. def priv_key(self,s,on_fail='die'):
  145. return PrivKey(proto=self,s=s,on_fail=on_fail)
  146. class Secp256k1(Base):
  147. """
  148. Bitcoin and Ethereum protocols inherit from this class
  149. """
  150. secp256k1_ge = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
  151. privkey_len = 32
  152. def preprocess_key(self,sec,pubkey_type):
  153. # Key must be non-zero and less than group order of secp256k1 curve
  154. if 0 < int.from_bytes(sec,'big') < self.secp256k1_ge:
  155. return sec
  156. else: # chance of this is less than 1 in 2^127
  157. pk = int.from_bytes(sec,'big')
  158. if pk == 0: # chance of this is 1 in 2^256
  159. ydie(3,'Private key is zero!')
  160. elif pk == self.secp256k1_ge: # ditto
  161. ydie(3,'Private key == secp256k1_ge!')
  162. else:
  163. if not g.test_suite:
  164. ymsg(f'Warning: private key is greater than secp256k1 group order!:\n {hexpriv}')
  165. return (pk % self.secp256k1_ge).to_bytes(self.privkey_len,'big')
  166. class Bitcoin(Secp256k1): # chainparams.cpp
  167. """
  168. All Bitcoin code and chain forks inherit from this class
  169. """
  170. mod_clsname = 'Bitcoin'
  171. daemon_name = 'bitcoind'
  172. daemon_family = 'bitcoind'
  173. addr_ver_bytes = { '00': 'p2pkh', '05': 'p2sh' }
  174. addr_len = 20
  175. wif_ver_num = { 'std': '80' }
  176. mmtypes = ('L','C','S','B')
  177. dfl_mmtype = 'L'
  178. rpc_port = 8332
  179. coin_amt = BTCAmt
  180. max_tx_fee = BTCAmt('0.003')
  181. daemon_data_dir = ( os.path.join(os.getenv('APPDATA'),'Bitcoin') if g.platform == 'win' else
  182. os.path.join(g.home_dir,'.bitcoin') )
  183. daemon_data_subdir = ''
  184. sighash_type = 'ALL'
  185. block0 = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
  186. forks = [
  187. finfo(478559,'00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148','BCH',False),
  188. finfo(None,'','B2X',True),
  189. ]
  190. caps = ('rbf','segwit')
  191. mmcaps = ('key','addr','rpc','tx')
  192. base_coin = 'BTC'
  193. base_proto = 'Bitcoin'
  194. # From BIP173: witness version 'n' is stored as 'OP_n'. OP_0 is encoded as 0x00,
  195. # but OP_1 through OP_16 are encoded as 0x51 though 0x60 (81 to 96 in decimal).
  196. witness_vernum_hex = '00'
  197. witness_vernum = int(witness_vernum_hex,16)
  198. bech32_hrp = 'bc'
  199. sign_mode = 'daemon'
  200. avg_bdi = int(9.7 * 60) # average block discovery interval (historical)
  201. def hex2wif(self,hexpriv,pubkey_type,compressed): # input is preprocessed hex
  202. sec = bytes.fromhex(hexpriv)
  203. assert len(sec) == self.privkey_len, f'{len(sec)} bytes: incorrect private key length!'
  204. assert pubkey_type in self.wif_ver_num, f'{pubkey_type!r}: invalid pubkey_type'
  205. return _b58chk_encode(
  206. bytes.fromhex(self.wif_ver_num[pubkey_type])
  207. + sec
  208. + (b'',b'\x01')[bool(compressed)])
  209. def parse_wif(self,wif):
  210. key = _b58chk_decode(wif)
  211. for k,v in self.wif_ver_num.items():
  212. v = bytes.fromhex(v)
  213. if key[:len(v)] == v:
  214. pubkey_type = k
  215. key = key[len(v):]
  216. break
  217. else:
  218. raise ValueError('Invalid WIF version number')
  219. if len(key) == self.privkey_len + 1:
  220. assert key[-1] == 0x01, f'{key[-1]!r}: invalid compressed key suffix byte'
  221. compressed = True
  222. elif len(key) == self.privkey_len:
  223. compressed = False
  224. else:
  225. raise ValueError(f'{len(key)}: invalid key length')
  226. return parsed_wif(key[:self.privkey_len], pubkey_type, compressed)
  227. def parse_addr(self,addr):
  228. if 'B' in self.mmtypes and addr[:len(self.bech32_hrp)] == self.bech32_hrp:
  229. ret = bech32.decode(self.bech32_hrp,addr)
  230. if ret[0] != self.witness_vernum:
  231. msg(f'{ret[0]}: Invalid witness version number')
  232. return False
  233. return parsed_addr( bytes(ret[1]), 'bech32' ) if ret[1] else False
  234. return self.parse_addr_bytes(_b58chk_decode(addr))
  235. def pubhash2addr(self,pubkey_hash,p2sh):
  236. assert len(pubkey_hash) == 40, f'{len(pubkey_hash)}: invalid length for pubkey hash'
  237. s = self.addr_fmt_to_ver_bytes(('p2pkh','p2sh')[p2sh],return_hex=True) + pubkey_hash
  238. return _b58chk_encode(bytes.fromhex(s))
  239. # Segwit:
  240. def pubhex2redeem_script(self,pubhex):
  241. # https://bitcoincore.org/en/segwit_wallet_dev/
  242. # The P2SH redeemScript is always 22 bytes. It starts with a OP_0, followed
  243. # by a canonical push of the keyhash (i.e. 0x0014{20-byte keyhash})
  244. return self.witness_vernum_hex + '14' + hash160(pubhex)
  245. def pubhex2segwitaddr(self,pubhex):
  246. return self.pubhash2addr(hash160(self.pubhex2redeem_script(pubhex)),p2sh=True)
  247. def pubhash2bech32addr(self,pubhash):
  248. d = list(bytes.fromhex(pubhash))
  249. return bech32.bech32_encode(self.bech32_hrp,[self.witness_vernum]+bech32.convertbits(d,8,5))
  250. class BitcoinTestnet(Bitcoin):
  251. addr_ver_bytes = { '6f': 'p2pkh', 'c4': 'p2sh' }
  252. wif_ver_num = { 'std': 'ef' }
  253. daemon_data_subdir = 'testnet3'
  254. rpc_port = 18332
  255. bech32_hrp = 'tb'
  256. class BitcoinRegtest(BitcoinTestnet):
  257. bech32_hrp = 'bcrt'
  258. class BitcoinCash(Bitcoin):
  259. is_fork_of = 'Bitcoin'
  260. # TODO: assumes MSWin user installs in custom dir 'Bitcoin_ABC'
  261. daemon_name = 'bitcoind-abc'
  262. daemon_data_dir = ( os.path.join(os.getenv('APPDATA'),'Bitcoin_ABC') if g.platform == 'win' else
  263. os.path.join(g.home_dir,'.bitcoin-abc') )
  264. rpc_port = 8442
  265. mmtypes = ('L','C')
  266. sighash_type = 'ALL|FORKID'
  267. forks = [
  268. finfo(478559,'000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec','BTC',False)
  269. ]
  270. caps = ()
  271. coin_amt = BCHAmt
  272. max_tx_fee = BCHAmt('0.1')
  273. def pubhex2redeem_script(self,pubhex): raise NotImplementedError
  274. def pubhex2segwitaddr(self,pubhex): raise NotImplementedError
  275. class BitcoinCashTestnet(BitcoinCash):
  276. rpc_port = 18442
  277. addr_ver_bytes = { '6f': 'p2pkh', 'c4': 'p2sh' }
  278. wif_ver_num = { 'std': 'ef' }
  279. daemon_data_subdir = 'testnet3'
  280. class BitcoinCashRegtest(BitcoinCashTestnet):
  281. pass
  282. class B2X(Bitcoin):
  283. is_fork_of = 'Bitcoin'
  284. daemon_name = 'bitcoind-2x'
  285. daemon_data_dir = ( os.path.join(os.getenv('APPDATA'),'Bitcoin_2X') if g.platform == 'win' else
  286. os.path.join(g.home_dir,'.bitcoin-2x') )
  287. rpc_port = 8338
  288. coin_amt = B2XAmt
  289. max_tx_fee = B2XAmt('0.1')
  290. forks = [
  291. finfo(None,'','BTC',True) # activation: 494784
  292. ]
  293. class B2XTestnet(B2X):
  294. addr_ver_bytes = { '6f': 'p2pkh', 'c4': 'p2sh' }
  295. wif_ver_num = { 'std': 'ef' }
  296. daemon_data_subdir = 'testnet5'
  297. rpc_port = 18338
  298. class Litecoin(Bitcoin):
  299. block0 = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2'
  300. daemon_name = 'litecoind'
  301. daemon_data_dir = ( os.path.join(os.getenv('APPDATA'),'Litecoin') if g.platform == 'win' else
  302. os.path.join(g.home_dir,'.litecoin') )
  303. addr_ver_bytes = { '30': 'p2pkh', '32': 'p2sh', '05': 'p2sh' } # new p2sh ver 0x32 must come first
  304. wif_ver_num = { 'std': 'b0' }
  305. mmtypes = ('L','C','S','B')
  306. rpc_port = 9332
  307. coin_amt = LTCAmt
  308. max_tx_fee = LTCAmt('0.3')
  309. base_coin = 'LTC'
  310. forks = []
  311. bech32_hrp = 'ltc'
  312. avg_bdi = 150
  313. class LitecoinTestnet(Litecoin):
  314. # addr ver nums same as Bitcoin testnet, except for 'p2sh'
  315. addr_ver_bytes = { '6f':'p2pkh', '3a':'p2sh', 'c4':'p2sh' }
  316. wif_ver_num = { 'std': 'ef' } # same as Bitcoin testnet
  317. daemon_data_subdir = 'testnet4'
  318. rpc_port = 19332
  319. bech32_hrp = 'tltc'
  320. class LitecoinRegtest(LitecoinTestnet):
  321. bech32_hrp = 'rltc'
  322. class DummyWIF:
  323. def hex2wif(self,hexpriv,pubkey_type,compressed):
  324. assert pubkey_type == self.pubkey_type, f'{pubkey_type}: invalid pubkey_type for {self.name} protocol!'
  325. assert compressed == False, f'{self.name} protocol does not support compressed pubkeys!'
  326. return hexpriv
  327. def parse_wif(self,wif):
  328. return parsed_wif(bytes.fromhex(wif), self.pubkey_type, False)
  329. class Ethereum(DummyWIF,Secp256k1):
  330. addr_len = 20
  331. mmtypes = ('E',)
  332. dfl_mmtype = 'E'
  333. mod_clsname = 'Ethereum'
  334. base_coin = 'ETH'
  335. pubkey_type = 'std' # required by DummyWIF
  336. daemon_name = 'parity'
  337. daemon_family = 'parity'
  338. daemon_data_dir = os.path.join(g.home_dir,'.local','share','io.parity.ethereum')
  339. daemon_data_subdir = ''
  340. rpc_port = 8545
  341. coin_amt = ETHAmt
  342. max_tx_fee = ETHAmt('0.005')
  343. chain_name = 'foundation'
  344. sign_mode = 'standalone'
  345. caps = ('token',)
  346. mmcaps = ('key','addr','rpc','tx')
  347. base_proto = 'Ethereum'
  348. avg_bdi = 15
  349. @property
  350. def dcoin(self):
  351. return self.tokensym or self.coin
  352. def parse_addr(self,addr):
  353. from .util import is_hex_str_lc
  354. if is_hex_str_lc(addr) and len(addr) == self.addr_len * 2:
  355. return parsed_addr( bytes.fromhex(addr), 'ethereum' )
  356. if g.debug:
  357. Msg(f'Invalid address: {addr}')
  358. return False
  359. def pubhash2addr(self,pubkey_hash,p2sh):
  360. assert len(pubkey_hash) == 40, f'{len(pubkey_hash)}: invalid length for {self.name} pubkey hash'
  361. assert not p2sh, f'{self.name} protocol has no P2SH address format'
  362. return pubkey_hash
  363. class EthereumTestnet(Ethereum):
  364. rpc_port = 8547 # start Parity with --jsonrpc-port=8547 or --ports-shift=2
  365. chain_name = 'kovan'
  366. class EthereumRegtest(EthereumTestnet):
  367. chain_name = 'developmentchain'
  368. class EthereumClassic(Ethereum):
  369. rpc_port = 8555 # start Parity with --jsonrpc-port=8555 or --ports-shift=10
  370. chain_name = 'ethereum_classic' # chain_id 0x3d (61)
  371. class EthereumClassicTestnet(EthereumClassic):
  372. rpc_port = 8557 # start Parity with --jsonrpc-port=8557 or --ports-shift=12
  373. chain_name = 'classic-testnet' # aka Morden, chain_id 0x3e (62) (UNTESTED)
  374. class EthereumClassicRegtest(EthereumClassicTestnet):
  375. chain_name = 'developmentchain'
  376. class Zcash(Bitcoin):
  377. base_coin = 'ZEC'
  378. addr_ver_bytes = { '1cb8': 'p2pkh', '1cbd': 'p2sh', '169a': 'zcash_z', 'a8abd3': 'viewkey' }
  379. wif_ver_num = { 'std': '80', 'zcash_z': 'ab36' }
  380. mmtypes = ('L','C','Z')
  381. mmcaps = ('key','addr')
  382. dfl_mmtype = 'L'
  383. avg_bdi = 75
  384. def get_addr_len(self,addr_fmt):
  385. return (20,64)[addr_fmt in ('zcash_z','viewkey')]
  386. def preprocess_key(self,sec,pubkey_type):
  387. if pubkey_type == 'zcash_z': # zero the first four bits
  388. return bytes([sec[0] & 0x0f]) + sec[1:]
  389. else:
  390. return super().preprocess_key(sec,pubkey_type)
  391. def pubhash2addr(self,pubkey_hash,p2sh):
  392. hash_len = len(pubkey_hash)
  393. if hash_len == 40:
  394. return super().pubhash2addr(pubkey_hash,p2sh)
  395. elif hash_len == 128:
  396. raise NotImplementedError('Zcash z-addresses have no pubkey hash')
  397. else:
  398. raise ValueError(f'{hash_len}: incorrect pubkey_hash length')
  399. class ZcashTestnet(Zcash):
  400. wif_ver_num = { 'std': 'ef', 'zcash_z': 'ac08' }
  401. addr_ver_bytes = { '1d25': 'p2pkh', '1cba': 'p2sh', '16b6': 'zcash_z', 'a8ac0c': 'viewkey' }
  402. # https://github.com/monero-project/monero/blob/master/src/cryptonote_config.h
  403. class Monero(DummyWIF,Base):
  404. base_coin = 'XMR'
  405. addr_ver_bytes = { '12': 'monero', '2a': 'monero_sub' }
  406. addr_len = 68
  407. wif_ver_num = {}
  408. mmtypes = ('M',)
  409. dfl_mmtype = 'M'
  410. pubkey_type = 'monero' # required by DummyWIF
  411. avg_bdi = 120
  412. privkey_len = 32
  413. mmcaps = ('key','addr')
  414. def preprocess_key(self,sec,pubkey_type): # reduce key
  415. from .ed25519 import l
  416. n = int.from_bytes(sec[::-1],'big') % l
  417. return int.to_bytes(n,self.privkey_len,'big')[::-1]
  418. def parse_addr(self,addr):
  419. from .baseconv import baseconv,is_b58_str
  420. def b58dec(addr_str):
  421. l = len(addr_str)
  422. a = b''.join([baseconv.tobytes(addr_str[i*11:i*11+11],'b58',pad=8) for i in range(l//11)])
  423. b = baseconv.tobytes(addr_str[-(l%11):],'b58',pad=5)
  424. return a + b
  425. ret = b58dec(addr)
  426. try:
  427. assert not g.use_internal_keccak_module
  428. from sha3 import keccak_256
  429. except:
  430. from .keccak import keccak_256
  431. chk = keccak_256(ret[:-4]).digest()[:4]
  432. assert ret[-4:] == chk, f'{ret[-4:].hex()}: incorrect checksum. Correct value: {chk.hex()}'
  433. return self.parse_addr_bytes(ret)
  434. class MoneroTestnet(Monero):
  435. addr_ver_bytes = { '35': 'monero', '3f': 'monero_sub' }
  436. def init_proto(coin=None,testnet=False,regtest=False,network=None,network_id=None,tokensym=None):
  437. assert type(testnet) == bool, 'init_proto_chk1'
  438. assert type(regtest) == bool, 'init_proto_chk2'
  439. assert coin or network_id, 'init_proto_chk3'
  440. assert not (coin and network_id), 'init_proto_chk4'
  441. if network_id:
  442. coin,network = CoinProtocol.Base.parse_network_id(network_id)
  443. elif network:
  444. assert network in CoinProtocol.Base.networks, f'init_proto_chk5 - {network!r}: invalid network'
  445. assert testnet == False, 'init_proto_chk6'
  446. assert regtest == False, 'init_proto_chk7'
  447. else:
  448. network = 'regtest' if regtest else 'testnet' if testnet else 'mainnet'
  449. coin = coin.lower()
  450. if coin not in CoinProtocol.coins:
  451. raise ValueError(
  452. f'{coin.upper()}: not a valid coin for network {network.upper()}\n'
  453. + 'Supported coins: '
  454. + ' '.join(c.upper() for c in CoinProtocol.coins) )
  455. name = CoinProtocol.coins[coin].name
  456. proto_name = name + ('' if network == 'mainnet' else network.capitalize())
  457. return getattr(CoinProtocol,proto_name)(
  458. coin = coin,
  459. name = name,
  460. network = network,
  461. tokensym = tokensym )
  462. def init_proto_from_opts():
  463. return init_proto(
  464. coin = g.coin,
  465. testnet = g.testnet,
  466. regtest = g.regtest,
  467. tokensym = g.token )
  468. def init_genonly_altcoins(usr_coin=None,testnet=False):
  469. """
  470. Initialize altcoin protocol class or classes for current network.
  471. If usr_coin is a core coin, initialization is skipped.
  472. If usr_coin has a trust level of -1, an exception is raised.
  473. If usr_coin is None, initializes all coins for current network with trust level >-1.
  474. Returns trust_level of usr_coin, or 0 (untrusted) if usr_coin is None.
  475. """
  476. from .altcoin import CoinInfo as ci
  477. data = { 'mainnet': (), 'testnet': () }
  478. networks = ['mainnet'] + (['testnet'] if testnet else [])
  479. network = 'testnet' if testnet else 'mainnet'
  480. if usr_coin == None:
  481. for network in networks:
  482. data[network] = ci.get_supported_coins(network)
  483. trust_level = 0
  484. else:
  485. if usr_coin.lower() in CoinProtocol.core_coins: # core coin, so return immediately
  486. return CoinProtocol.coins[usr_coin.lower()].trust_level
  487. for network in networks:
  488. data[network] = (ci.get_entry(usr_coin,network),)
  489. cinfo = data[network][0]
  490. if not cinfo:
  491. raise ValueError(f'{usr_coin.upper()!r}: unrecognized coin for network {network.upper()}')
  492. if cinfo.trust_level == -1:
  493. raise ValueError(f'{usr_coin.upper()!r}: unsupported (disabled) coin for network {network.upper()}')
  494. trust_level = cinfo.trust_level
  495. exec(make_init_genonly_altcoins_str(data),globals(),globals())
  496. return trust_level
  497. def make_init_genonly_altcoins_str(data):
  498. def make_proto(e,testnet=False):
  499. tn_str = 'Testnet' if testnet else ''
  500. proto = e.name + tn_str
  501. coin = e.symbol
  502. if proto[0] in '0123456789':
  503. proto = 'X_'+proto
  504. if hasattr(CoinProtocol,proto) or coin.lower() in CoinProtocol.coins:
  505. return ''
  506. def num2hexstr(n):
  507. return "'{:0{}x}'".format(n,(4,2)[n < 256])
  508. p2sh_info = ", {}: 'p2sh'".format(num2hexstr(e.p2sh_info[0])) if e.p2sh_info else ''
  509. sw_mmtype = ",'S'" if e.has_segwit else ''
  510. return f"""
  511. class {proto}(CoinProtocol.Bitcoin{tn_str}):
  512. base_coin = {coin!r}
  513. addr_ver_bytes = {{ {num2hexstr(e.p2pkh_info[0])}: 'p2pkh'{p2sh_info} }}
  514. wif_ver_num = {{ 'std': {num2hexstr(e.wif_ver_num)} }}
  515. mmtypes = ('L','C'{sw_mmtype})
  516. dfl_mmtype = 'L'
  517. mmcaps = ('key','addr')
  518. """.rstrip()
  519. def gen_text():
  520. yield "class CoinProtocol(CoinProtocol):"
  521. for e in data['mainnet']:
  522. yield make_proto(e)
  523. for e in data['testnet']:
  524. yield make_proto(e,testnet=True)
  525. for e in data['mainnet']:
  526. proto,coin = e.name,e.symbol
  527. if proto[0] in '0123456789':
  528. proto = 'X_'+proto
  529. if hasattr(CoinProtocol,proto) or coin.lower() in CoinProtocol.coins:
  530. continue
  531. yield 'CoinProtocol.coins[{!r}] = CoinProtocol.proto_info({!r},{})'.format(
  532. coin.lower(),
  533. proto,
  534. e.trust_level )
  535. return '\n'.join(gen_text()) + '\n'