def __init__( self, non_witness_utxo: Optional[Tx] = None, witness_utxo: Optional[TxOut] = None, partial_sigs: Optional[Mapping[Octets, Octets]] = None, sig_hash_type: Optional[int] = None, redeem_script: Octets = b"", witness_script: Octets = b"", hd_key_paths: Optional[Mapping[Octets, BIP32KeyOrigin]] = None, final_script_sig: Octets = b"", final_script_witness: Witness = Witness(), unknown: Optional[Mapping[Octets, Octets]] = None, check_validity: bool = True, ) -> None: self.non_witness_utxo = non_witness_utxo self.witness_utxo = witness_utxo # https://docs.python.org/3/tutorial/controlflow.html#default-argument-values self.partial_sigs = (decode_dict_bytes_bytes(partial_sigs) if partial_sigs else {}) self.sig_hash_type = sig_hash_type self.redeem_script = bytes_from_octets(redeem_script) self.witness_script = bytes_from_octets(witness_script) self.hd_key_paths = decode_hd_key_paths(hd_key_paths) self.final_script_sig = bytes_from_octets(final_script_sig) self.final_script_witness = final_script_witness self.unknown = dict(sorted(decode_dict_bytes_bytes(unknown).items())) if check_validity: self.assert_valid()
def check_output_pubkey(q: Octets, script: Octets, control: Octets, ec: Curve = secp256k1) -> bool: q = bytes_from_octets(q) script = bytes_from_octets(script) control = bytes_from_octets(control) if len(control) > 4129: # 33 + 32 * 128 raise BTClibValueError("Control block too long") m = (len(control) - 33) // 32 if len(control) != 33 + 32 * m: raise BTClibValueError("Invalid control block length") leaf_version = control[0] & 0xFE preimage = leaf_version.to_bytes(1, "big") + var_bytes.serialize(script) k = tagged_hash(b"TapLeaf", preimage) for j in range(m): e = control[33 + 32 * j:65 + 32 * j] if k < e: k = tagged_hash(b"TapBranch", k + e) else: k = tagged_hash(b"TapBranch", e + k) p_bytes = control[1:33] t_bytes = tagged_hash(b"TapTweak", p_bytes + k) p = int.from_bytes(p_bytes, "big") t = int.from_bytes(t_bytes, "big") # edge case that cannot be reproduced in the test suite if t >= ec.n: raise BTClibValueError("Invalid script tree hash") # pragma: no cover P = (p, secp256k1.y_even(p)) Q = secp256k1.add(P, mult(t)) return Q[0] == int.from_bytes(q, "big") and control[0] & 1 == Q[1] % 2
def test_utils(self): s_spaces = " 0C 28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D " b = bytes_from_octets(s_spaces) s = b.hex() # lower case, no spaces self.assertNotEqual(s, s_spaces) self.assertEqual(hash160(s_spaces), hash160(bytes_from_octets(s))) self.assertEqual(hash256(s_spaces), hash256(bytes_from_octets(s)))
def _rootxprv_from_seed( seed: Octets, version: Octets = NETWORKS["mainnet"].bip32_prv ) -> BIP32KeyData: """Return BIP32 root master extended private key from seed.""" seed = bytes_from_octets(seed) bitlenght = len(seed) * 8 if bitlenght < 128: raise BTClibValueError( f"too few bits for seed: {bitlenght} in '{hex_string(seed)}'" ) if bitlenght > 512: raise BTClibValueError( f"too many bits for seed: {bitlenght} in '{hex_string(seed)}'" ) hmac_ = hmac.new(b"Bitcoin seed", seed, "sha512").digest() k = b"\x00" + hmac_[:32] v = bytes_from_octets(version, 4) return BIP32KeyData( version=v, depth=0, parent_fingerprint=b"\x00" * 4, index=0, chain_code=hmac_[32:], key=k, )
def decode_dict_bytes_bytes( map_: Optional[Mapping[Octets, Octets]] ) -> Dict[bytes, bytes]: "Return the dataclass element from its json representation." # unknown could be sorted, partial_sigs cannot if map_ is None: return {} return {bytes_from_octets(k): bytes_from_octets(v) for k, v in map_.items()}
def _decode_from_bip32_deriv( bip32_deriv: Mapping[str, str]) -> Tuple[bytes, BIP32KeyOrigin]: # FIXME remove size checks to allow # the instantiation of invalid master_fingerprint and pub_key master_fingerprint = bytes_from_octets(bip32_deriv["master_fingerprint"], 4) der_path = indexes_from_bip32_path(bip32_deriv["path"]) key_origin = BIP32KeyOrigin(master_fingerprint, der_path) return bytes_from_octets(bip32_deriv["pub_key"]), key_origin
def check_witness(wit_ver: int, wit_prg: Octets) -> bytes: if not 0 <= int(wit_ver) < 17: err_msg = "invalid witness version: " err_msg += f"{wit_ver} not in 0..16" raise BTClibValueError(err_msg) if wit_ver == 0: return bytes_from_octets(wit_prg, (20, 32)) return bytes_from_octets(wit_prg, list(range(2, 41)))
def __init__(self, script: Octets = b"", check_validity: bool = True) -> None: self.script = bytes_from_octets(script) if check_validity: self.assert_valid()
def legacy(script_: Octets, tx: Tx, vin_i: int, hash_type: int) -> bytes: script_ = bytes_from_octets(script_) new_tx = deepcopy(tx) for txin in new_tx.vin: txin.script_sig = b"" # TODO: delete sig from script_ (even if non standard) new_tx.vin[vin_i].script_sig = script_ if hash_type & 0x1F == NONE: new_tx.vout = [] for i, txin in enumerate(new_tx.vin): if i != vin_i: txin.sequence = 0 if hash_type & 0x1F == SINGLE: # sig_hash single bug if vin_i >= len(new_tx.vout): return (256**31).to_bytes(32, byteorder="big", signed=False) new_tx.vout = new_tx.vout[:vin_i + 1] for txout in new_tx.vout[:-1]: txout.script_pub_key = ScriptPubKey(b"") txout.value = 0xFFFFFFFFFFFFFFFF for i, txin in enumerate(new_tx.vin): if i != vin_i: txin.sequence = 0 if hash_type & 0x80: new_tx.vin = [new_tx.vin[vin_i]] preimage = new_tx.serialize(include_witness=False, check_validity=False) preimage += hash_type.to_bytes(4, byteorder="little", signed=False) return hash256(preimage)
def point_from_bip340pub_key(x_Q: BIP340PubKey, ec: Curve = secp256k1) -> Point: """Return a verified-as-valid BIP340 public key as Point tuple. It supports: - BIP32 extended keys (bytes, string, or BIP32KeyData) - SEC Octets (bytes or hex-string, with 02, 03, or 04 prefix) - BIP340 Octets (bytes or hex-string, p-size Point x-coordinate) - native tuple """ # BIP 340 key as integer if isinstance(x_Q, int): return x_Q, ec.y_even(x_Q) # (tuple) Point, (dict or str) BIP32Key, or 33/65 bytes try: x_Q = point_from_pub_key(x_Q, ec)[0] return x_Q, ec.y_even(x_Q) except BTClibValueError: pass # BIP 340 key as bytes or hex-string if isinstance(x_Q, (str, bytes)): Q = bytes_from_octets(x_Q, ec.p_size) x_Q = int.from_bytes(Q, "big", signed=False) return x_Q, ec.y_even(x_Q) raise BTClibTypeError("not a BIP340 public key")
def decode_hd_key_paths( map_: Optional[Mapping[Octets, BIP32KeyOrigin]]) -> HdKeyPaths: "Return the dataclass element from its json representation." hd_key_paths = {bytes_from_octets(k): v for k, v in map_.items()} if map_ else {} return dict(sorted(hd_key_paths.items()))
def recover_pub_key_( key_id: int, msg_hash: Octets, sig: Union[Sig, Octets], lower_s: bool = True, hf: HashF = sha256, ) -> Point: """ECDSA public key recovery (SEC 1 v.2 section 4.1.6). See also: https://crypto.stackexchange.com/questions/18105/how-does-recovering-the-public-key-from-an-ecdsa-signature-work/18106#18106 """ if isinstance(sig, Sig): sig.assert_valid() else: sig = Sig.parse(sig) # The message msg_hash: a hf_len array hf_len = hf().digest_size msg_hash = bytes_from_octets(msg_hash, hf_len) c = challenge_(msg_hash, sig.ec, hf) # 1.5 QJ = _recover_pub_key_(key_id, c, sig.r, sig.s, lower_s, sig.ec) return sig.ec.aff_from_jac(QJ)
def __init__( self, redeem_script: Octets = b"", witness_script: Octets = b"", hd_key_paths: Optional[Mapping[Octets, BIP32KeyOrigin]] = None, unknown: Optional[Mapping[Octets, Octets]] = None, check_validity: bool = True, ) -> None: self.redeem_script = bytes_from_octets(redeem_script) self.witness_script = bytes_from_octets(witness_script) self.hd_key_paths = decode_hd_key_paths(hd_key_paths) self.unknown = dict(sorted(decode_dict_bytes_bytes(unknown).items())) if check_validity: self.assert_valid()
def op_pushdata(data: Octets) -> bytes: """Convert to canonical push: OP_PUSHDATA (if needed) | length | data. According to standardness rules (BIP-62) the minimum possible PUSHDATA operator must be used. Byte vectors on the stack are not allowed to be more than 520 bytes long. """ data = bytes_from_octets(data) out: List[bytes] = [] length = len(data) if length < 76: # 1-byte-length out.append(length.to_bytes(1, byteorder="little", signed=False)) elif length < 256: # OP_PUSHDATA1 | 1-byte-length out.append(OP_CODES["OP_PUSHDATA1"]) out.append(length.to_bytes(1, byteorder="little", signed=False)) elif length < 521: # OP_PUSHDATA2 | 2-byte-length out.append(OP_CODES["OP_PUSHDATA2"]) out.append(length.to_bytes(2, byteorder="little", signed=False)) else: # because of the 520 bytes limit # there is no need to use OP_PUSHDATA4 # out.append(OP_CODES['OP_PUSHDATA4']) # out.append(length.to_bytes(4, byteorder="little", signed=False)) raise BTClibValueError(f"too many bytes for OP_PUSHDATA: {length}") out.append(data) return b"".join(out)
def sign_( msg_hash: Octets, prv_key: PrvKey, nonce: Optional[PrvKey] = None, lower_s: bool = True, ec: Curve = secp256k1, hf: HashF = sha256, ) -> Sig: """Sign a hf_len bytes message according to ECDSA signature algorithm. If the deterministic nonce is not provided, the RFC6979 specification is used. """ # the message msg_hash: a hf_len array hf_len = hf().digest_size msg_hash = bytes_from_octets(msg_hash, hf_len) # the secret key q: an integer in the range 1..n-1. # SEC 1 v.2 section 3.2.1 q = int_from_prv_key(prv_key, ec) # the challenge c = challenge_(msg_hash, ec, hf) # 4, 5 # nonce: an integer in the range 1..n-1. if nonce is None: nonce = _rfc6979_(c, q, ec, hf) # 1 else: nonce = int_from_prv_key(nonce, ec) # second part delegated to helper function return _sign_(c, q, nonce, lower_s, ec)
def sign_( msg_hash: Octets, prv_key: PrvKey, nonce: Optional[PrvKey] = None, ec: Curve = secp256k1, hf: HashF = sha256, ) -> Sig: """Sign a hf_len bytes message according to BIP340 signature algorithm. If the deterministic nonce is not provided, the BIP340 specification (not RFC6979) is used. """ # the message msg_hash: a hf_len array hf_len = hf().digest_size msg_hash = bytes_from_octets(msg_hash, hf_len) # private and public keys q, x_Q = gen_keys(prv_key, ec) # nonce: an integer in the range 1..n-1. if nonce is None: nonce = _det_nonce_(msg_hash, q, x_Q, secrets.token_bytes(hf_len), ec, hf) nonce, x_K = gen_keys(nonce, ec) # the challenge c = challenge_(msg_hash, x_Q, x_K, ec, hf) return _sign_(c, q, nonce, x_K, ec)
def prv_keyinfo_from_prv_key(prv_key: PrvKey, network: Optional[str] = None, compressed: Optional[bool] = None) -> PrvkeyInfo: compr = True if compressed is None else compressed net = "mainnet" if network is None else network ec = NETWORKS[net].curve if isinstance(prv_key, int): q = prv_key elif isinstance(prv_key, BIP32KeyData): return _prv_keyinfo_from_xprv(prv_key, network, compressed) else: try: return _prv_keyinfo_from_xprvwif(prv_key, network, compressed) # FIXME: except the NotPrvKeyError only, let InvalidPrvKey go through except ValueError: pass # it must be octets try: prv_key = bytes_from_octets(prv_key, ec.n_size) q = int.from_bytes(prv_key, byteorder="big", signed=False) except ValueError as e: raise BTClibValueError(f"not a private key: {prv_key!r}") from e if not 0 < q < ec.n: raise BTClibValueError(f"private key not in 1..n-1: {hex(q).upper()}") return q, net, compr
def bin_str_entropy_from_bytes(bytes_entropy: Octets, bits: OneOrMoreInt = _bits) -> BinStr: """Return raw entropy from the input Octets entropy. Input entropy can be expressed as hex-string or bytes; it is never padded to satisfy the bit-size requirement. If more bits than required are provided, the leftmost ones are retained. Default bit-sizes are 128, 160, 192, 224, 256, or 512 bits. """ bytes_entropy = bytes_from_octets(bytes_entropy) # if a single int, make it a tuple if isinstance(bits, int): bits = (bits, ) # ascending unique sorting of allowed bits bits = sorted(set(bits)) n_bits = len(bytes_entropy) * 8 n_bits = min(n_bits, bits[-1]) if n_bits not in bits: err_msg = f"invalid number of bits: {n_bits} instead of {bits}" raise BTClibValueError(err_msg) int_entropy = int.from_bytes(bytes_entropy, byteorder="big", signed=False) # only the leftmost bits will be retained return bin_str_entropy_from_int(int_entropy, n_bits)
def assert_as_valid(msg: Octets, e0: bytes, s: SValues, pubk_rings: PubkeyRing) -> bool: msg = bytes_from_octets(msg) m = _get_msg_format(msg, pubk_rings) ring_size = len(pubk_rings) e: SValues = defaultdict(list) e0bytes = m for i in range(ring_size): keys_size = len(pubk_rings[i]) e[i] = [0] * keys_size e[i][0] = int_from_bits(_hash(m, e0, i, 0), ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if e[i][0] == 0: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover r = b"\0x00" for j in range(keys_size): t = double_mult(-e[i][j], pubk_rings[i][j], s[i][j], ec.G) r = bytes_from_point(t, ec) if j != len(pubk_rings[i]) - 1: h = _hash(m, r, i, j + 1) e[i][j + 1] = int_from_bits(h, ec.nlen) % ec.n # edge case that cannot be reproduced in the test suite if e[i][j + 1] == 0: err_msg = "implausibile signature failure" # pragma: no cover raise BTClibRuntimeError(err_msg) # pragma: no cover else: e0bytes += r e0_prime = hf(e0bytes).digest() return e0_prime == e0
def reduce_to_hlen(msg: Octets, hf: HashF = hashlib.sha256) -> bytes: msg = bytes_from_octets(msg) # Step 4 of SEC 1 v.2 section 4.1.3 h = hf() h.update(msg) return h.digest()
def test_hash160_hash256() -> None: test_vectors = (plain_prv_keys + net_unaware_compressed_pub_keys + net_unaware_uncompressed_pub_keys) for hexstring in test_vectors: b = bytes_from_octets(hexstring) s = b.hex() # lower case, no spaces assert hash160(hexstring) == hash160(s) assert hash256(hexstring) == hash256(s)
def parse(cls: Type["BIP32KeyOrigin"], data: Octets, check_validity: bool = True) -> "BIP32KeyOrigin": "Return a BIP32KeyOrigin by parsing binary data." data = bytes_from_octets(data) master_fingerprint = data[:4] der_path = indexes_from_bip32_path(data[4:]) return cls(master_fingerprint, der_path, check_validity)
def challenge_(msg_hash: Octets, ec: Curve = secp256k1, hf: HashF = hashlib.sha256) -> int: # the message msg_hash: a hf_len array hf_len = hf().digest_size msg_hash = bytes_from_octets(msg_hash, hf_len) # leftmost ec.nlen bits %= ec.n return int_from_bits(msg_hash, ec.nlen) % ec.n
def __init__(self, stack: Optional[Sequence[Octets]] = None, check_validity: bool = True) -> None: # https://docs.python.org/3/tutorial/controlflow.html#default-argument-values self.stack = [bytes_from_octets(element) for element in stack] if stack else [] if check_validity: self.assert_valid()
def det_nonce_( msg_hash: Octets, prv_key: PrvKey, aux: Optional[Octets] = None, ec: Curve = secp256k1, hf: HashF = sha256, ) -> int: "Return a BIP340 deterministic ephemeral key (nonce)." # the message msg_hash: a hf_len array hf_len = hf().digest_size msg_hash = bytes_from_octets(msg_hash, hf_len) q, Q = gen_keys(prv_key, ec) # the auxiliary random component aux = secrets.token_bytes(hf_len) if aux is None else bytes_from_octets(aux) return _det_nonce_(msg_hash, q, Q, aux, ec, hf)
def __init__( self, version: int = 1, previous_block_hash: Octets = b"", merkle_root_: Octets = b"", time: datetime = datetime.fromtimestamp(0), bits: Octets = b"", nonce: int = 0, check_validity: bool = True, ) -> None: self.version = version self.previous_block_hash = bytes_from_octets(previous_block_hash) self.merkle_root = bytes_from_octets(merkle_root_) self.time = time self.bits = bytes_from_octets(bits) self.nonce = nonce if check_validity: self.assert_valid()
def __init__( self, version: Octets, depth: int, parent_fingerprint: Octets, index: int, chain_code: Octets, key: Octets, check_validity: bool = True, ) -> None: self.version = bytes_from_octets(version) self.depth = depth self.parent_fingerprint = bytes_from_octets(parent_fingerprint) self.index = index self.chain_code = bytes_from_octets(chain_code) self.key = bytes_from_octets(key) if check_validity: self.assert_valid()
def test_utils(self): b = bytes_from_octets(int_with_whitespaces) s = b.hex() # lower case, no spaces self.assertNotEqual(int_with_whitespaces, s) self.assertEqual(hash160(int_with_whitespaces), hash160(s)) self.assertEqual(hash256(int_with_whitespaces), hash256(s)) i = secrets.randbits(256) self.assertEqual(i, int_from_integer(i)) self.assertEqual(i, int_from_integer(i.to_bytes(32, "big"))) self.assertEqual(i, int_from_integer(hex(i)))
def assert_p2wpkh(script_pub_key: Octets) -> None: script_pub_key = bytes_from_octets(script_pub_key, 22) # p2wpkh [OP_0, pub_key hash] # 0x0014{20-byte pub_key hash} if script_pub_key[0] != 0: err_msg = f"invalid witness version: {script_pub_key[0]}" err_msg += f" instead of {0}" raise BTClibValueError(err_msg) if script_pub_key[1] != 0x14: err_msg = f"invalid pub_key hash length marker: {script_pub_key[1]}" err_msg += f" instead of {0x14}" raise BTClibValueError(err_msg)
def assert_p2wsh(script_pub_key: Octets) -> None: script_pub_key = bytes_from_octets(script_pub_key, 34) # p2wsh [OP_0, redeem_script hash] # 0x0020{32-byte redeem_script hash} if script_pub_key[0] != 0: err_msg = f"invalid witness version: {script_pub_key[0]}" err_msg += f" instead of {0}" raise BTClibValueError(err_msg) if script_pub_key[1] != 0x20: err_msg = f"invalid redeem script hash length marker: {script_pub_key[1]}" err_msg += f" instead of {0x20}" raise BTClibValueError(err_msg)