Skip to content

Commit

Permalink
fix taproot composition; fix segwit detection in gettxtinfo; add test
Browse files Browse the repository at this point in the history
  • Loading branch information
Ouziel committed Mar 3, 2025
1 parent 047ab64 commit 3f613eb
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 150 deletions.
3 changes: 2 additions & 1 deletion counterparty-core/counterpartycore/lib/api/composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ def is_address_script(address, script_pub_key):
]
script_address = f"{asm[0]}_{'_'.join(addresses)}_{asm[-2]}"
else:
script_address = script.script_to_address2(script_pub_key)
script_address = script.script_to_address(script_pub_key)
print(address, script_address)
return address == script_address


Expand Down
2 changes: 1 addition & 1 deletion counterparty-core/counterpartycore/lib/parser/gettxinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def get_transaction_sources(decoded_tx):
elif asm[0] == opcodes.OP_HASH160 and asm[-1] == opcodes.OP_EQUAL and len(asm) == 3: # noqa: F405
new_source, new_data = decode_scripthash(asm)
assert not new_data and new_source
elif protocol.enabled("segwit_support") and asm[0] == b"":
elif protocol.enabled("segwit_support") and asm[0] in [b"", b"\x01"]:
# Segwit output
new_source = script.script_to_address(script_pubkey)
new_data = None
Expand Down
14 changes: 1 addition & 13 deletions counterparty-core/counterpartycore/lib/utils/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,6 @@ def script_to_address(scriptpubkey):
script = (
bytes(scriptpubkey, "utf-8") if isinstance(scriptpubkey, str) else bytes(scriptpubkey)
) # noqa: E721
return utils.script_to_address3(script, config.NETWORK_NAME)
return utils.script_to_address(script, config.NETWORK_NAME)
except BaseException as e:
raise exceptions.DecodeError("scriptpubkey decoding error") from e


def script_to_address2(scriptpubkey):
if isinstance(scriptpubkey, str):
scriptpubkey = binascii.unhexlify(scriptpubkey)
try:
script = (
bytes(scriptpubkey, "utf-8") if isinstance(scriptpubkey, str) else bytes(scriptpubkey)
) # noqa: E721
return utils.script_to_address2(script, config.NETWORK_NAME)
except BaseException as e: # noqa: F841
raise exceptions.DecodeError("scriptpubkey decoding error") from e
2 changes: 1 addition & 1 deletion counterparty-core/counterpartycore/test/mocks/bitcoind.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def save_address_and_value(self, decoded_tx):
]
address = f"{signatures_required}_{'_'.join(addresses)}_{len(addresses)}"
else:
address = script.script_to_address2(decoded_tx["vout"][-1]["script_pub_key"])
address = script.script_to_address(decoded_tx["vout"][-1]["script_pub_key"])
value = decoded_tx["vout"][-1]["value"]
utxo = f"{decoded_tx['tx_id']}:0"
self.address_and_value_by_utxo[utxo] = (address, value)
Expand Down
50 changes: 50 additions & 0 deletions counterparty-core/counterpartycore/test/units/api/composer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2047,3 +2047,53 @@ def test_compose_attach(ledger_db, defaults):
"inputs_set": "ae241be7be83ebb14902757ad94854f787d9730fc553d6f695346c9375c0d8c1:0:546:76a9144838d8b3588c4c7ba7c1d06f866e9b3739c6303788ac",
},
)


def test_compose_taproot(ledger_db, defaults):
script_pubkey = composer.address_to_script_pub_key(defaults["p2tr_addresses"][0])
script_pubkey = script_pubkey.to_hex()

params = {
"source": defaults["p2tr_addresses"][0],
"asset": "XCP",
"quantity": 10,
"destination": defaults["addresses"][1],
}
construct_params = {
"verbose": True,
"inputs_set": f"ae241be7be83ebb14902757ad94854f787d9730fc553d6f695346c9375c0d8c1:0:1052:{script_pubkey}",
"disable_utxo_locks": True,
"validate": False,
}

