def serialize_dict_bytes_bytes(type_: bytes, dictionary: Mapping[bytes, bytes]) -> bytes: "Return the binary representation of the dataclass element." return b"".join([ var_bytes.serialize(type_ + k) + var_bytes.serialize(v) for k, v in sorted(dictionary.items()) ])
def serialize_hd_key_paths( type_: bytes, hd_key_paths: Mapping[bytes, BIP32KeyOrigin]) -> bytes: "Return the binary representation of the dataclass element." if len(type_) != 1: err_msg = f"invalid type marker lenght: {len(type_)}, instead of 1" raise BTClibValueError(err_msg) return b"".join([ var_bytes.serialize(type_ + k) + var_bytes.serialize(v.serialize()) for k, v in sorted(hd_key_paths.items()) ])
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 serialize(self, check_validity: bool = True) -> bytes: if check_validity: self.assert_valid() out = self.value.to_bytes(8, byteorder="little", signed=False) out += var_bytes.serialize(self.script_pub_key.script) return out
def serialize(self, check_validity: bool = True) -> bytes: "Return the serialization of the Witness." if check_validity: self.assert_valid() out = var_int.serialize(len(self.stack)) return out + b"".join([var_bytes.serialize(w) for w in self.stack])
def test_nulldata() -> None: OP_RETURN = b"\x6a" # pylint: disable=invalid-name # self-consistency string = "time-stamped data" payload = string.encode() script_pub_key = serialize(["OP_RETURN", payload]) assert script_pub_key == ScriptPubKey.nulldata(string).script # back from the script_pub_key to the payload assert ("nulldata", payload) == type_and_payload(script_pub_key) # data -> payload in this case is invertible (no hash functions) assert payload.decode("ascii") == string assert address(script_pub_key) == "" # documented test cases: https://learnmeabitcoin.com/guide/nulldata string = "hello world" payload = string.encode() assert payload.hex() == "68656c6c6f20776f726c64" # pylint: disable=no-member script_pub_key = OP_RETURN + var_bytes.serialize(payload) assert script_pub_key == ScriptPubKey.nulldata(string).script assert ("nulldata", payload) == type_and_payload(script_pub_key) # documented test cases: https://learnmeabitcoin.com/guide/nulldata string = "charley loves heidi" payload = string.encode() assert (payload.hex() # pylint: disable=no-member == "636861726c6579206c6f766573206865696469") script_pub_key = OP_RETURN + var_bytes.serialize(payload) assert script_pub_key == ScriptPubKey.nulldata(string).script assert ("nulldata", payload) == type_and_payload(script_pub_key) # documented test cases: https://learnmeabitcoin.com/guide/nulldata string = "家族も友達もみんなが笑顔の毎日がほしい" payload = string.encode() assert ( payload.hex() # pylint: disable=no-member == "e5aeb6e6978fe38282e58f8be98194e38282e381bfe38293e381aae3818ce7ac91e9a194e381aee6af8ee697a5e3818ce381bbe38197e38184" ) script_pub_key = OP_RETURN + var_bytes.serialize(payload) assert script_pub_key == ScriptPubKey.nulldata(string).script assert ("nulldata", payload) == type_and_payload(script_pub_key)
def serialize(self, check_validity: bool = True) -> bytes: if check_validity: self.assert_valid() out = self.prev_out.serialize() out += var_bytes.serialize(self.script_sig) out += self.sequence.to_bytes(4, byteorder="little", signed=False) return out
def serialize(self, check_validity: bool = True) -> bytes: "Serialize an ECDSA signature to strict ASN.1 DER representation" if check_validity: self.assert_valid() out = _serialize_scalar(self.r) out += _serialize_scalar(self.s) return _DER_SIG_MARKER + var_bytes.serialize(out)
def from_tx(prevouts: List[TxOut], tx: Tx, vin_i: int, hash_type: int) -> bytes: script = prevouts[vin_i].script_pub_key.script if is_p2tr(script): annex = b"" witness = tx.vin[vin_i].script_witness if len(witness.stack) >= 2 and witness.stack[-1][0] == 0x50: annex = witness.stack[-1] witness.stack = witness.stack[:-1] if len(witness.stack) == 0: raise BTClibValueError("Empty stack") ext = b"" if len(witness.stack) > 1: leaf_version = witness.stack[-1][0] & 0xFE preimage = leaf_version.to_bytes(1, "big") preimage += var_bytes.serialize(witness.stack[-2]) tapleaf_hash = tagged_hash(b"TapLeaf", preimage) ext = tapleaf_hash + b"\x00\xff\xff\xff\xff" return taproot( tx, vin_i, [x.value for x in prevouts], [x.script_pub_key for x in prevouts], hash_type, int(bool(ext)), annex, ext, ) # handle all p2sh-wrapped scripts if is_p2sh(script): script = tx.vin[vin_i].script_sig if is_p2wpkh(script): script_ = witness_v0_script(script)[0] return segwit_v0(script_, tx, vin_i, hash_type, prevouts[vin_i].value) if is_p2wsh(script): # the real script is contained in the witness script_ = witness_v0_script(tx.vin[vin_i].script_witness.stack[-1])[0] return segwit_v0(script_, tx, vin_i, hash_type, prevouts[vin_i].value) if is_p2tr(script): raise BTClibValueError("Taproot scripts cannot be wrapped in p2sh") script_ = legacy_script(script)[0] return legacy(script_, tx, vin_i, hash_type)
def tree_helper(script_tree) -> Tuple[Any, bytes]: if len(script_tree) == 1: leaf_version, script = script_tree[0] leaf_version = leaf_version & 0xFE preimage = leaf_version.to_bytes(1, "big") preimage += var_bytes.serialize(serialize(script)) h = tagged_hash(b"TapLeaf", preimage) return ([((leaf_version, script), bytes())], h) left, left_h = tree_helper(script_tree[0]) right, right_h = tree_helper(script_tree[1]) info = [(leaf, c + right_h) for leaf, c in left] info += [(leaf, c + left_h) for leaf, c in right] if right_h < left_h: left_h, right_h = right_h, left_h return (info, tagged_hash(b"TapBranch", left_h + right_h))
def segwit_v0(script_: Octets, tx: Tx, vin_i: int, hash_type: int, amount: int) -> bytes: script_ = bytes_from_octets(script_) hash_prev_outs = b"\x00" * 32 if not hash_type & ANYONECANPAY: hash_prev_outs = b"".join([vin.prev_out.serialize() for vin in tx.vin]) hash_prev_outs = hash256(hash_prev_outs) hash_seqs = b"\x00" * 32 if (not (hash_type & ANYONECANPAY) and (hash_type & 0x1F) != SINGLE and (hash_type & 0x1F) != NONE): hash_seqs = b"".join([ vin.sequence.to_bytes(4, byteorder="little", signed=False) for vin in tx.vin ]) hash_seqs = hash256(hash_seqs) hash_outputs = b"\x00" * 32 if hash_type & 0x1F not in (SINGLE, NONE): hash_outputs = b"".join([vout.serialize() for vout in tx.vout]) hash_outputs = hash256(hash_outputs) elif (hash_type & 0x1F) == SINGLE and vin_i < len(tx.vout): hash_outputs = hash256(tx.vout[vin_i].serialize()) preimage = b"".join([ tx.version.to_bytes(4, byteorder="little", signed=False), hash_prev_outs, hash_seqs, tx.vin[vin_i].prev_out.serialize(), var_bytes.serialize(script_), amount.to_bytes(8, byteorder="little", signed=False), # value tx.vin[vin_i].sequence.to_bytes(4, byteorder="little", signed=False), hash_outputs, tx.lock_time.to_bytes(4, byteorder="little", signed=False), hash_type.to_bytes(4, byteorder="little", signed=False), ]) return hash256(preimage)
def _serialize_scalar(scalar: int) -> bytes: # 'highest bit set' padding included here scalar_size = scalar.bit_length() // 8 + 1 scalar_bytes = scalar.to_bytes(scalar_size, byteorder="big", signed=False) return _DER_SCALAR_MARKER + var_bytes.serialize(scalar_bytes)
def serialize_bytes(type_: bytes, value: bytes) -> bytes: "Return the binary representation of the dataclass element." return var_bytes.serialize(type_) + var_bytes.serialize(value)
def taproot( transaction: Tx, input_index: int, amounts: List[int], scriptpubkeys: List[ScriptPubKey], hashtype: int, ext_flag: int, annex: bytes, message_extension: bytes, ) -> bytes: if hashtype not in SIG_HASH_TYPES: raise BTClibValueError(f"Unknown hash type: {hashtype}") if hashtype & 0x03 == SINGLE and input_index >= len(transaction.vout): raise BTClibValueError("Sighash single wihout a corresponding output") preimage = b"\x00" preimage += hashtype.to_bytes(1, "little") preimage += transaction.nVersion.to_bytes(4, "little") preimage += transaction.nLockTime.to_bytes(4, "little") if hashtype & 0x80 != ANYONECANPAY: sha_prevouts = b"" sha_amounts = b"" sha_scriptpubkeys = b"" sha_sequences = b"" for i, vin in enumerate(transaction.vin): sha_prevouts += vin.prev_out.serialize() sha_amounts += amounts[i].to_bytes(8, "little") sha_scriptpubkeys += var_bytes.serialize(scriptpubkeys[i].script) sha_sequences += vin.nSequence.to_bytes(4, "little") preimage += sha256(sha_prevouts) preimage += sha256(sha_amounts) preimage += sha256(sha_scriptpubkeys) preimage += sha256(sha_sequences) if hashtype & 0x03 not in [NONE, SINGLE]: sha_outputs = b"" for vout in transaction.vout: sha_outputs += vout.serialize() preimage += sha256(sha_outputs) annex_present = int(bool(annex)) preimage += (2 * ext_flag + annex_present).to_bytes(1, "little") if hashtype & 0x80 == ANYONECANPAY: preimage += transaction.vin[input_index].prev_out.serialize() preimage += amounts[input_index].to_bytes(8, "little") preimage += var_bytes.serialize(scriptpubkeys[input_index].script) preimage += transaction.vin[input_index].nSequence.to_bytes( 4, "little") else: preimage += input_index.to_bytes(4, "little") if annex_present: sha_annex = var_bytes.serialize(annex) preimage += sha256(sha_annex) if hashtype & 0x03 == SINGLE: preimage += sha256(transaction.vout[input_index].serialize()) preimage += message_extension sig_hash = tagged_hash(b"TapSighash", preimage) return sig_hash