Source code for bitcoinlib.encoding

# -*- coding: utf-8 -*-
#
#    BitcoinLib - Python Cryptocurrency Library
#    ENCODING - Methods for encoding and conversion
#    © 2016 - 2020 February - 1200 Web Development <http://1200wd.com/>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import os
import math
import numbers
from copy import deepcopy
import hashlib
import pyaes
import binascii
import unicodedata
import struct
from bitcoinlib.main import *
_logger = logging.getLogger(__name__)


SCRYPT_ERROR = None
USING_MODULE_SCRYPT = os.getenv("USING_MODULE_SCRYPT") not in ["false", "False", "0", "FALSE"]
try:
    if USING_MODULE_SCRYPT != False:
        import scrypt
        USING_MODULE_SCRYPT = True
except ImportError as SCRYPT_ERROR:
    pass
if 'scrypt' not in sys.modules:
    import pyscrypt as scrypt
    USING_MODULE_SCRYPT = False

if not USING_MODULE_SCRYPT:
    if 'scrypt_error' not in locals():
        SCRYPT_ERROR = 'unknown'
    _logger.warning("Error when trying to import scrypt module %s" % SCRYPT_ERROR)

USE_FASTECDSA = os.getenv("USE_FASTECDSA") not in ["false", "False", "0", "FALSE"]
try:
    if USE_FASTECDSA != False:
        from fastecdsa.encoding.der import DEREncoder
        USE_FASTECDSA = True
except ImportError:
    pass
if 'fastecdsa' not in sys.modules:
    _logger.warning("Could not include fastecdsa library, using slower ecdsa instead. ")
    USE_FASTECDSA = False
    import ecdsa