result = composer.compose_transaction(
ledger_db,
"send",
params,
construct_params,
)

assert result == {
"rawtransaction": "0200000001c1d8c075936c3495f6d653c50f73d987f75448d97a750249b1eb83bee71b24ae0000000000ffffffff020000000000000000306a2e3d5b4af14ef23843673dd7ada4dc727a020865759e9fba5ae415be70194634d5bc65170687d48129041cf454dc3cca020000000000002251208790903eefbbb8ac03e5e884f76127186e3d18d9c93331f10dd112ad4426415600000000",
"btc_in": 1052,
"btc_out": 0,
"btc_change": 714,
"btc_fee": 338,
"data": b"CNTRPRTY\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\no\x8dj\xe8\xa3\xb3\x81f1\x18\xb4\xe1\xef\xf4\xcf\xc7\xd0\x95M\xd6\xec",
"lock_scripts": ["51208790903eefbbb8ac03e5e884f76127186e3d18d9c93331f10dd112ad44264156"],
"inputs_values": [1052],
"signed_tx_estimated_size": {"vsize": 169, "adjusted_vsize": 169, "sigops_count": 0},
"psbt": "0200000001c1d8c075936c3495f6d653c50f73d987f75448d97a750249b1eb83bee71b24ae0000000000ffffffff020000000000000000306a2e3d5b4af14ef23843673dd7ada4dc727a020865759e9fba5ae415be70194634d5bc65170687d48129041cf454dc3cca020000000000002251208790903eefbbb8ac03e5e884f76127186e3d18d9c93331f10dd112ad4426415600000000",
"params": {
"source": "bcrt1ps7gfq0h0hwu2cql9azz0wcf8rphr6xxeeyenrugd6yf263pxg9tqzsj5ec",
"asset": "XCP",
"quantity": 10,
"destination": "mtQheFaSfWELRB2MyMBaiWjdDm6ux9Ezns",
"memo": None,
"memo_is_hex": False,
"use_enhanced_send": None,
"skip_validation": True,
"no_dispense": False,
},
"name": "send",
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def test_decode_p2w():
)
# correct address
assert (
utils.script_to_address3(script_pubkey, "mainnet")
utils.script_to_address(script_pubkey, "mainnet")
== "bc1pp7w6kxnj7lzgm29pmuhezwl0vjdlcrthqukll5gn9xuqfq5n673smy4m63"
)
# incorrect address
Expand All @@ -176,11 +176,6 @@ def test_decode_p2w():
)
# correct address
assert (
utils.script_to_address3(script_pubkey, "mainnet")
utils.script_to_address(script_pubkey, "mainnet")
== "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38"
)
# incorrect address
assert (
utils.script_to_address2(script_pubkey, "mainnet")
== "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5wgm2h7"
)
18 changes: 14 additions & 4 deletions counterparty-rs/src/indexer/bitcoin_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::iter::repeat;
use std::thread::JoinHandle;

use crate::b58::b58_encode;
use crate::utils::script_to_address3;
use crate::utils::script_to_address;
use bitcoin::{
consensus::serialize,
hashes::{hex::prelude::*, ripemd160, sha256, sha256d::Hash as Sha256dHash, Hash},
Expand Down Expand Up @@ -91,8 +91,18 @@ fn arc4_decrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
}

