util.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. # 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. tool.util: Utility commands for the 'mmgen-tool' utility
  20. """
  21. import sys, os
  22. from .common import tool_cmd_base
  23. class tool_cmd(tool_cmd_base):
  24. "general string conversion and hashing utilities"
  25. # mmgen.util2.bytespec_map
  26. def bytespec(self, dd_style_byte_specifier: str):
  27. """
  28. convert a byte specifier such as ‘4GB’ into an integer
  29. Valid specifiers:
  30. c = 1
  31. w = 2
  32. b = 512
  33. kB = 1000
  34. K = 1024
  35. MB = 1000000
  36. M = 1048576
  37. GB = 1000000000
  38. G = 1073741824
  39. TB = 1000000000000
  40. T = 1099511627776
  41. PB = 1000000000000000
  42. P = 1125899906842624
  43. EB = 1000000000000000000
  44. E = 1152921504606846976
  45. """
  46. from ..util2 import parse_bytespec
  47. return parse_bytespec(dd_style_byte_specifier)
  48. # mmgen.util2.bytespec_map
  49. def to_bytespec(self,
  50. n: int,
  51. dd_style_byte_specifier: str,
  52. *,
  53. fmt: 'width and precision of output' = '0.2',
  54. print_sym: 'print the specifier after the numerical value' = True,
  55. strip: 'strip trailing zeroes' = False,
  56. add_space: 'with print_sym, add space between value and specifier' = False):
  57. """
  58. convert an integer to a byte specifier such as ‘4GB’
  59. Supported specifiers:
  60. c = 1
  61. w = 2
  62. b = 512
  63. kB = 1000
  64. K = 1024
  65. MB = 1000000
  66. M = 1048576
  67. GB = 1000000000
  68. G = 1073741824
  69. TB = 1000000000000
  70. T = 1099511627776
  71. PB = 1000000000000000
  72. P = 1125899906842624
  73. EB = 1000000000000000000
  74. E = 1152921504606846976
  75. """
  76. from ..util2 import int2bytespec
  77. return int2bytespec(
  78. n,
  79. dd_style_byte_specifier,
  80. fmt,
  81. print_sym = print_sym,
  82. strip = strip,
  83. add_space = add_space)
  84. def randhex(self,
  85. nbytes: 'number of bytes to output' = 32):
  86. "print 'n' bytes (default 32) of random data in hex format"
  87. from ..crypto import Crypto
  88. return Crypto(self.cfg).get_random(nbytes).hex()
  89. def hexreverse(self, hexstr: 'sstr'):
  90. "reverse bytes of a hexadecimal string"
  91. return bytes.fromhex(hexstr.strip())[::-1].hex()
  92. def hexlify(self, infile: str):
  93. "convert bytes in file to hexadecimal (use '-' for stdin)"
  94. from ..fileutil import get_data_from_file
  95. data = get_data_from_file(self.cfg, infile, dash=True, quiet=True, binary=True)
  96. return data.hex()
  97. def unhexlify(self, hexstr: 'sstr'):
  98. "convert a hexadecimal string to bytes (warning: outputs binary data)"
  99. return bytes.fromhex(hexstr)
  100. def hexdump(self,
  101. infile: str,
  102. *,
  103. cols: 'number of columns in output' = 8,
  104. line_nums: "format for line numbers (valid choices: 'hex','dec')" = 'hex'):
  105. "create hexdump of data from file (use '-' for stdin)"
  106. from ..fileutil import get_data_from_file
  107. from ..util2 import pretty_hexdump
  108. data = get_data_from_file(self.cfg, infile, dash=True, quiet=True, binary=True)
  109. return pretty_hexdump(data, cols=cols, line_nums=line_nums).rstrip()
  110. def unhexdump(self, infile: str):
  111. "decode hexdump from file (use '-' for stdin) (warning: outputs binary data)"
  112. if sys.platform == 'win32':
  113. import msvcrt
  114. msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
  115. from ..fileutil import get_data_from_file
  116. from ..util2 import decode_pretty_hexdump
  117. hexdata = get_data_from_file(self.cfg, infile, dash=True, quiet=True)
  118. return decode_pretty_hexdump(hexdata)
  119. def hash160(self, hexstr: 'sstr'):
  120. "compute ripemd160(sha256(data)) (convert hex pubkey to hex addr)"
  121. from ..proto.btc.common import hash160
  122. return hash160(bytes.fromhex(hexstr)).hex()
  123. # TODO: handle stdin
  124. def hash256(self,
  125. data: str,
  126. *,
  127. file_input: 'first arg is the name of a file containing the data' = False,
  128. hex_input: 'first arg is a hexadecimal string' = False):
  129. "compute sha256(sha256(data)) (double sha256)"
  130. from hashlib import sha256
  131. if file_input:
  132. from ..fileutil import get_data_from_file
  133. b = get_data_from_file(self.cfg, data, binary=True)
  134. elif hex_input:
  135. from ..util2 import decode_pretty_hexdump
  136. b = decode_pretty_hexdump(data)
  137. else:
  138. b = data
  139. return sha256(sha256(b.encode()).digest()).hexdigest()
  140. def id6(self, infile: str):
  141. "generate 6-character MMGen ID for a file (use '-' for stdin)"
  142. from ..util import make_chksum_6
  143. from ..fileutil import get_data_from_file
  144. return make_chksum_6(
  145. get_data_from_file(self.cfg, infile, dash=True, quiet=True, binary=True))
  146. def str2id6(self, string: 'sstr'): # retain ignoring of space for backwards compat
  147. "generate 6-character MMGen ID for a string, ignoring spaces in string"
  148. from ..util import make_chksum_6
  149. return make_chksum_6(''.join(string.split()))
  150. def id8(self, infile: str):
  151. "generate 8-character MMGen ID for a file (use '-' for stdin)"
  152. from ..util import make_chksum_8
  153. from ..fileutil import get_data_from_file
  154. return make_chksum_8(
  155. get_data_from_file(self.cfg, infile, dash=True, quiet=True, binary=True))
  156. def randb58(self, *,
  157. nbytes: 'number of bytes to output' = 32,
  158. pad: 'pad output to this width' = 0):
  159. "generate random data (default: 32 bytes) and convert it to base 58"
  160. from ..baseconv import baseconv
  161. from ..crypto import Crypto
  162. return baseconv('b58').frombytes(Crypto(self.cfg).get_random(nbytes), pad=pad, tostr=True)
  163. def bytestob58(self, infile: str, *, pad: 'pad output to this width' = 0):
  164. "convert bytes to base 58 (supply data via STDIN)"
  165. from ..fileutil import get_data_from_file
  166. from ..baseconv import baseconv
  167. data = get_data_from_file(self.cfg, infile, dash=True, quiet=True, binary=True)
  168. return baseconv('b58').frombytes(data, pad=pad, tostr=True)
  169. def b58tobytes(self, b58_str: 'sstr', *, pad: 'pad output to this width' = 0):
  170. "convert a base 58 string to bytes (warning: outputs binary data)"
  171. from ..baseconv import baseconv
  172. return baseconv('b58').tobytes(b58_str, pad=pad)
  173. def hextob58(self, hexstr: 'sstr', *, pad: 'pad output to this width' = 0):
  174. "convert a hexadecimal string to base 58"
  175. from ..baseconv import baseconv
  176. return baseconv('b58').fromhex(hexstr, pad=pad, tostr=True)
  177. def b58tohex(self, b58_str: 'sstr', *, pad: 'pad output to this width' = 0):
  178. "convert a base 58 string to hexadecimal"
  179. from ..baseconv import baseconv
  180. return baseconv('b58').tohex(b58_str, pad=pad)
  181. def hextob58chk(self, hexstr: 'sstr'):
  182. "convert a hexadecimal string to base58-check encoding"
  183. from ..proto.btc.common import b58chk_encode
  184. return b58chk_encode(bytes.fromhex(hexstr))
  185. def b58chktohex(self, b58chk_str: 'sstr'):
  186. "convert a base58-check encoded string to hexadecimal"
  187. from ..proto.btc.common import b58chk_decode
  188. return b58chk_decode(b58chk_str).hex()
  189. def hextob32(self, hexstr: 'sstr', *, pad: 'pad output to this width' = 0):
  190. "convert a hexadecimal string to an MMGen-flavor base 32 string"
  191. from ..baseconv import baseconv
  192. return baseconv('b32').fromhex(hexstr, pad=pad, tostr=True)
  193. def b32tohex(self, b32_str: 'sstr', *, pad: 'pad output to this width' = 0):
  194. "convert an MMGen-flavor base 32 string to hexadecimal"
  195. from ..baseconv import baseconv
  196. return baseconv('b32').tohex(b32_str.upper(), pad=pad)
  197. def hextob6d(self,
  198. hexstr: 'sstr',
  199. *,
  200. pad: 'pad output to this width' = 0,
  201. add_spaces: 'add a space after every 5th character' = True):
  202. "convert a hexadecimal string to die roll base6 (base6d)"
  203. from ..baseconv import baseconv
  204. from ..util2 import block_format
  205. ret = baseconv('b6d').fromhex(hexstr, pad=pad, tostr=True)
  206. return block_format(ret, gw=5, cols=None).strip() if add_spaces else ret
  207. def b6dtohex(self, b6d_str: 'sstr', *, pad: 'pad output to this width' = 0):
  208. "convert a die roll base6 (base6d) string to hexadecimal"
  209. from ..baseconv import baseconv
  210. from ..util import remove_whitespace
  211. return baseconv('b6d').tohex(remove_whitespace(b6d_str), pad=pad)