use crate::{
opcodes::codes::{OpBlake2b, OpCheckSig, OpCheckSigECDSA, OpData32, OpData33, OpEqual},
script_builder::{ScriptBuilder, ScriptBuilderResult},
script_class::ScriptClass,
};
use blake2b_simd::Params;
use kaspa_addresses::{Address, Prefix, Version};
use kaspa_consensus_core::tx::{ScriptPublicKey, ScriptVec};
use kaspa_txscript_errors::TxScriptError;
use smallvec::SmallVec;
use std::iter::once;
mod multisig;
pub use multisig::{multisig_redeem_script, multisig_redeem_script_ecdsa, Error as MultisigCreateError};
fn pay_to_pub_key(address_payload: &[u8]) -> ScriptVec {
assert_eq!(address_payload.len(), 32);
SmallVec::from_iter(once(OpData32).chain(address_payload.iter().copied()).chain(once(OpCheckSig)))
}
fn pay_to_pub_key_ecdsa(address_payload: &[u8]) -> ScriptVec {
assert_eq!(address_payload.len(), 33);
SmallVec::from_iter(once(OpData33).chain(address_payload.iter().copied()).chain(once(OpCheckSigECDSA)))
}
fn pay_to_script_hash(script_hash: &[u8]) -> ScriptVec {
assert_eq!(script_hash.len(), 32);
SmallVec::from_iter([OpBlake2b, OpData32].iter().copied().chain(script_hash.iter().copied()).chain(once(OpEqual)))
}
pub fn pay_to_address_script(address: &Address) -> ScriptPublicKey {
let script = match address.version {
Version::PubKey => pay_to_pub_key(address.payload.as_slice()),
Version::PubKeyECDSA => pay_to_pub_key_ecdsa(address.payload.as_slice()),
Version::ScriptHash => pay_to_script_hash(address.payload.as_slice()),
};
ScriptPublicKey::new(ScriptClass::from(address.version).version(), script)
}
pub fn pay_to_script_hash_script(redeem_script: &[u8]) -> ScriptPublicKey {
let redeem_script_hash = Params::new().hash_length(32).to_state().update(redeem_script).finalize();
let script = pay_to_script_hash(redeem_script_hash.as_bytes());
ScriptPublicKey::new(ScriptClass::ScriptHash.version(), script)
}
pub fn pay_to_script_hash_signature_script(redeem_script: Vec<u8>, signature: Vec<u8>) -> ScriptBuilderResult<Vec<u8>> {
let redeem_script_as_data = ScriptBuilder::new().add_data(&redeem_script)?.drain();
Ok(Vec::from_iter(signature.iter().copied().chain(redeem_script_as_data.iter().copied())))
}
pub fn extract_script_pub_key_address(script_public_key: &ScriptPublicKey, prefix: Prefix) -> Result<Address, TxScriptError> {
let class = ScriptClass::from_script(script_public_key);
if script_public_key.version() > class.version() {
return Err(TxScriptError::PubKeyFormat);
}
let script = script_public_key.script();
match class {
ScriptClass::NonStandard => Err(TxScriptError::PubKeyFormat),
ScriptClass::PubKey => Ok(Address::new(prefix, Version::PubKey, &script[1..33])),
ScriptClass::PubKeyECDSA => Ok(Address::new(prefix, Version::PubKeyECDSA, &script[1..34])),
ScriptClass::ScriptHash => Ok(Address::new(prefix, Version::ScriptHash, &script[2..34])),
}
}
pub mod test_helpers {
use super::*;
use crate::{opcodes::codes::OpTrue, MAX_TX_IN_SEQUENCE_NUM};
use kaspa_consensus_core::{
constants::TX_VERSION,
subnets::SUBNETWORK_ID_NATIVE,
tx::{Transaction, TransactionInput, TransactionOutpoint, TransactionOutput},
};
pub fn op_true_script() -> (ScriptPublicKey, Vec<u8>) {
let redeem_script = vec![OpTrue];
let script_public_key = pay_to_script_hash_script(&redeem_script);
(script_public_key, redeem_script)
}
pub fn create_transaction(tx_to_spend: &Transaction, fee: u64) -> Transaction {
let (script_public_key, redeem_script) = op_true_script();
let signature_script = pay_to_script_hash_signature_script(redeem_script, vec![]).expect("the script is canonical");
let previous_outpoint = TransactionOutpoint::new(tx_to_spend.id(), 0);
let input = TransactionInput::new(previous_outpoint, signature_script, MAX_TX_IN_SEQUENCE_NUM, 1);
let output = TransactionOutput::new(tx_to_spend.outputs[0].value - fee, script_public_key);
Transaction::new(TX_VERSION, vec![input], vec![output], 0, SUBNETWORK_ID_NATIVE, 0, vec![])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_address_and_encode_script() {
struct Test {
name: &'static str,
script_pub_key: ScriptPublicKey,
prefix: Prefix,
expected_address: Result<Address, TxScriptError>,
}
let tests = vec![
Test {
name: "Mainnet PubKey script and address",
script_pub_key: ScriptPublicKey::new(
ScriptClass::PubKey.version(),
ScriptVec::from_slice(
&hex::decode("207bc04196f1125e4f2676cd09ed14afb77223b1f62177da5488346323eaa91a69ac").unwrap(),
),
),
prefix: Prefix::Mainnet,
expected_address: Ok("kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j".try_into().unwrap()),
},
Test {
name: "Testnet PubKeyECDSA script and address",
script_pub_key: ScriptPublicKey::new(
ScriptClass::PubKeyECDSA.version(),
ScriptVec::from_slice(
&hex::decode("21ba01fc5f4e9d9879599c69a3dafdb835a7255e5f2e934e9322ecd3af190ab0f60eab").unwrap(),
),
),
prefix: Prefix::Testnet,
expected_address: Ok("kaspatest:qxaqrlzlf6wes72en3568khahq66wf27tuhfxn5nytkd8tcep2c0vrse6gdmpks".try_into().unwrap()),
},
Test {
name: "Testnet non standard script",
script_pub_key: ScriptPublicKey::new(
ScriptClass::PubKey.version(),
ScriptVec::from_slice(
&hex::decode("2001fc5f4e9d9879599c69a3dafdb835a7255e5f2e934e9322ecd3af190ab0f60eab").unwrap(),
),
),
prefix: Prefix::Testnet,
expected_address: Err(TxScriptError::PubKeyFormat),
},
Test {
name: "Mainnet script with unknown version",
script_pub_key: ScriptPublicKey::new(
ScriptClass::PubKey.version() + 1,
ScriptVec::from_slice(
&hex::decode("207bc04196f1125e4f2676cd09ed14afb77223b1f62177da5488346323eaa91a69ac").unwrap(),
),
),
prefix: Prefix::Mainnet,
expected_address: Err(TxScriptError::PubKeyFormat),
},
];
for test in tests {
let extracted = extract_script_pub_key_address(&test.script_pub_key, test.prefix);
assert_eq!(extracted, test.expected_address, "extract address test failed for '{}'", test.name);
if let Ok(ref address) = extracted {
let encoded = pay_to_address_script(address);
assert_eq!(encoded, test.script_pub_key, "encode public key script test failed for '{}'", test.name);
}
}
}
}