ut_baseconv.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. #!/usr/bin/env python3
  2. """
  3. test.modtest_d.ut_baseconv: Base conversion unit test for the MMGen suite
  4. """
  5. from mmgen.util import msg, msg_r
  6. from ..include.common import cfg, qmsg, qmsg_r, vmsg, vmsg_r
  7. class unit_test:
  8. vectors = {
  9. 'b58': (
  10. (('00', None), '1'),
  11. (('00', 1), '1'),
  12. (('00', 2), '11'),
  13. (('01', None), '2'),
  14. (('01', 1), '2'),
  15. (('01', 2), '12'),
  16. (('0f', None), 'G'),
  17. (('0f', 1), 'G'),
  18. (('0f', 2), '1G'),
  19. (('deadbeef', None), '6h8cQN'),
  20. (('deadbeef', 20), '111111111111116h8cQN'),
  21. (('00000000', None), '1'),
  22. (('00000000', 20), '11111111111111111111'),
  23. (('ffffffff', None), '7YXq9G'),
  24. (('ffffffff', 20), '111111111111117YXq9G'),
  25. (('ff'*16, 'seed'), 'YcVfxkQb6JRzqk5kF2tNLv'),
  26. (('ff'*24, 'seed'), 'QLbz7JHiBTspS962RLKV8GndWFwiEaqKL'),
  27. (('ff'*32, 'seed'), 'JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFG'),
  28. (('00'*16, 'seed'), '1111111111111111111111'),
  29. (('00'*24, 'seed'), '111111111111111111111111111111111'),
  30. (('00'*32, 'seed'), '11111111111111111111111111111111111111111111'),
  31. ),
  32. # MMGen-flavored base32 using simple base conversion
  33. 'b32': (
  34. (('00', None), 'A'),
  35. (('00', 1), 'A'),
  36. (('00', 2), 'AA'),
  37. (('01', None), 'B'),
  38. (('01', 1), 'B'),
  39. (('01', 2), 'AB'),
  40. (('0f', None), 'P'),
  41. (('0f', 1), 'P'),
  42. (('0f', 2), 'AP'),
  43. (('deadbeef', None), 'DPK3PXP'),
  44. (('deadbeef', 20), 'AAAAAAAAAAAAADPK3PXP'),
  45. (('00000000', None), 'A'),
  46. (('00000000', 20), 'AAAAAAAAAAAAAAAAAAAA'),
  47. (('ffffffff', None), 'D777777'),
  48. (('ffffffff', 20), 'AAAAAAAAAAAAAD777777'),
  49. ),
  50. 'b16': (
  51. (('00', None), '0'),
  52. (('00', 1), '0'),
  53. (('00', 2), '00'),
  54. (('01', None), '1'),
  55. (('01', 1), '1'),
  56. (('01', 2), '01'),
  57. (('0f', None), 'f'),
  58. (('0f', 1), 'f'),
  59. (('0f', 2), '0f'),
  60. (('deadbeef', None), 'deadbeef'),
  61. (('deadbeef', 20), '000000000000deadbeef'),
  62. (('00000000', None), '0'),
  63. (('00000000', 20), '00000000000000000000'),
  64. (('ffffffff', None), 'ffffffff'),
  65. (('ffffffff', 20), '000000000000ffffffff'),
  66. ),
  67. 'b10': (
  68. (('00', None), '0'),
  69. (('00', 1), '0'),
  70. (('00', 2), '00'),
  71. (('01', None), '1'),
  72. (('01', 1), '1'),
  73. (('01', 2), '01'),
  74. (('0f', None), '15'),
  75. (('0f', 1), '15'),
  76. (('0f', 2), '15'),
  77. (('deadbeef', None), '3735928559'),
  78. (('deadbeef', 20), '00000000003735928559'),
  79. (('00000000', None), '0'),
  80. (('00000000', 20), '00000000000000000000'),
  81. (('ffffffff', None), '4294967295'),
  82. (('ffffffff', 20), '00000000004294967295'),
  83. ),
  84. 'b8': (
  85. (('00', None), '0'),
  86. (('00', 1), '0'),
  87. (('00', 2), '00'),
  88. (('01', None), '1'),
  89. (('01', 1), '1'),
  90. (('01', 2), '01'),
  91. (('0f', None), '17'),
  92. (('0f', 1), '17'),
  93. (('0f', 2), '17'),
  94. (('deadbeef', None), '33653337357'),
  95. (('deadbeef', 20), '00000000033653337357'),
  96. (('00000000', None), '0'),
  97. (('00000000', 20), '00000000000000000000'),
  98. (('ffffffff', None), '37777777777'),
  99. (('ffffffff', 20), '00000000037777777777'),
  100. ),
  101. 'b6d': (
  102. (('00', None), '1'),
  103. (('00', 1), '1'),
  104. (('00', 2), '11'),
  105. (('01', None), '2'),
  106. (('01', 1), '2'),
  107. (('01', 2), '12'),
  108. (('0f', None), '34'),
  109. (('0f', 1), '34'),
  110. (('0f', 2), '34'),
  111. (('010f', None), '2242'),
  112. (('010f', 20), '11111111111111112242'),
  113. (('deadbeef', None), '2525524636426'),
  114. (('deadbeef', 20), '11111112525524636426'),
  115. (('00000000', None), '1'),
  116. (('00000000', 20), '11111111111111111111'),
  117. (('ffffffff', None), '2661215126614'),
  118. (('ffffffff', 20), '11111112661215126614'),
  119. (('ff'*16, 'seed'), '34164464641266661652465154654653354436664555521414'),
  120. (('ff'*24, 'seed'), '246111411433323364222465566552324652566121541623426135163525451613554313654'),
  121. (('ff'*32, 'seed'), '2132521653312613134145131423465414636252114131225324246311141642456513416322412146151432142242565134'),
  122. (('00'*16, 'seed'), '1'*50),
  123. (('00'*24, 'seed'), '1'*75),
  124. (('00'*32, 'seed'), '1'*100),
  125. ),
  126. 'mmgen': (
  127. (
  128. ('deadbeefdeadbeefdeadbeefdeadbeef', 'seed'),
  129. 'table cast forgive master funny gaze sadness ripple million paint moral match'
  130. ), (
  131. ('deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', 'seed'),
  132. 'swirl maybe anymore mix scale stray fog use approach page crime rhyme ' +
  133. 'class former strange window snap soon'
  134. ), (
  135. ('deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', 'seed'),
  136. 'swell type milk figure cheese phone fill black test bloom heard comfort ' +
  137. 'image terrible radio lesson own reply battle goal goodbye need laugh stream'
  138. ), (
  139. ('ffffffffffffffffffffffffffffffff', 'seed'),
  140. 'yellow yeah show bowl season spider cling defeat poison law shelter reflect'
  141. ), (
  142. ('ffffffffffffffffffffffffffffffffffffffffffffffff', 'seed'),
  143. 'yeah youth quit fail perhaps drum out person young click skin ' +
  144. 'weird inside silently perfectly together anyone memory'
  145. ), (
  146. ('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'seed'),
  147. 'wrote affection object cell opinion here laughter stare honest north cost begin ' +
  148. 'murder something yourself effort acid dot doubt game broke tell guilt innocent'
  149. ), (
  150. ('00000000000000000000000000000001', 'seed'),
  151. 'able ' * 11 + 'about'
  152. ), (
  153. ('000000000000000000000000000000000000000000000001', 'seed'),
  154. 'able ' * 17 + 'about'
  155. ), (
  156. ('0000000000000000000000000000000000000000000000000000000000000001', 'seed'),
  157. 'able ' * 23 + 'about'
  158. ),
  159. ),
  160. }
  161. def run_test(self, name, ut):
  162. msg_r('Testing base conversion routines...')
  163. from mmgen.baseconv import baseconv
  164. perr = "length of {!r} less than pad length ({})"
  165. rerr = "return value ({!r}) does not match reference value ({!r})"
  166. qmsg_r('\nChecking hex-to-base conversion:')
  167. for base, data in self.vectors.items():
  168. fs = " {h:%s} {p:<6} {r}" % max(len(d[0][0]) for d in data)
  169. if not cfg.verbose:
  170. qmsg_r(f' {base}')
  171. vmsg(f'\nBase: {base}')
  172. vmsg(fs.format(h='Input', p='Pad', r='Output'))
  173. for (hexstr, pad), ret_chk in data:
  174. ret = baseconv(base).fromhex(hexstr, pad=pad, tostr=True)
  175. if pad != 'seed':
  176. assert len(ret) >= (pad or 0), perr.format(ret, pad or 0)
  177. assert ret == ret_chk, rerr.format(ret, ret_chk)
  178. vmsg(fs.format(h=hexstr, r=ret, p=str(pad)))
  179. qmsg_r('\nChecking base-to-hex conversion:')
  180. for base, data in self.vectors.items():
  181. fs = " {h:%s} {p:<6} {r}" % max(len(d[1]) for d in data)
  182. if not cfg.verbose:
  183. qmsg_r(f' {base}')
  184. vmsg(f'\nBase: {base}')
  185. vmsg(fs.format(h='Input', p='Pad', r='Output'))
  186. for (hexstr, pad), ret_chk in data:
  187. if type(pad) is int:
  188. pad = len(hexstr)
  189. ret = baseconv(base).tohex( ret_chk.split() if base == 'mmgen' else ret_chk, pad=pad )
  190. if pad is None:
  191. assert int(ret, 16) == int(hexstr, 16), rerr.format(int(ret, 16), int(hexstr, 16))
  192. else:
  193. assert ret == hexstr, rerr.format(ret, hexstr)
  194. vmsg(fs.format(h=ret_chk, r=ret, p=str(pad)))
  195. qmsg_r('\nChecking wordlist checksums:')
  196. vmsg('')
  197. for wl_id in baseconv.constants['wl_chksum']:
  198. vmsg_r(f' {wl_id+":":9}')
  199. baseconv(wl_id).check_wordlist(cfg)
  200. qmsg('')
  201. qmsg('Checking error handling:')
  202. bad_b58 = 'I'*22
  203. bad_b58len = 'a'*23
  204. fr58 = baseconv('b58').fromhex
  205. to58 = baseconv('b58').tohex
  206. to32 = baseconv('b32').tohex
  207. to8 = baseconv('b8').tohex
  208. hse = 'HexadecimalStringError'
  209. bce = 'BaseConversionError'
  210. bpe = 'BaseConversionPadError'
  211. sle = 'SeedLengthError'
  212. bad_data = (
  213. ('hexstr', hse, ': not a hexadecimal str', lambda: fr58('x')),
  214. ('hexstr (seed)', hse, 'seed data not a hexadec', lambda: fr58('x', pad='seed')),
  215. ('hexstr (empty)', bce, 'empty data not allowed', lambda: fr58('')),
  216. ('b58 data', bce, ': not in base58', lambda: to58('IfFzZ')),
  217. ('b58 data (seed)', bce, 'seed data not in base58', lambda: to58(bad_b58, pad='seed')),
  218. ('b58 len (seed)', bce, 'invalid length for', lambda: to58(bad_b58len, pad='seed')),
  219. ('b58 data (empty)', bce, 'empty base58 data', lambda: to58('')),
  220. ('b8 data (empty)' , bce, 'empty base8 string data', lambda: to8('')),
  221. ('b32 data', bce, 'not in MMGen base32', lambda: to32('1az')),
  222. ('pad arg (in)', bpe, "illegal value for 'pad'", lambda: fr58('ff', pad='foo')),
  223. ('pad arg (in)', bpe, "illegal value for 'pad'", lambda: fr58('ff', pad=False)),
  224. ('pad arg (in)', bpe, "illegal value for 'pad'", lambda: fr58('ff', pad=True)),
  225. ('seedlen (in)', sle, 'invalid byte length', lambda: fr58('ff', pad='seed')),
  226. ('pad arg (out)', bpe, "illegal value for 'pad'", lambda: to58('Z', pad='foo')),
  227. ('pad arg (out)', bpe, "illegal value for 'pad'", lambda: to58('Z', pad=False)),
  228. ('pad arg (out)', bpe, "illegal value for 'pad'", lambda: to58('Z', pad=True)),
  229. ('seedlen (out)', bce, 'invalid length for seed', lambda: to58('Z', pad='seed')),
  230. )
  231. ut.process_bad_data(bad_data)
  232. msg('OK')
  233. return True