btc_mainnet.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
  5. """
  6. test.objtest_d.btc_mainnet: BTC mainnet test vectors for MMGen data objects
  7. """
  8. from decimal import Decimal
  9. from mmgen.obj import (
  10. Int,
  11. MMGenTxID,
  12. CoinTxID,
  13. MMGenWalletLabel,
  14. MMGenTxComment,
  15. MMGenPWIDString,
  16. TrackingWalletName)
  17. from mmgen.addrlist import AddrIdx, AddrIdxList, AddrListID
  18. from mmgen.seed import Seed, SeedID
  19. from mmgen.subseed import SubSeedList, SubSeedIdx, SubSeedIdxRange
  20. from mmgen.seedsplit import SeedShareIdx, SeedShareCount, MasterShareIdx, SeedSplitSpecifier
  21. from mmgen.key import PrivKey, WifKey, PubKey
  22. from mmgen.amt import BTCAmt
  23. from mmgen.addr import CoinAddr, MMGenID, MMGenAddrType, MMGenPasswordType
  24. from mmgen.tw.shared import TwMMGenID, TwLabel, TwComment
  25. from mmgen.rpc import IPPort
  26. from mmgen.protocol import init_proto
  27. from .common import r16, r32
  28. from ..include.common import (
  29. cfg,
  30. utf8_ctrl,
  31. utf8_text,
  32. utf8_combining,
  33. gr_uc_w_ctrl,
  34. text_jp,
  35. text_zh,
  36. ru_uc,
  37. gr_uc)
  38. proto = init_proto(cfg, 'btc', need_amt=True)
  39. tw_pfx = proto.base_coin.lower() + ':'
  40. zero_addr = '1111111111111111111114oLvT2'
  41. ssm = str(SeedShareCount.max_val)
  42. privkey = PrivKey(proto=proto, s=bytes.fromhex('deadbeef'*8), compressed=True, pubkey_type='std')
  43. tests = {
  44. 'Int': {
  45. 'arg1': 'n',
  46. 'bad': ('1L', 0.0, '0.0', '1.0', 1.0, 's', 1.1, '1.1'),
  47. 'good': (
  48. ('0', 0), ('-1', -1), ('7', 7), -1, 0, 1, 9999999,
  49. {'n': '0x0', 'base': 16, 'ret': 0},
  50. {'n': '0x1', 'base': 16, 'ret': 1},
  51. {'n': '0xf', 'base': 16, 'ret': 15},
  52. {'n': '0xff', 'base': 16, 'ret': 255},
  53. )
  54. },
  55. 'AddrIdx': {
  56. 'arg1': 'n',
  57. 'bad': ('s', 1.1, 10000000, -1, 0),
  58. 'good': (('7', 7), (1, 1), (9999999, 9999999))
  59. },
  60. 'SeedShareIdx': {
  61. 'arg1': 'n',
  62. 'bad': ('s', 1.1, 1025, -1, 0),
  63. 'good': (('7', 7), (1, 1), (1024, 1024))
  64. },
  65. 'SeedShareCount': {
  66. 'arg1': 'n',
  67. 'bad': ('s', 2.1, 1025, -1, 0, 1),
  68. 'good': (('7', 7), (2, 2), (1024, 1024))
  69. },
  70. 'MasterShareIdx': {
  71. 'arg1': 'n',
  72. 'bad': ('s', 1.1, 1025, -1, 0),
  73. 'good': (('7', 7), (1, 1), (1024, 1024))
  74. },
  75. 'AddrIdxList': {
  76. 'arg1': 'fmt_str',
  77. 'bad': (
  78. {'fmt_str': 'x'},
  79. {'fmt_str': '5,9,1-2-3'},
  80. {'fmt_str': '8,-11'},
  81. {'fmt_str': '66,3-2'},
  82. {'fmt_str': '0-3'},
  83. ),
  84. 'good': (
  85. {'fmt_str': '3,2,2', 'ret': (2, 3)},
  86. {'fmt_str': '101,1,3,5,2-7,99', 'ret': (1, 2, 3, 4, 5, 6, 7, 99, 101)},
  87. {'idx_list': AddrIdxList(fmt_str='1-5'), 'ret': (1, 2, 3, 4, 5)},
  88. )
  89. },
  90. 'SubSeedIdxRange': {
  91. 'bad': (33, 'x', '-11', '66,3','0', '3-2', '8000000', '100000000', (1, 2, 3)),
  92. 'good': (
  93. ('3', (3, 3)),
  94. ((3, 5), (3, 5)),
  95. ('1-2', (1, 2)),
  96. (str(SubSeedList.dfl_len), (SubSeedList.dfl_len, SubSeedList.dfl_len)),
  97. (str(SubSeedIdxRange.max_idx), (SubSeedIdxRange.max_idx, SubSeedIdxRange.max_idx)),
  98. )
  99. },
  100. 'BTCAmt': {
  101. 'arg1': 'num',
  102. 'bad': ('-3.2', '0.123456789', 123, '123L', '22000000', 20999999.12345678,
  103. {'num': '1', 'from_decimal': True},
  104. {'num': 1, 'from_decimal': True},
  105. ),
  106. 'good': (('20999999.12345678', Decimal('20999999.12345678')),
  107. # rounding
  108. {'num': Decimal('1.23456789623456789'), 'from_decimal': True, 'ret': Decimal('1.23456790')},
  109. {'num': Decimal('1.234'), 'from_decimal': True, 'ret': Decimal('1.234')},
  110. {'num': Decimal('0.0'), 'from_decimal': True, 'ret': Decimal('0')},
  111. # emulate network fee estimation:
  112. # BTC/kB fee_adjust tx size
  113. { 'num': Decimal('0.00053249') * Decimal('0.9') * 109 / 1024 , # ≈53 sat/byte
  114. 'from_decimal': True,
  115. 'ret': Decimal('0.00005101')},
  116. { 'num': Decimal('0.00003249') * Decimal('1.1') * 109 / 1024 , # ≈3 sat/byte
  117. 'from_decimal': True,
  118. 'ret': Decimal('0.00000380')},
  119. { 'num': Decimal('0.00011249') * Decimal('1.0') * 221 / 1024 , # ≈11 sat/byte
  120. 'from_decimal': True,
  121. 'ret': Decimal('0.00002428')},
  122. {'num': 1234, 'from_unit': 'satoshi', 'ret': Decimal('0.00001234')},
  123. )
  124. },
  125. 'CoinAddr': {
  126. 'arg1': 'addr',
  127. 'good': (
  128. {'addr': '1MjjELEy6EJwk8fSNfpS8b5teFRo4X5fZr', 'proto': proto},
  129. {'addr': '32GiSWo9zJQgkCmjAaLRrbPwXhKry2jHhj', 'proto': proto},
  130. ),
  131. 'bad': (
  132. {'addr': 1, 'proto': proto},
  133. {'addr': 'x', 'proto': proto},
  134. {'addr': 'я', 'proto': proto},
  135. ),
  136. },
  137. 'SeedID': {
  138. 'arg1': 'sid',
  139. 'bad': (
  140. {'sid': 'я'},
  141. {'sid': 'F00F00'},
  142. {'sid': 'xF00F00x'},
  143. {'sid': 1},
  144. {'sid': 'F00BAA123'},
  145. {'sid': 'f00baa12'},
  146. {'seed': r32},
  147. {'sid': 'abc'},
  148. ),
  149. 'good': (
  150. {'sid': 'F00BAA12'},
  151. {'seed': Seed(cfg, seed_bin=r16), 'ret': SeedID(seed=Seed(cfg, seed_bin=r16))},
  152. {'sid': Seed(cfg, seed_bin=r16).sid, 'ret': SeedID(seed=Seed(cfg, seed_bin=r16))}
  153. )
  154. },
  155. 'SubSeedIdx': {
  156. 'arg1': 's',
  157. 'bad': (33, 'x', 'я', '1x', 200, '1ss', 'L', 's', '200LS', '30ll', 's100',
  158. str(SubSeedIdxRange.max_idx+1), '0'),
  159. 'good': (
  160. ('1', '1L'), ('1s', '1S'), '20S', '30L', ('300l', '300L'), ('200', '200L'),
  161. str(SubSeedIdxRange.max_idx)+'S')
  162. },
  163. 'MMGenID': {
  164. 'arg1': 'id_str',
  165. 'bad': (
  166. {'id_str': 'x', 'proto': proto},
  167. {'id_str': 1, 'proto': proto},
  168. {'id_str': 'f00f00f', 'proto': proto},
  169. {'id_str': 'a:b', 'proto': proto},
  170. {'id_str': 'x:L:3', 'proto': proto},
  171. {'id_str': 'F00BAA12', 'proto': proto},
  172. {'id_str': 'F00BAA12:Z:99', 'proto': proto},
  173. ),
  174. 'good': (
  175. {'id_str': 'F00BAA12:99', 'proto': proto, 'ret': 'F00BAA12:L:99'},
  176. {'id_str': 'F00BAA12:L:99', 'proto': proto},
  177. {'id_str': 'F00BAA12:S:99', 'proto': proto},
  178. ),
  179. },
  180. 'TwMMGenID': {
  181. 'arg1': 'id_str',
  182. 'bad': (
  183. {'id_str': 'x', 'proto': proto},
  184. {'id_str': 'я', 'proto': proto},
  185. {'id_str': 'я:я', 'proto': proto},
  186. {'id_str': 1, 'proto': proto},
  187. {'id_str': 'f00f00f', 'proto': proto},
  188. {'id_str': 'a:b', 'proto': proto},
  189. {'id_str': 'x:L:3', 'proto': proto},
  190. {'id_str': 'F00BAA12:0', 'proto': proto},
  191. {'id_str': 'F00BAA12:Z:99', 'proto': proto},
  192. {'id_str': tw_pfx, 'proto': proto},
  193. {'id_str': tw_pfx+'я', 'proto': proto},
  194. {'id_str': tw_pfx+'x', 'proto': proto},
  195. ),
  196. 'good': (
  197. {'id_str': tw_pfx+zero_addr, 'proto': proto},
  198. {'id_str': 'F00BAA12:99', 'proto': proto, 'ret': 'F00BAA12:L:99'},
  199. {'id_str': 'F00BAA12:L:99', 'proto': proto},
  200. {'id_str': 'F00BAA12:S:9999999', 'proto': proto},
  201. ),
  202. },
  203. 'TrackingWalletName': {
  204. 'arg1': 's',
  205. 'bad': ('-abcdefg', 'a' * 50, 'abc-α-ω'),
  206. 'good': ('ab-cd-def_ghi-', '321-abcd')
  207. },
  208. 'TwLabel': {
  209. 'arg1': 'proto',
  210. 'exc_name': 'BadTwLabel',
  211. 'bad': (
  212. {'text': '', 'proto': proto},
  213. {'text': 'x x', 'proto': proto},
  214. {'text': 'x я', 'proto': proto},
  215. {'text': 'я:я', 'proto': proto},
  216. {'text': 1, 'proto': proto},
  217. {'text': 'f00f00f', 'proto': proto},
  218. {'text': 'a:b', 'proto': proto},
  219. {'text': 'x:L:3', 'proto': proto},
  220. {'text': 'F00BAA12:0 x', 'proto': proto},
  221. {'text': 'F00BAA12:Z:99', 'proto': proto},
  222. {'text': tw_pfx+' x', 'proto': proto},
  223. {'text': tw_pfx+'я x', 'proto': proto},
  224. {'text': utf8_ctrl[:40], 'proto': proto},
  225. {'text': 'F00BAA12:S:1 ' + utf8_ctrl[:40], 'proto': proto, 'exc_name': 'BadTwComment'},
  226. {'text': tw_pfx+'x comment', 'proto': proto},
  227. ),
  228. 'good': (
  229. {'text': 'F00BAA12:99 a comment', 'proto': proto, 'ret': 'F00BAA12:L:99 a comment'},
  230. {'text': 'F00BAA12:L:99 a comment', 'proto': proto},
  231. {'text': 'F00BAA12:L:99 comment (UTF-8) α', 'proto': proto},
  232. {'text': 'F00BAA12:S:9999999 comment', 'proto': proto},
  233. {'text': tw_pfx+zero_addr+' comment', 'proto': proto},
  234. ),
  235. },
  236. 'MMGenTxID': {
  237. 'arg1': 's',
  238. 'bad': (1, [], '\0', '\1', 'я', 'g', 'gg', 'FF', 'f00', 'F00F0012'),
  239. 'good': ('DEADBE', 'F00BAA')
  240. },
  241. 'CoinTxID': {
  242. 'arg1': 's',
  243. 'bad': (1, [], '\0', '\1', 'я', 'g', 'gg', 'FF', 'f00', 'F00F0012', r16.hex(), r32.hex()+'ee'),
  244. 'good': (r32.hex(),)
  245. },
  246. 'WifKey': {
  247. 'arg1': 'proto',
  248. 'bad': (
  249. {'proto': proto, 'wif': 1},
  250. {'proto': proto, 'wif': []},
  251. {'proto': proto, 'wif': '\0'},
  252. {'proto': proto, 'wif': '\1'},
  253. {'proto': proto, 'wif': 'я'},
  254. {'proto': proto, 'wif': 'g'},
  255. {'proto': proto, 'wif': 'gg'},
  256. {'proto': proto, 'wif': 'FF'},
  257. {'proto': proto, 'wif': 'f00'},
  258. {'proto': proto, 'wif': r16.hex()},
  259. {'proto': proto, 'wif': '2MspvWFjBbkv2wzQGqhxJUYPCk3Y2jMaxLN'},
  260. ),
  261. 'good': (
  262. {'proto': proto, 'wif': '5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb', 'ret_idx': 1},
  263. {'proto': proto, 'wif': 'KwWr9rDh8KK5TtDa3HLChEvQXNYcUXpwhRFUPc5uSNnMtqNKLFhk', 'ret_idx': 1},
  264. )
  265. },
  266. 'PubKey': {
  267. 'arg1': 's',
  268. 'bad': (
  269. {'s': 1, 'compressed': True},
  270. {'s': 'F00BAA12', 'compressed': False},
  271. ),
  272. 'good': ( # TODO: add real pubkeys
  273. {'s': bytes.fromhex('deadbeef'), 'compressed': True},
  274. )
  275. },
  276. 'PrivKey': {
  277. 'arg1': 'proto',
  278. 'bad': (
  279. {'proto': proto, 'wif': 1},
  280. {'proto': proto, 'wif': '1'},
  281. {'proto': proto, 'wif': 'cMsqcmDYZP1LdKgqRh9L4ZRU9br28yvdmTPwW2YQwVSN9aQiMAoR'},
  282. {'proto': proto, 's': r32, 'wif': '5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb'},
  283. {'proto': proto, 'pubkey_type': 'std'},
  284. {'proto': proto, 's': r32},
  285. {'proto': proto, 's': r32, 'compressed': 'yes'},
  286. {'proto': proto, 's': r32, 'compressed': 'yes', 'pubkey_type': 'std'},
  287. {'proto': proto, 's': r32, 'compressed': True, 'pubkey_type': 'nonstd'},
  288. {'proto': proto, 's': r32+b'x', 'compressed': True, 'pubkey_type': 'std'}
  289. ),
  290. 'good': (
  291. {'proto': proto, 'wif': '5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb',
  292. 'ret': bytes.fromhex('e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c')},
  293. {'proto': proto, 'wif': 'KwWr9rDh8KK5TtDa3HLChEvQXNYcUXpwhRFUPc5uSNnMtqNKLFhk',
  294. 'ret': bytes.fromhex('08d0ed83b64b68d56fa064be48e2385060ed205be2b1e63cd56d218038c3a05f')},
  295. {'proto': proto, 's': r32, 'compressed': False, 'pubkey_type': 'std', 'ret': r32},
  296. {'proto': proto, 's': r32, 'compressed': True, 'pubkey_type': 'std', 'ret': r32}
  297. )
  298. },
  299. 'AddrListID': {
  300. 'arg1': 'sid',
  301. 'bad': (
  302. {'sid': SeedID(sid='F00BAA12'), 'mmtype': 'Z', 'ret': 'F00BAA12:Z'},
  303. {'id_str': 'G00BAA12:B', 'proto': proto},
  304. ),
  305. 'good': (
  306. {'id_str': 'F00BAA12:B', 'proto': proto, 'ret': 'F00BAA12:B'},
  307. {'sid': SeedID(sid='F00BAA12'), 'mmtype': proto.addr_type(id_str='S'), 'ret': 'F00BAA12:S'},
  308. {'sid': SeedID(sid='F00BAA12'), 'mmtype': proto.addr_type(id_str='L'), 'ret': 'F00BAA12:L'},
  309. )
  310. },
  311. 'MMGenWalletLabel': {
  312. 'arg1': 's',
  313. 'bad': (utf8_text[:49], utf8_combining[:48], utf8_ctrl[:48], gr_uc_w_ctrl),
  314. 'good': (utf8_text[:48],)
  315. },
  316. 'TwComment': {
  317. 'exc_name': 'BadTwComment',
  318. 'arg1': 's',
  319. 'bad': ( utf8_combining[:40],
  320. utf8_ctrl[:40],
  321. text_jp[:41],
  322. text_zh[:41],
  323. gr_uc_w_ctrl,
  324. utf8_text[:81]),
  325. 'good': ( utf8_text[:80],
  326. (ru_uc + gr_uc + utf8_text)[:80],
  327. text_jp[:40],
  328. text_zh[:40])
  329. },
  330. 'MMGenTxComment': {
  331. 'arg1': 's',
  332. 'bad': (utf8_text[:73], utf8_combining[:72], utf8_ctrl[:72], gr_uc_w_ctrl),
  333. 'good': (utf8_text[:72],)
  334. },
  335. 'MMGenPWIDString': { # forbidden = list(u' :/\\')
  336. 'arg1': 's',
  337. 'bad': ('foo/', 'foo:', 'foo:\\'),
  338. 'good': ('qwerty@яяя',)
  339. },
  340. 'MMGenAddrType': {
  341. 'arg1': 'proto',
  342. 'bad': (
  343. {'proto': proto, 'id_str': 'U', 'ret': 'L'},
  344. {'proto': proto, 'id_str': 'z', 'ret': 'L'},
  345. {'proto': proto, 'id_str': 'xx', 'ret': 'C'},
  346. {'proto': proto, 'id_str': 'dogecoin', 'ret': 'C'},
  347. ),
  348. 'good': (
  349. {'proto': proto, 'id_str': 'legacy', 'ret': 'L'},
  350. {'proto': proto, 'id_str': 'L', 'ret': 'L'},
  351. {'proto': proto, 'id_str': 'compressed', 'ret': 'C'},
  352. {'proto': proto, 'id_str': 'C', 'ret': 'C'},
  353. {'proto': proto, 'id_str': 'segwit', 'ret': 'S'},
  354. {'proto': proto, 'id_str': 'S', 'ret': 'S'},
  355. {'proto': proto, 'id_str': 'bech32', 'ret': 'B'},
  356. {'proto': proto, 'id_str': 'B', 'ret': 'B'}
  357. )
  358. },
  359. 'MMGenPasswordType': {
  360. 'arg1': 'proto',
  361. 'bad': (
  362. {'proto': proto, 'id_str': 'U', 'ret': 'L'},
  363. {'proto': proto, 'id_str': 'z', 'ret': 'L'},
  364. {'proto': proto, 'id_str': 'я', 'ret': 'C'},
  365. {'proto': proto, 'id_str': 1, 'ret': 'C'},
  366. {'proto': proto, 'id_str': 'passw0rd', 'ret': 'C'},
  367. ),
  368. 'good': (
  369. {'proto': proto, 'id_str': 'password', 'ret': 'P'},
  370. {'proto': proto, 'id_str': 'P', 'ret': 'P'},
  371. )
  372. },
  373. 'SeedSplitSpecifier': {
  374. 'arg1': 's',
  375. 'bad': ('M', 'αβ:2', 1, '0:1', '1:1', '2:1', '3:2', '1:2000', 'abc:0:2'),
  376. 'good': (
  377. ('1:2', '2:2', 'alice:2:2', 'αβ:2:2', '1:'+ssm, ssm+':'+ssm)
  378. )
  379. },
  380. 'IPPort': {
  381. 'arg1': 's',
  382. 'bad': (
  383. '.0.0.0:0',
  384. '127.0.0.1',
  385. '127.0.0.1:',
  386. '20',
  387. ':20',
  388. '127.0.0.256:0',
  389. '127.0.0.1:65536',
  390. '127.0.0.01:60000',
  391. '0.0.0.0:00',
  392. ),
  393. 'good': (
  394. (
  395. '0.0.0.0:0',
  396. '0.0.0.1:0',
  397. '0.0.1.0:0',
  398. '0.0.1.1:0',
  399. '0.1.0.0:0',
  400. '1.0.0.0:0',
  401. '10.0.0.1:0',
  402. '127.0.0.1:65535',
  403. '255.0.0.0:400',
  404. '255.255.255.255:65535',
  405. )
  406. )
  407. },
  408. }