util2.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. util2: Less frequently-used variables, classes and utility functions for the MMGen suite
  12. """
  13. import sys,re,time
  14. from .util import msg,suf,hexdigits,die
  15. def die_wait(delay,ev=0,s=''):
  16. assert isinstance(delay,int)
  17. assert isinstance(ev,int)
  18. if s:
  19. msg(s)
  20. time.sleep(delay)
  21. sys.exit(ev)
  22. def die_pause(ev=0,s=''):
  23. assert isinstance(ev,int)
  24. if s:
  25. msg(s)
  26. input('Press ENTER to exit')
  27. sys.exit(ev)
  28. # monkey-patch function for monero-python: permits its use with pycryptodome (e.g. MSYS2)
  29. # instead of the expected pycryptodomex
  30. def load_cryptodomex():
  31. try:
  32. import Cryptodome # cryptodomex
  33. except ImportError:
  34. try:
  35. import Crypto # cryptodome
  36. except ImportError:
  37. die(2,'Unable to import either the ‘pycryptodomex’ or ‘pycryptodome’ package')
  38. else:
  39. sys.modules['Cryptodome'] = Crypto
  40. # called with no arguments by pyethereum.utils:
  41. def get_keccak(cfg=None,cached_ret=[]):
  42. if not cached_ret:
  43. if cfg and cfg.use_internal_keccak_module:
  44. cfg._util.qmsg('Using internal keccak module by user request')
  45. from .contrib.keccak import keccak_256
  46. else:
  47. try:
  48. from Cryptodome.Hash import keccak
  49. except ImportError as e:
  50. try:
  51. from Crypto.Hash import keccak
  52. except ImportError as e2:
  53. msg(f'{e2} and {e}')
  54. die('MMGenImportError',
  55. 'Please install the ‘pycryptodome’ or ‘pycryptodomex’ package on your system')
  56. def keccak_256(data):
  57. return keccak.new(data=data,digest_bytes=32)
  58. cached_ret.append(keccak_256)
  59. return cached_ret[0]
  60. # From 'man dd':
  61. # c=1, w=2, b=512, kB=1000, K=1024, MB=1000*1000, M=1024*1024,
  62. # GB=1000*1000*1000, G=1024*1024*1024, and so on for T, P, E, Z, Y.
  63. bytespec_map = (
  64. ('c', 1),
  65. ('w', 2),
  66. ('b', 512),
  67. ('kB', 1000),
  68. ('K', 1024),
  69. ('MB', 1000000),
  70. ('M', 1048576),
  71. ('GB', 1000000000),
  72. ('G', 1073741824),
  73. ('TB', 1000000000000),
  74. ('T', 1099511627776),
  75. ('PB', 1000000000000000),
  76. ('P', 1125899906842624),
  77. ('EB', 1000000000000000000),
  78. ('E', 1152921504606846976),
  79. )
  80. def int2bytespec(n,spec,fmt,print_sym=True,strip=False,add_space=False):
  81. def spec2int(spec):
  82. for k,v in bytespec_map:
  83. if k == spec:
  84. return v
  85. else:
  86. die(1,f'{spec!r}: unrecognized bytespec')
  87. ret = f'{n/spec2int(spec):{fmt}f}'
  88. if strip:
  89. ret = ret.rstrip('0')
  90. return (
  91. ret
  92. + ('0' if ret.endswith('.') else '')
  93. + ((' ' if add_space else '') + spec if print_sym else '') )
  94. else:
  95. return (
  96. ret
  97. + ((' ' if add_space else '') + spec if print_sym else '') )
  98. def parse_bytespec(nbytes):
  99. m = re.match(r'([0123456789.]+)(.*)',nbytes)
  100. if m:
  101. if m.group(2):
  102. for k,v in bytespec_map:
  103. if k == m.group(2):
  104. from decimal import Decimal
  105. return int(Decimal(m.group(1)) * v)
  106. else:
  107. msg("Valid byte specifiers: '{}'".format("' '".join([i[0] for i in bytespec_map])))
  108. elif '.' in nbytes:
  109. raise ValueError('fractional bytes not allowed')
  110. else:
  111. return int(nbytes)
  112. die(1,f'{nbytes!r}: invalid byte specifier')
  113. def format_elapsed_days_hr(t,now=None,cached={}):
  114. e = int((now or time.time()) - t)
  115. if not e in cached:
  116. days = abs(e) // 86400
  117. cached[e] = f'{days} day{suf(days)} ' + ('ago' if e > 0 else 'in the future')
  118. return cached[e]
  119. def format_elapsed_hr(t, now=None, cached={}, rel_now=True, show_secs=False):
  120. e = int((now or time.time()) - t)
  121. key = f'{e}:{rel_now}:{show_secs}'
  122. if not key in cached:
  123. def add_suffix():
  124. return (
  125. ((' ago' if rel_now else '') if e > 0 else
  126. (' in the future' if rel_now else ' (negative elapsed)'))
  127. if (abs_e if show_secs else abs_e // 60) else
  128. ('just now' if rel_now else ('0 ' + ('seconds' if show_secs else 'minutes')))
  129. )
  130. abs_e = abs(e)
  131. data = (
  132. ('day', abs_e // 86400),
  133. ('hour', abs_e // 3600 % 24),
  134. ('minute', abs_e // 60 % 60),
  135. ('second', abs_e % 60),
  136. ) if show_secs else (
  137. ('day', abs_e // 86400),
  138. ('hour', abs_e // 3600 % 24),
  139. ('minute', abs_e // 60 % 60),
  140. )
  141. cached[key] = ' '.join(f'{n} {desc}{suf(n)}' for desc, n in data if n) + add_suffix()
  142. return cached[key]
  143. def pretty_format(s,width=80,pfx=''):
  144. out = []
  145. while s:
  146. if len(s) <= width:
  147. out.append(s)
  148. break
  149. i = s[:width].rfind(' ')
  150. out.append(s[:i])
  151. s = s[i+1:]
  152. return pfx + ('\n'+pfx).join(out)
  153. def block_format(data,gw=2,cols=8,line_nums=None,data_is_hex=False):
  154. assert line_nums in (None,'hex','dec'),"'line_nums' must be one of None, 'hex' or 'dec'"
  155. ln_fs = '{:06x}: ' if line_nums == 'hex' else '{:06}: '
  156. bytes_per_chunk = gw
  157. if data_is_hex:
  158. gw *= 2
  159. nchunks = len(data)//gw + bool(len(data)%gw)
  160. return ''.join(
  161. ('' if (line_nums is None or i % cols) else ln_fs.format(i*bytes_per_chunk))
  162. + data[i*gw:i*gw+gw]
  163. + (' ' if (not cols or (i+1) % cols) else '\n')
  164. for i in range(nchunks)
  165. ).rstrip() + '\n'
  166. def pretty_hexdump(data,gw=2,cols=8,line_nums=None):
  167. return block_format(data.hex(),gw,cols,line_nums,data_is_hex=True)
  168. def decode_pretty_hexdump(data):
  169. pat = re.compile(fr'^[{hexdigits}]+:\s+')
  170. lines = [pat.sub('',line) for line in data.splitlines()]
  171. try:
  172. return bytes.fromhex(''.join((''.join(lines).split())))
  173. except:
  174. msg('Data not in hexdump format')
  175. return False