def assert_signable(self) -> None: self.assert_valid() for i, tx_in in enumerate(self.tx.vin): non_witness_utxo = self.inputs[i].non_witness_utxo witness_utxo = self.inputs[i].witness_utxo redeem_script = self.inputs[i].redeem_script if witness_utxo: script_pub_key = witness_utxo.script_pub_key script_type, payload = type_and_payload(script_pub_key.script) if script_type == "p2sh": script_type, _ = type_and_payload(redeem_script) if script_type not in ("p2wpkh", "p2wsh"): raise BTClibValueError( "script type not it ('p2wpkh', 'p2wsh')") elif non_witness_utxo: script_pub_key = non_witness_utxo.vout[ tx_in.prev_out.vout].script_pub_key _, payload = type_and_payload(script_pub_key.script) else: err_msg = "missing script_pub_key" raise BTClibValueError(err_msg) if redeem_script and payload != hash160(redeem_script): raise BTClibValueError("invalid redeem script hash160") if self.inputs[i].witness_script: if redeem_script: _, payload = type_and_payload(redeem_script) if payload != sha256(self.inputs[i].witness_script): raise BTClibValueError("invalid witness script sha256")
def test_p2wpkh() -> None: # self-consistency pub_key = "02 cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf" payload = hash160(pub_key) script_pub_key = serialize(["OP_0", payload]) assert_p2wpkh(script_pub_key) assert script_pub_key == ScriptPubKey.p2wpkh(pub_key).script assert ("p2wpkh", payload) == type_and_payload(script_pub_key) # bech32 address network = "mainnet" addr = b32.p2wpkh(pub_key, network) assert addr == address(script_pub_key, network) assert addr == b32.address_from_witness(0, payload, network) # back from the address to the script_pub_key assert script_pub_key == ScriptPubKey.from_address(addr).script assert network == ScriptPubKey.from_address(addr).network # p2sh-wrapped base58 address addr = b58.p2wpkh_p2sh(pub_key, network) assert addr == "3BJxz2r8zY7LxJfdGjUpjjHNh6YEiitvf2" err_msg = "invalid witness version: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2wpkh(b"\x33" + script_pub_key[1:]) err_msg = "invalid pub_key hash length marker: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2wpkh(script_pub_key[:1] + b"\x00" + script_pub_key[2:])
def test_p2wsh() -> None: # self-consistency pub_key = "02 cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf" redeem_script = ScriptPubKey.p2pkh(pub_key).script payload = sha256(redeem_script) script_pub_key = serialize(["OP_0", payload]) assert_p2wsh(script_pub_key) assert script_pub_key == ScriptPubKey.p2wsh(redeem_script).script assert ("p2wsh", payload) == type_and_payload(script_pub_key) # bech32 address network = "mainnet" addr = b32.p2wsh(redeem_script, network) assert addr == address(script_pub_key, network) assert addr == b32.address_from_witness(0, payload, network) # back from the address to the script_pub_key assert script_pub_key == ScriptPubKey.from_address(addr).script assert network == ScriptPubKey.from_address(addr).network # p2sh-wrapped base58 address addr = b58.p2wsh_p2sh(redeem_script, network) assert addr == "39GUePMSQ4mADpihVLd8cFQ2tih9Fy4qkz" err_msg = "invalid witness version: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2wsh(b"\x33" + script_pub_key[1:]) err_msg = "invalid redeem script hash length marker: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2wsh(script_pub_key[:1] + b"\x00" + script_pub_key[2:])
def test_p2ms_2() -> None: m = 1 # all uncompressed pub_key0 = "04 cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf f7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4" pub_key1 = "04 61cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d765 19aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af" pub_key2 = "04 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8" uncompressed_pub_keys: List[Key] = [pub_key0, pub_key1, pub_key2] # mixed compressed / uncompressed public keys pub_key0 = "04 cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf f7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4" pub_key1 = "03 61cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d765" pub_key2 = "02 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" mixed_pub_keys: List[Key] = [pub_key0, pub_key1, pub_key2] for pub_keys in (uncompressed_pub_keys, mixed_pub_keys): for lexi_sort in (True, False): script_pub_key = ScriptPubKey.p2ms(m, pub_keys, lexi_sort=lexi_sort).script assert is_p2ms(script_pub_key) assert address(script_pub_key) == "" script_type, payload = type_and_payload(script_pub_key) assert script_type == "p2ms" assert payload == script_pub_key[:-1]
def test_valid_script_path() -> None: fname = "tapscript_test_vector.json" filename = path.join(path.dirname(__file__), "_data", fname) with open(filename, "r", encoding="ascii") as file_: data = json.load(file_) for x in data: prevouts = [TxOut.parse(prevout) for prevout in x["prevouts"]] index = x["index"] if not is_p2tr(prevouts[index].script_pub_key.script): continue script_sig = x["success"]["scriptSig"] assert not script_sig witness = Witness(x["success"]["witness"]) if len(witness.stack) >= 2 and witness.stack[-1][0] == 0x50: witness.stack = witness.stack[:-1] # check script paths if len(witness.stack) < 2: continue Q = type_and_payload(prevouts[index].script_pub_key.script)[1] script = witness.stack[-2] control = witness.stack[-1] assert check_output_pubkey(Q, script, control)
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 witness_v0_script(script_pub_key: Octets) -> List[bytes]: script_type, payload = type_and_payload(script_pub_key) if script_type == "p2wpkh": script = serialize( ["OP_DUP", "OP_HASH160", payload, "OP_EQUALVERIFY", "OP_CHECKSIG"]) return [script] script_s: List[bytes] = [] current_script: List[Command] = [] for token in parse(script_pub_key)[::-1]: if token == "OP_CODESEPARATOR": # nosec required for python < 3.8 script_s.append(serialize(current_script[::-1])) current_script.append(token) script_s.append(serialize(current_script[::-1])) return script_s[::-1]
def test_p2tr() -> None: pub_key = "cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf" payload = output_pubkey(pub_key)[0] script_pub_key = serialize(["OP_1", payload]) assert_p2tr(script_pub_key) assert ("p2tr", payload) == type_and_payload(script_pub_key) network = "mainnet" addr = b32.p2tr(pub_key, network=network) assert addr == address(script_pub_key, network) assert script_pub_key == ScriptPubKey.from_address(addr).script assert script_pub_key == ScriptPubKey.p2tr(pub_key).script err_msg = "invalid redeem script hash length marker: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2tr(script_pub_key[:1] + b"\x00" + script_pub_key[2:])
def test_nulldata4() -> None: script_: List[Command] = [ "OP_RETURN", "OP_RETURN", "OP_3", "OP_1", "OP_VERIF", "OP_0", "OP_3", ] # FIXME: serialization is not 0x6A{1 byte data-length}{data 6 bytes)} script_pub_key = serialize(script_) assert len(script_pub_key) == 7 assert parse(script_pub_key) == script_ script_type, _ = type_and_payload(script_pub_key) # FIXME: it should be "nulldata" assert script_type == "unknown"
def test_p2pkh() -> None: # self-consistency pub_key = ( "04 " "cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf" "f7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4") payload = hash160(pub_key) script_pub_key = serialize( ["OP_DUP", "OP_HASH160", payload, "OP_EQUALVERIFY", "OP_CHECKSIG"]) assert_p2pkh(script_pub_key) assert script_pub_key == ScriptPubKey.p2pkh(pub_key).script assert ("p2pkh", payload) == type_and_payload(script_pub_key) # base58 address network = "mainnet" addr = b58.p2pkh(pub_key, network) assert addr == address(script_pub_key, network) assert addr == b58.address_from_h160("p2pkh", payload, network) # back from the address to the script_pub_key assert script_pub_key == ScriptPubKey.from_address(addr).script assert network == ScriptPubKey.from_address(addr).network # documented test case: https://learnmeabitcoin.com/guide/p2pkh payload = bytes.fromhex("12ab8dc588ca9d5787dde7eb29569da63c3a238c") script_pub_key = bytes.fromhex("76a914") + payload + bytes.fromhex("88ac") assert_p2pkh(script_pub_key) addr = "12higDjoCCNXSA95xZMWUdPvXNmkAduhWv" assert addr == address(script_pub_key, network) assert script_pub_key == ScriptPubKey.from_address(addr).script assert network == ScriptPubKey.from_address(addr).network err_msg = "missing final OP_EQUALVERIFY, OP_CHECKSIG" with pytest.raises(BTClibValueError, match=err_msg): assert_p2pkh(script_pub_key[:-2] + b"\x40\x40") err_msg = "missing leading OP_DUP, OP_HASH160" with pytest.raises(BTClibValueError, match=err_msg): assert_p2pkh(b"\x40\x40" + script_pub_key[2:]) err_msg = "invalid pub_key hash length marker: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2pkh(script_pub_key[:2] + b"\x40" + script_pub_key[3:])
def test_p2sh() -> None: # self-consistency pub_key = "02 cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf" redeem_script = ScriptPubKey.p2pkh(pub_key).script payload = hash160(redeem_script) script_pub_key = serialize(["OP_HASH160", payload, "OP_EQUAL"]) assert_p2sh(script_pub_key) assert script_pub_key == ScriptPubKey.p2sh(redeem_script).script assert ("p2sh", payload) == type_and_payload(script_pub_key) # base58 address network = "mainnet" addr = b58.p2sh(redeem_script, network) assert addr == address(script_pub_key, network) assert addr == b58.address_from_h160("p2sh", payload, network) # back from the address to the script_pub_key assert script_pub_key == ScriptPubKey.from_address(addr).script assert network == ScriptPubKey.from_address(addr).network # documented test case: https://learnmeabitcoin.com/guide/p2sh payload = bytes.fromhex("748284390f9e263a4b766a75d0633c50426eb875") script_pub_key = bytes.fromhex("a914") + payload + bytes.fromhex("87") assert_p2sh(script_pub_key) addr = "3CK4fEwbMP7heJarmU4eqA3sMbVJyEnU3V" assert addr == address(script_pub_key, network) assert script_pub_key == ScriptPubKey.from_address(addr).script assert network == ScriptPubKey.from_address(addr).network err_msg = "missing final OP_EQUAL" with pytest.raises(BTClibValueError, match=err_msg): assert_p2sh(script_pub_key[:-1] + b"\x40") err_msg = "missing leading OP_HASH160" with pytest.raises(BTClibValueError, match=err_msg): assert_p2sh(b"\x40" + script_pub_key[1:]) err_msg = "invalid redeem script hash length marker: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2sh(script_pub_key[:1] + b"\x40" + script_pub_key[2:])
def test_invalid_taproot_key_path() -> None: fname = "tapscript_test_vector.json" filename = path.join(path.dirname(__file__), "_data", fname) with open(filename, "r", encoding="ascii") as file_: data = json.load(file_) for x in filter(lambda x: "failure" in x.keys(), data): tx = Tx.parse(x["tx"]) prevouts = [TxOut.parse(prevout) for prevout in x["prevouts"]] index = x["index"] if not is_p2tr(prevouts[index].script_pub_key.script): continue witness = Witness(x["failure"]["witness"]) tx.vin[index].script_witness = witness # check only key paths if (len(witness.stack) == 1 or len(witness.stack) == 2 and witness.stack[-1][0] == 0x50): with pytest.raises( (BTClibRuntimeError, BTClibValueError, AssertionError)): assert not x["failure"]["scriptSig"] sighash_type = 0 # all signature = witness.stack[0][:64] if len(witness.stack[0]) == 65: sighash_type = witness.stack[0][-1] if sighash_type == 0: raise BTClibValueError( "invalid sighash 0 in 65 bytes signature") msg_hash = sig_hash.from_tx(prevouts, tx, index, sighash_type) pub_key = type_and_payload( prevouts[index].script_pub_key.script)[1] ssa.assert_as_valid_(msg_hash, pub_key, signature)
def test_bip67() -> None: "BIP67 test vectors https://en.bitcoin.it/wiki/BIP_0067" data_folder = path.join(path.dirname(__file__), "_data") filename = path.join(data_folder, "bip67_test_vectors.json") with open(filename, "r") as file_: # json.dump(test_vectors, f, indent=4) test_vectors = json.load(file_) m = 2 for i in test_vectors: keys, addr = test_vectors[i] script_pub_key = ScriptPubKey.p2ms(m, keys, lexi_sort=True).script assert is_p2ms(script_pub_key) assert address(script_pub_key) == "" script_type, payload = type_and_payload(script_pub_key) assert script_type == "p2ms" assert payload == script_pub_key[:-1] errmsg = f"Test vector #{i}" assert addr == b58.p2sh(script_pub_key), errmsg
def test_p2pk() -> None: # self-consistency pub_key = "02 cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf" script_pub_key = serialize([pub_key, "OP_CHECKSIG"]) assert_p2pk(script_pub_key) assert script_pub_key == ScriptPubKey.p2pk(pub_key).script assert ("p2pk", bytes.fromhex(pub_key)) == type_and_payload(script_pub_key) assert address(script_pub_key) == "" err_msg = "invalid pub_key length marker: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2pk(b"\x31" + script_pub_key[1:]) # documented test case: https://learnmeabitcoin.com/guide/p2pk pub_key = ( "04" "ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414" "e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84c") script_pub_key = bytes.fromhex("41" + pub_key + "ac") assert_p2pk(script_pub_key) assert script_pub_key == ScriptPubKey.p2pk(pub_key).script err_msg = "missing final OP_CHECKSIG" with pytest.raises(BTClibValueError, match=err_msg): assert_p2pk(script_pub_key[:-1] + b"\x00") err_msg = "invalid pub_key length marker: " with pytest.raises(BTClibValueError, match=err_msg): assert_p2pk(b"\x31" + script_pub_key[1:]) # invalid size: 34 bytes instead of (33, 65) pub_key = "03 ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414 14" err_msg = "not a private or public key: " with pytest.raises(BTClibValueError, match=err_msg): ScriptPubKey.p2pk(pub_key)
def test_valid_taproot_key_path() -> None: fname = "tapscript_test_vector.json" filename = path.join(path.dirname(__file__), "_data", fname) with open(filename, "r", encoding="ascii") as file_: data = json.load(file_) for x in filter(lambda x: "TAPROOT" in x["flags"], data): tx = Tx.parse(x["tx"]) prevouts = [TxOut.parse(prevout) for prevout in x["prevouts"]] index = x["index"] if not is_p2tr(prevouts[index].script_pub_key.script): continue assert not x["success"]["scriptSig"] witness = Witness(x["success"]["witness"]) tx.vin[index].script_witness = witness if (len(witness.stack) == 1 or len(witness.stack) == 2 and witness.stack[-1][0] == 0x50): sighash_type = 0 # all signature = witness.stack[0][:64] if len(witness.stack[0]) == 65: sighash_type = witness.stack[0][-1] assert sighash_type != 0 msg_hash = sig_hash.from_tx(prevouts, tx, index, sighash_type) pub_key = type_and_payload( prevouts[index].script_pub_key.script)[1] ssa.assert_as_valid_(msg_hash, pub_key, signature)
def test_p2ms_1() -> None: # self-consistency pub_key0 = "04 cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf f7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4" pub_key1 = "04 61cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d765 19aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af" # documented test case: https://learnmeabitcoin.com/guide/p2ms script_pub_key = bytes.fromhex( # fmt: off "51" # OP_1 "41" # canonical 65-bytes push + pub_key0 + "41" # noqa E148 # canonical 65-bytes push + pub_key1 + "52" # noqa E148 # OP_2 "ae" # OP_CHECKMULTISIG ) # fmt: on assert is_p2ms(script_pub_key) assert address(script_pub_key) == "" script_type, payload = type_and_payload(script_pub_key) assert script_type == "p2ms" assert payload == script_pub_key[:-1] pub_keys: List[Key] = [pub_key0, pub_key1] assert script_pub_key == ScriptPubKey.p2ms(1, pub_keys, lexi_sort=False).script err_msg = "invalid m in m-of-n: " with pytest.raises(BTClibValueError, match=err_msg): ScriptPubKey.p2ms(4, pub_keys) err_msg = "invalid n in m-of-n: " with pytest.raises(BTClibValueError, match=err_msg): # pylance cannot grok the following line ScriptPubKey.p2ms(4, [pub_key0] * 17) # type: ignore err_msg = "invalid m in m-of-n: " with pytest.raises(BTClibValueError, match=err_msg): ScriptPubKey.p2ms(0, pub_keys) err_msg = "invalid m in m-of-n: " with pytest.raises(BTClibValueError, match=err_msg): ScriptPubKey.p2ms(17, pub_keys) err_msg = "not a private or public key: " with pytest.raises(BTClibValueError, match=err_msg): ScriptPubKey.p2ms(1, [pub_key0 + "00", pub_key1]) script_: List[Command] = [ "OP_1", pub_key0 + "00", pub_key1, "OP_2", "OP_CHECKMULTISIG", ] script_pub_key = serialize(script_) assert not is_p2ms(script_pub_key) err_msg = "invalid key in p2ms" script_pub_key = serialize( ["OP_1", pub_key0, "00", "OP_2", "OP_CHECKMULTISIG"]) assert not is_p2ms(script_pub_key) script_pub_key = serialize( ["OP_1", pub_key0, pub_key1, "OP_2", "OP_CHECKMULTISIG"]) assert is_p2ms(script_pub_key) script_pub_key = serialize( ["OP_2", pub_key0, pub_key1, "OP_2", "OP_CHECKMULTISIG"]) assert is_p2ms(script_pub_key) script_pub_key = serialize( ["OP_0", pub_key0, pub_key1, "OP_2", "OP_CHECKMULTISIG"]) assert not is_p2ms(script_pub_key) script_pub_key = serialize( ["OP_3", pub_key0, pub_key1, "OP_2", "OP_CHECKMULTISIG"]) assert not is_p2ms(script_pub_key) script_pub_key = serialize(["OP_1", "OP_2", "OP_CHECKMULTISIG"]) assert not is_p2ms(script_pub_key) script_pub_key = serialize(["OP_1", pub_key0, "OP_2", "OP_CHECKMULTISIG"]) assert not is_p2ms(script_pub_key) script_pub_key = serialize( ["OP_1", pub_key0, pub_key1, "OP_3", "OP_CHECKMULTISIG"]) assert not is_p2ms(script_pub_key) pub_key2 = "04 79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798 483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8" script_pub_key = serialize( ["OP_1", pub_key0, pub_key1, pub_key2, "OP_3", "OP_CHECKMULTISIG"]) assert_p2ms(script_pub_key) err_msg = "invalid p2ms script_pub_key size" with pytest.raises(BTClibValueError, match=err_msg): assert_p2ms(script_pub_key[:133] + b"\x40" + script_pub_key[134:]) with pytest.raises(BTClibValueError, match=err_msg): assert_p2ms(script_pub_key[:-2] + b"\x00" + script_pub_key[-2:])
def test_unknown() -> None: script_pub_key = serialize(["OP_16", 20 * b"\x00"]) assert address(script_pub_key) == "" assert type_and_payload(script_pub_key) == ("unknown", script_pub_key)
def test_nulldata2() -> None: for length in (0, 1, 16, 17, 74, 75, 76, 77, 78, 79, 80): payload = b"\x00" * length script_pub_key = serialize(["OP_RETURN", payload]) assert ("nulldata", payload) == type_and_payload(script_pub_key)