fn is_valid_segwit_script(script: &Script) -> bool {
if let Some(Ok(PushBytes(pb))) = script.instructions().next() {
return pb.is_empty();
if let Some(instruction) = script.instructions().next() {
match instruction {
Ok(bitcoin::blockdata::script::Instruction::PushBytes(pb)) => {
return pb.is_empty();
},
Ok(inst) => {
return format!("{:?}", inst).contains("OP_PUSHNUM_1");
},
Err(_) => {
return false;
}
}
}
false
}
Expand Down Expand Up @@ -353,7 +363,7 @@ fn parse_vout(
txid, vi
)));
} else if config.segwit_supported(height) && is_valid_segwit_script(&vout.script_pubkey) {
let destination = script_to_address3(
let destination = script_to_address(
vout.script_pubkey.as_bytes().to_vec(),
config.network.to_string().as_str(),
)
Expand Down
123 changes: 0 additions & 123 deletions counterparty-rs/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,127 +23,6 @@ pub fn script_to_address(script_pubkey: Vec<u8>, network: &str) -> PyResult<Stri
// Convert the script pubkey to a Script object
let script = ScriptBuf::from(script_pubkey);

// Convert the network string to a Network enum value
let network_enum = match network {
"mainnet" => Network::Bitcoin,
"testnet3" => Network::Testnet,
"testnet4" => Network::Testnet4,
"signet" => Network::Signet,
"regtest" => Network::Regtest,
_ => {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Invalid network value",
))
}
};
if script.is_witness_program() {
// This block below is necessary to reproduce a prior truncation bug in the python codebase.
let version = match WitnessVersion::try_from(opcodes::Opcode::from(script.as_bytes()[0])) {
Ok(vers) => vers,
Err(_) => {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Invalid version value",
))
}
};

let n = 22;
if script.len() < n {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Script length is less than 22",
));
}
let program = WitnessProgram::new(version, &script.as_bytes()[2..n]).map_err(|e| {
return PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Could not create witness program from script: {}",
e
));
})?;
let address = Address::from_witness_program(program, network_enum);
Ok(address.to_string())
} else {
/*
* the code below is correct, but not sure about the invocation path
* and untested bug compatibility
*/
let _address = match Address::from_script(&script, network_enum) {
Ok(addr) => addr,
Err(_) => {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Failed to derive address",
))
}
};
panic!("we thought this shouldn't happen!");
//Ok(address.to_string())
}
}

#[pyfunction]
pub fn script_to_address2(script_pubkey: Vec<u8>, network: &str) -> PyResult<String> {
// Convert the script pubkey to a Script object
let script = ScriptBuf::from(script_pubkey);

// Convert the network string to a Network enum value
let network_enum = match network {
"mainnet" => Network::Bitcoin,
"testnet3" => Network::Testnet,
"testnet4" => Network::Testnet4,
"signet" => Network::Signet,
"regtest" => Network::Regtest,
_ => {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Invalid network value",
))
}
};
if script.is_witness_program() {
// This block below is necessary to reproduce a prior truncation bug in the python codebase.
let version = match WitnessVersion::try_from(opcodes::Opcode::from(script.as_bytes()[0])) {
Ok(vers) => vers,
Err(_) => {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Invalid version value",
))
}
};

let n = 22;
if script.len() < n {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Script length is less than 22",
));
}
let program = WitnessProgram::new(version, &script.as_bytes()[2..n]).map_err(|e| {
return PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Could not create witness program from script: {}",
e
));
})?;
let address = Address::from_witness_program(program, network_enum);
Ok(address.to_string())
} else {
/*
* the code below is correct, but not sure about the invocation path
* and untested bug compatibility
*/
let address = match Address::from_script(&script, network_enum) {
Ok(addr) => addr,
Err(_) => {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Failed to derive address",
))
}
};
Ok(address.to_string())
}
}

#[pyfunction]
pub fn script_to_address3(script_pubkey: Vec<u8>, network: &str) -> PyResult<String> {
// Convert the script pubkey to a Script object
let script = ScriptBuf::from(script_pubkey);

// Convert the network string to a Network enum value
let network_enum = match network {
"mainnet" => Network::Bitcoin,
Expand Down Expand Up @@ -401,8 +280,6 @@ pub fn register_utils_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()
m.add_function(wrap_pyfunction!(inverse_hash, &m)?)?;
m.add_function(wrap_pyfunction!(script_to_asm, &m)?)?;
m.add_function(wrap_pyfunction!(script_to_address, &m)?)?;
m.add_function(wrap_pyfunction!(script_to_address2, &m)?)?;
m.add_function(wrap_pyfunction!(script_to_address3, &m)?)?;
parent_module.add_submodule(&m)?;
Ok(())
}

0 comments on commit 3f613eb

Please sign in to comment.