protocol.py 22 KB

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