[docs]class EncodingError(Exception): """ Log and raise encoding errors """ def __init__(self, msg=''): self.msg = msg def __str__(self): return self.msg
bytesascii = b'' for bxn in range(256): bytesascii += bytes(bytearray((bxn,))) code_strings = { 2: b'01', 3: b' ,.', 10: b'0123456789', 16: b'0123456789abcdef', 32: b'abcdefghijklmnopqrstuvwxyz234567', 58: b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', 256: b''.join([bytes(bytearray((csx,))) for csx in range(256)]), 'bech32': b'qpzry9x8gf2tvdw0s3jn54khce6mua7l' } def _get_code_string(base): if base in code_strings: return code_strings[base] else: return list(range(0, base)) def _array_to_codestring(array, base): codebase = code_strings[base] codestring = "" for i in array: if not PY3: codestring += codebase[i] else: codestring += chr(codebase[i]) return codestring def _codestring_to_array(codestring, base): codestring = to_bytes(codestring) codebase = code_strings[base] array = [] for s in codestring: try: array.append(codebase.index(s)) except ValueError: raise EncodingError("Character '%s' not found in codebase" % s) return array
[docs]def normalize_var(var, base=256): """ For Python 2 convert variable to string For Python 3 convert to bytes Convert decimals to integer type :param var: input variable in any format :type var: str, byte, bytearray, unicode :param base: specify variable format, i.e. 10 for decimal, 16 for hex :type base: int :return: Normalized var in string for Python 2, bytes for Python 3, decimal for base10 """ try: if PY3 and isinstance(var, str): var = var.encode('ISO-8859-1') except ValueError: try: var = var.encode('utf-8') except ValueError: raise EncodingError("Unknown character '%s' in input format" % var) if not PY3 and isinstance(var, unicode): try: var = str(var) except UnicodeEncodeError: try: var = var.encode('utf-8') except ValueError: raise EncodingError("Cannot convert this unicode to string format") if base == 10: return int(var) elif isinstance(var, list): return deepcopy(var) else: return var
[docs]def change_base(chars, base_from, base_to, min_length=0, output_even=None, output_as_list=None): """ Convert input chars from one numeric base to another. For instance from hexadecimal (base-16) to decimal (base-10) From and to numeric base can be any base. If base is not found in definitions an array of index numbers will be returned Examples: >>> change_base('FF', 16, 10) 255 >>> change_base('101', 2, 10) 5 Convert base-58 public WIF of a key to hexadecimal format >>> change_base('xpub661MyMwAqRbcFnkbk13gaJba22ibnEdJS7KAMY99C4jBBHMxWaCBSTrTinNTc9G5LTFtUqbLpWnzY5yPTNEF9u8sB1kBSygy4UsvuViAmiR', 58, 16) '0488b21e0000000000000000007d3cc6702f48bf618f3f14cce5ee2cacf3f70933345ee4710af6fa4a330cc7d503c045227451b3454ca8b6022b0f0155271d013b58d57d322fd05b519753a46e876388698a' Convert base-58 address to public key hash: '00' + length '21' + 20 byte key >>> change_base('142Zp9WZn9Fh4MV8F3H5Dv4Rbg7Ja1sPWZ', 58, 16) '0021342f229392d7c9ed82c932916cee6517fbc9a2487cd97a' Convert to 2048-base, for example a Mnemonic word list. Will return a list of integers >>> change_base(100, 16, 2048) [100] :param chars: Input string :type chars: any :param base_from: Base number or name from input. For example 2 for binary, 10 for decimal and 16 for hexadecimal :type base_from: int :param base_to: Base number or name for output. For example 2 for binary, 10 for decimal and 16 for hexadecimal :type base_to: int :param min_length: Minimal output length. Required for decimal, advised for all output to avoid leading zeros conversion problems. :type min_length: int :param output_even: Specify if output must contain a even number of characters. Sometimes handy for hex conversions. :type output_even: bool :param output_as_list: Always output as list instead of string. :type output_as_list: bool :return str, list: Base converted input as string or list. """ if base_from == 10 and not min_length: raise EncodingError("For a decimal input a minimum output length is required") code_str = _get_code_string(base_to) if base_to not in code_strings: output_as_list = True code_str_from = _get_code_string(base_from) if not isinstance(code_str_from, (bytes, list)): raise EncodingError("Code strings must be a list or defined as bytes") output = [] input_dec = 0 addzeros = 0 inp = normalize_var(chars, base_from) # Use binascii and int for standard conversions to speedup things if not min_length: if base_from == 256 and base_to == 16: return to_hexstring(inp) elif base_from == 16 and base_to == 256: return binascii.unhexlify(inp) if base_from == 16 and base_to == 10 and PY3: return int(inp, 16) if base_from == 10 and base_to == 16 and PY3: hex_outp = hex(inp)[2:] return hex_outp.zfill(min_length) if min_length else hex_outp if base_from == 256 and base_to == 10 and PY3: return int.from_bytes(inp, 'big') if output_even is None and base_to == 16: output_even = True if isinstance(inp, numbers.Number): input_dec = inp elif isinstance(inp, (str, list, bytes, bytearray)): factor = 1 while len(inp): if isinstance(inp, list): item = inp.pop() else: item = inp[-1:] inp = inp[:-1] try: pos = code_str_from.index(item) except ValueError: try: pos = code_str_from.index(item.lower()) except ValueError: raise EncodingError("Unknown character %s found in input string" % item) input_dec += pos * factor # Add leading zero if there are leading zero's in input if not pos * factor: if not PY3: firstchar = code_str_from[0] else: firstchar = chr(code_str_from[0]).encode('utf-8') if isinstance(inp, list): if not len([x for x in inp if x != firstchar]): addzeros += 1 elif not len(inp.strip(firstchar)): addzeros += 1 factor *= base_from else: raise EncodingError("Unknown input format %s" % inp) # Convert decimal to output base while input_dec != 0: input_dec, remainder = divmod(input_dec, base_to) output = [code_str[remainder]] + output if base_to != 10: pos_fact = math.log(base_to, base_from) expected_length = len(str(chars)) / pos_fact zeros = int(addzeros / pos_fact) if addzeros == 1: zeros = 1 for _ in range(zeros): if base_to != 10 and not expected_length == len(output): output = [code_str[0]] + output # Add zero's to make even number of digits on Hex output (or if specified) if output_even and len(output) % 2: output = [code_str[0]] + output # Add leading zero's while len(output) < min_length: output = [code_str[0]] + output if not output_as_list and isinstance(output, list): if len(output) == 0: output = 0 elif not PY3: output = ''.join(output) else: co = '' for c in output: co += chr(c) output = co # elif isinstance(output[0], bytes): # output = b''.join(output) # elif isinstance(output[0], int): # co = '' # for c in output: # co += chr(c) # output = co # else: # output = ''.join(output) if base_to == 10: return int(0) or (output != '' and int(output)) if PY3 and base_to == 256 and not output_as_list: return output.encode('ISO-8859-1') else: return output
[docs]def varbyteint_to_int(byteint): """ Convert CompactSize Variable length integer in byte format to integer. See https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer for specification >>> varbyteint_to_int(to_bytes('fd1027')) (10000, 3) :param byteint: 1-9 byte representation :type byteint: bytes, list, bytearray :return (int, int): tuple wit converted integer and size """ if not isinstance(byteint, (bytes, list, bytearray)): raise EncodingError("Byteint must be a list or defined as bytes") if PY3 or isinstance(byteint, (list, bytearray)): ni = byteint[0] else: ni = ord(byteint[0]) if ni < 253: return ni, 1 if ni == 253: # integer of 2 bytes size = 2 elif ni == 254: # integer of 4 bytes size = 4 else: # integer of 8 bytes size = 8 return change_base(byteint[1:1+size][::-1], 256, 10), size + 1
[docs]def int_to_varbyteint(inp): """ Convert integer to CompactSize Variable length integer in byte format. See https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer for specification >>> to_hexstring(int_to_varbyteint(10000)) 'fd1027' :param inp: Integer to convert :type inp: int :return: byteint: 1-9 byte representation as integer """ if not isinstance(inp, numbers.Number): raise EncodingError("Input must be a number type") if inp < 0xfd: return struct.pack('B', inp) elif inp < 0xffff: return struct.pack('<cH', b'\xfd', inp) elif inp < 0xffffffff: return struct.pack('<cL', b'\xfe', inp) else: return struct.pack('<cQ', b'\xff', inp)
[docs]def convert_der_sig(signature, as_hex=True): """ Extract content from DER encoded string: Convert DER encoded signature to signature string. :param signature: DER signature :type signature: bytes :param as_hex: Output as hexstring :type as_hex: bool :return bytes, str: Signature """ if not signature: return "" if USE_FASTECDSA: r, s = DEREncoder.decode_signature(bytes(signature)) else: sg, junk = ecdsa.der.remove_sequence(signature) if junk != b'': raise EncodingError("Junk found in encoding sequence %s" % junk) r, sg = ecdsa.der.remove_integer(sg) s, sg = ecdsa.der.remove_integer(sg) sig = '%064x%064x' % (r, s) if as_hex: return sig else: return binascii.unhexlify(sig)
[docs]def der_encode_sig(r, s): """ Create DER encoded signature string with signature r and s value. :param r: r value of signature :type r: int :param s: s value of signature :type s: int :return bytes: """ if USE_FASTECDSA: return DEREncoder.encode_signature(r, s) else: rb = ecdsa.der.encode_integer(r) sb = ecdsa.der.encode_integer(s) return ecdsa.der.encode_sequence(rb, sb)
[docs]def addr_to_pubkeyhash(address, as_hex=False, encoding=None): """ Convert base58 or bech32 address to public key hash Wrapper for the :func:`addr_base58_to_pubkeyhash` and :func:`addr_bech32_to_pubkeyhash` method :param address: Crypto currency address in base-58 format :type address: str :param as_hex: Output as hexstring :type as_hex: bool :param encoding: Address encoding used: base58 or bech32. Default is base58. Try to derive from address if encoding=None is provided :type encoding: str :return bytes, str: public key hash """ if encoding == 'base58' or encoding is None: try: pkh = addr_base58_to_pubkeyhash(address, as_hex) except EncodingError: pkh = None if pkh is not None: return pkh if encoding == 'bech32' or encoding is None: return addr_bech32_to_pubkeyhash(address, as_hex=as_hex)
[docs]def addr_base58_to_pubkeyhash(address, as_hex=False): """ Convert Base58 encoded address to public key hash >>> addr_base58_to_pubkeyhash('142Zp9WZn9Fh4MV8F3H5Dv4Rbg7Ja1sPWZ', as_hex=True) '21342f229392d7c9ed82c932916cee6517fbc9a2' :param address: Crypto currency address in base-58 format :type address: str, bytes :param as_hex: Output as hexstring :type as_hex: bool :return bytes, str: Public Key Hash """ try: address = change_base(address, 58, 256, 25) except EncodingError as err: raise EncodingError("Invalid address %s: %s" % (address, err)) check = address[-4:] pkh = address[:-4] checksum = double_sha256(pkh)[0:4] assert (check == checksum), "Invalid address, checksum incorrect" if as_hex: return change_base(pkh, 256, 16)[2:] else: return pkh[1:]
[docs]def addr_bech32_to_pubkeyhash(bech, prefix=None, include_witver=False, as_hex=False): """ Decode bech32 / segwit address to public key hash >>> addr_bech32_to_pubkeyhash('bc1qy8qmc6262m68ny0ftlexs4h9paud8sgce3sf84', as_hex=True) '21c1bc695a56f47991e95ff26856e50f78d3c118' Validate the bech32 string, and determine HRP and data. Only standard data size of 20 and 32 bytes are excepted :param bech: Bech32 address to convert :type bech: str :param prefix: Address prefix called Human-readable part. Default is None and tries to derive prefix, for bitcoin specify 'bc' and for bitcoin testnet 'tb' :type prefix: str :param include_witver: Include witness version in output? Default is False :type include_witver: bool :param as_hex: Output public key hash as hex or bytes. Default is False :type as_hex: bool :return str: Public Key Hash """ if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (bech.lower() != bech and bech.upper() != bech): raise EncodingError("Invalid bech32 character in bech string") bech = bech.lower() pos = bech.rfind('1') if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: raise EncodingError("Invalid bech32 string length") if prefix and prefix != bech[:pos]: raise EncodingError("Invalid bech32 address. Prefix '%s', prefix expected is '%s'" % (bech[:pos], prefix)) else: hrp = bech[:pos] data = _codestring_to_array(bech[pos + 1:], 'bech32') hrp_expanded = [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] if not _bech32_polymod(hrp_expanded + data) == 1: raise EncodingError("Bech polymod check failed") data = data[:-6] decoded = bytearray(convertbits(data[1:], 5, 8, pad=False)) if decoded is None or len(decoded) < 2 or len(decoded) > 40: raise EncodingError("Invalid decoded data length, must be between 2 and 40") if data[0] > 16: raise EncodingError("Invalid decoded data length") if data[0] == 0 and len(decoded) not in [20, 32]: raise EncodingError("Invalid decoded data length, must be 20 or 32 bytes") prefix = b'' if include_witver: datalen = len(decoded) prefix = bytearray([data[0] + 0x50 if data[0] else 0, datalen]) if as_hex: return change_base(prefix + decoded, 256, 16) return prefix + decoded
[docs]def pubkeyhash_to_addr(pubkeyhash, prefix=None, encoding='base58'): """ Convert public key hash to base58 encoded address Wrapper for the :func:`pubkeyhash_to_addr_base58` and :func:`pubkeyhash_to_addr_bech32` method :param pubkeyhash: Public key hash :type pubkeyhash: bytes, str :param prefix: Prefix version byte of network, default is bitcoin '\x00' :type prefix: str, bytes :param encoding: Encoding of address to calculate: base58 or bech32. Default is base58 :type encoding: str :return str: Base58 or bech32 encoded address """ if encoding == 'base58': if prefix is None: prefix = b'\x00' return pubkeyhash_to_addr_base58(pubkeyhash, prefix) elif encoding == 'bech32': if prefix is None: prefix = 'bc' return pubkeyhash_to_addr_bech32(pubkeyhash, prefix) else: raise EncodingError("Encoding %s not supported" % encoding)
[docs]def pubkeyhash_to_addr_base58(pubkeyhash, prefix=b'\x00'): """ Convert public key hash to base58 encoded address >>> pubkeyhash_to_addr_base58('21342f229392d7c9ed82c932916cee6517fbc9a2') '142Zp9WZn9Fh4MV8F3H5Dv4Rbg7Ja1sPWZ' :param pubkeyhash: Public key hash :type pubkeyhash: bytes, str :param prefix: Prefix version byte of network, default is bitcoin '\x00' :type prefix: str, bytes :return str: Base-58 encoded address """ # prefix = to_bytes(prefix) key = to_bytearray(prefix) + to_bytearray(pubkeyhash) addr256 = key + double_sha256(key)[:4] return change_base(addr256, 256, 58)
[docs]def pubkeyhash_to_addr_bech32(pubkeyhash, prefix='bc', witver=0, separator='1'): """ Encode public key hash as bech32 encoded (segwit) address >>> pubkeyhash_to_addr_bech32('21c1bc695a56f47991e95ff26856e50f78d3c118') 'bc1qy8qmc6262m68ny0ftlexs4h9paud8sgce3sf84' Format of address is prefix/hrp + seperator + bech32 address + checksum For more information see BIP173 proposal at https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki :param pubkeyhash: Public key hash :type pubkeyhash: str, bytes, bytearray :param prefix: Address prefix or Human-readable part. Default is 'bc' an abbreviation of Bitcoin. Use 'tb' for testnet. :type prefix: str :param witver: Witness version between 0 and 16 :type witver: int :param separator: Separator char between hrp and data, should always be left to '1' otherwise its not standard. :type separator: str :return str: Bech32 encoded address """ if not isinstance(pubkeyhash, bytearray): pubkeyhash = bytearray(to_bytes(pubkeyhash)) if len(pubkeyhash) not in [20, 32]: if int(pubkeyhash[0]) != 0: witver = int(pubkeyhash[0]) - 0x50 pubkeyhash = pubkeyhash[2:] data = [witver] + convertbits(pubkeyhash, 8, 5) # Expand the HRP into values for checksum computation hrp_expanded = [ord(x) >> 5 for x in prefix] + [0] + [ord(x) & 31 for x in prefix] polymod = _bech32_polymod(hrp_expanded + data + [0, 0, 0, 0, 0, 0]) ^ 1 checksum = [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] return prefix + separator + _array_to_codestring(data, 'bech32') + _array_to_codestring(checksum, 'bech32')
def _bech32_polymod(values): """ Internal function that computes the Bech32 checksum """ generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] chk = 1 for value in values: top = chk >> 25 chk = (chk & 0x1ffffff) << 5 ^ value for i in range(5): chk ^= generator[i] if ((top >> i) & 1) else 0 return chk
[docs]def convertbits(data, frombits, tobits, pad=True): """ 'General power-of-2 base conversion' Source: https://github.com/sipa/bech32/tree/master/ref/python :param data: Data values to convert :type data: list, bytearray :param frombits: Number of bits in source data :type frombits: int :param tobits: Number of bits in result data :type tobits: int :param pad: Use padding zero's or not. Default is True :type pad: bool :return list: Converted values """ acc = 0 bits = 0 ret = [] maxv = (1 << tobits) - 1 max_acc = (1 << (frombits + tobits - 1)) - 1 for value in data: if not PY3 and isinstance(value, str): value = int(value, 16) if value < 0 or (value >> frombits): return None acc = ((acc << frombits) | value) & max_acc bits += frombits while bits >= tobits: bits -= tobits ret.append((acc >> bits) & maxv) if pad: if bits: ret.append((acc << (tobits - bits)) & maxv) elif bits >= frombits or ((acc << (tobits - bits)) & maxv): return None return ret
[docs]def varstr(string): """ Convert string to variably sized string: Bytestring preceded with length byte >>> to_hexstring(varstr(to_bytes('5468697320737472696e67206861732061206c656e677468206f66203330'))) '1e5468697320737472696e67206861732061206c656e677468206f66203330' :param string: String input :type string: bytes, str :return bytes: varstring """ s = normalize_var(string) if s == b'\0': return s return int_to_varbyteint(len(s)) + s
[docs]def to_bytearray(string): """ Convert String, Unicode or Bytes to Python 2 and 3 compatible ByteArray :param string: String, Unicode, Bytes or ByteArray :type string: bytes, str, bytearray :return bytearray: """ if isinstance(string, TYPE_TEXT): try: string = binascii.unhexlify(string) except (TypeError, binascii.Error): pass return bytearray(string)
[docs]def to_bytes(string, unhexlify=True): """ Convert String, Unicode or ByteArray to Bytes :param string: String to convert :type string: str, unicode, bytes, bytearray :param unhexlify: Try to unhexlify hexstring :type unhexlify: bool :return: Bytes var """ s = normalize_var(string) if unhexlify: try: s = binascii.unhexlify(s) return s except (TypeError, binascii.Error): pass return s
[docs]def to_hexstring(string): """ Convert Bytes or ByteArray to hexadecimal string >>> to_hexstring('\x12\xaa\xdd') '12aadd' :param string: Variable to convert to hex string :type string: bytes, bytearray, str :return: hexstring """ string = normalize_var(string) if isinstance(string, (str, bytes)): try: binascii.unhexlify(string) if PY3: return str(string, 'ISO-8859-1') else: return string except (TypeError, binascii.Error): pass s = binascii.hexlify(string) if PY3: return str(s, 'ISO-8859-1') else: return s
[docs]def normalize_string(string): """ Normalize a string to the default NFKD unicode format See https://en.wikipedia.org/wiki/Unicode_equivalence#Normalization :param string: string value :type string: bytes, bytearray, str :return: string """ if isinstance(string, str if sys.version < '3' else bytes): utxt = string.decode('utf8') elif isinstance(string, TYPE_TEXT): utxt = string else: raise TypeError("String value expected") return unicodedata.normalize('NFKD', utxt)
[docs]def double_sha256(string, as_hex=False): """ Get double SHA256 hash of string :param string: String to be hashed :type string: bytes :param as_hex: Return value as hexadecimal string. Default is False :type as_hex: bool :return bytes, str: """ if not as_hex: return hashlib.sha256(hashlib.sha256(string).digest()).digest() else: return hashlib.sha256(hashlib.sha256(string).digest()).hexdigest()
[docs]def hash160(string): """ Creates a RIPEMD-160 + SHA256 hash of the input string :param string: Script :type string: bytes :return bytes: RIPEMD-160 hash of script """ return hashlib.new('ripemd160', hashlib.sha256(string).digest()).digest()
[docs]def bip38_decrypt(encrypted_privkey, passphrase): """ BIP0038 non-ec-multiply decryption. Returns WIF private key. Based on code from https://github.com/nomorecoin/python-bip38-testing This method is called by Key class init function when importing BIP0038 key. :param encrypted_privkey: Encrypted private key using WIF protected key format :type encrypted_privkey: str :param passphrase: Required passphrase for decryption :type passphrase: str :return tupple (bytes, bytes): (Private Key bytes, 4 byte address hash for verification) """ d = change_base(encrypted_privkey, 58, 256)[2:] flagbyte = d[0:1] d = d[1:] if flagbyte == b'\xc0': compressed = False elif flagbyte == b'\xe0': compressed = True else: raise EncodingError("Unrecognised password protected key format. Flagbyte incorrect.") if isinstance(passphrase, str) and sys.version_info > (3,): passphrase = passphrase.encode('utf-8') addresshash = d[0:4] d = d[4:-4] key = scrypt.hash(passphrase, addresshash, 16384, 8, 8, 64) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] encryptedhalf1 = d[0:16] encryptedhalf2 = d[16:32] aes = pyaes.AESModeOfOperationECB(derivedhalf2) decryptedhalf2 = aes.decrypt(encryptedhalf2) decryptedhalf1 = aes.decrypt(encryptedhalf1) priv = decryptedhalf1 + decryptedhalf2 priv = binascii.unhexlify('%064x' % (int(binascii.hexlify(priv), 16) ^ int(binascii.hexlify(derivedhalf1), 16))) # if compressed: # # FIXME: This works but does probably not follow the BIP38 standards (was before: priv = b'\0' + priv) # priv += b'\1' return priv, addresshash, compressed
[docs]def bip38_encrypt(private_hex, address, passphrase, flagbyte=b'\xe0'): """ BIP0038 non-ec-multiply encryption. Returns BIP0038 encrypted private key Based on code from https://github.com/nomorecoin/python-bip38-testing :param private_hex: Private key in hex format :type private_hex: str :param address: Address string :type address: str :param passphrase: Required passphrase for encryption :type passphrase: str :param flagbyte: Flagbyte prefix for WIF :type flagbyte: bytes :return str: BIP38 passphrase encrypted private key """ if isinstance(address, str) and sys.version_info > (3,): address = address.encode('utf-8') if isinstance(passphrase, str) and sys.version_info > (3,): passphrase = passphrase.encode('utf-8') addresshash = double_sha256(address)[0:4] key = scrypt.hash(passphrase, addresshash, 16384, 8, 8, 64) derivedhalf1 = key[0:32] derivedhalf2 = key[32:64] aes = pyaes.AESModeOfOperationECB(derivedhalf2) encryptedhalf1 = aes.encrypt(binascii.unhexlify('%0.32x' % (int(private_hex[0:32], 16) ^ int(binascii.hexlify(derivedhalf1[0:16]), 16)))) encryptedhalf2 = aes.encrypt(binascii.unhexlify('%0.32x' % (int(private_hex[32:64], 16) ^ int(binascii.hexlify(derivedhalf1[16:32]), 16)))) encrypted_privkey = b'\x01\x42' + flagbyte + addresshash + encryptedhalf1 + encryptedhalf2 encrypted_privkey += double_sha256(encrypted_privkey)[:4] return change_base(encrypted_privkey, 256, 58)
[docs]class Quantity: """ Class to convert very large or very small numbers to a readable format. Provided value is converted to number between 0 and 1000, and a metric prefix will be added. >>> # Example - the Hashrate on 10th July 2020 >>> str(Quantity(122972532877979100000, 'H/s')) '122.973 EH/s' """ def __init__(self, value, units='', precision=3): """ Convert given value to number between 0 and 1000 and determine metric prefix :param value: Value as integer in base 0 :type value: int, float :param units: Base units, so 'g' for grams for instance :type units: str :param precision: Number of digits after the comma :type precision: int """ # Metric prefixes according to BIPM, the International System of Units (SI) in 10**3 steps self.prefix_list = list('yzafpnμm1kMGTPEZY') self.base = self.prefix_list.index('1') assert value > 0 self.absolute = value self.units = units self.precision = precision while (value < 1 or value > 1000) and 0 < self.base < len(self.prefix_list)-1: if value > 1000: self.base += 1 value /= 1000.0 elif value < 1000: self.base -= 1 value *= 1000.0 self.value = value def __str__(self): # > Python 3.6: return f"{self.value:4.{self.precision}f} {self.prefix_list[self.base]}{self.units}" return '%4.*f %s%s' % (self.precision, self.value, self.prefix_list[self.base], self.units)