util.py 8.2 KB

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