1use crate::{
2 EngineFlags,
3 opcodes::codes::{OpBlake2b, OpCheckSig, OpCheckSigECDSA, OpData32, OpData33, OpEqual},
4 script_builder::{ScriptBuilder, ScriptBuilderResult},
5 script_class::ScriptClass,
6};
7use blake2b_simd::Params;
8use kaspa_addresses::{Address, Prefix, Version};
9use kaspa_consensus_core::tx::{ScriptPublicKey, ScriptVec};
10use kaspa_txscript_errors::TxScriptError;
11use smallvec::SmallVec;
12use std::iter::once;
13
14mod multisig;
15
16pub use multisig::{Error as MultisigCreateError, multisig_redeem_script, multisig_redeem_script_ecdsa};
17
18fn pay_to_pub_key(address_payload: &[u8]) -> ScriptVec {
20 assert_eq!(address_payload.len(), 32);
22 SmallVec::from_iter(once(OpData32).chain(address_payload.iter().copied()).chain(once(OpCheckSig)))
23}
24
25fn pay_to_pub_key_ecdsa(address_payload: &[u8]) -> ScriptVec {
27 assert_eq!(address_payload.len(), 33);
29 SmallVec::from_iter(once(OpData33).chain(address_payload.iter().copied()).chain(once(OpCheckSigECDSA)))
30}
31
32fn pay_to_script_hash(script_hash: &[u8]) -> ScriptVec {
35 assert_eq!(script_hash.len(), 32);
37 SmallVec::from_iter([OpBlake2b, OpData32].iter().copied().chain(script_hash.iter().copied()).chain(once(OpEqual)))
38}
39
40pub fn pay_to_address_script(address: &Address) -> ScriptPublicKey {
42 let script = match address.version {
43 Version::PubKey => pay_to_pub_key(address.payload.as_slice()),
44 Version::PubKeyECDSA => pay_to_pub_key_ecdsa(address.payload.as_slice()),
45 Version::ScriptHash => pay_to_script_hash(address.payload.as_slice()),
46 };
47 ScriptPublicKey::new(ScriptClass::from(address.version).version(), script)
48}
49
50pub fn pay_to_script_hash_script(redeem_script: &[u8]) -> ScriptPublicKey {
52 let redeem_script_hash = Params::new().hash_length(32).to_state().update(redeem_script).finalize();
53 let script = pay_to_script_hash(redeem_script_hash.as_bytes());
54 ScriptPublicKey::new(ScriptClass::ScriptHash.version(), script)
55}
56
57pub fn pay_to_script_hash_signature_script(redeem_script: Vec<u8>, signature: Vec<u8>) -> ScriptBuilderResult<Vec<u8>> {
59 pay_to_script_hash_signature_script_with_flags(redeem_script, signature, EngineFlags::default())
60}
61
62pub fn pay_to_script_hash_signature_script_with_flags(
64 redeem_script: Vec<u8>,
65 signature: Vec<u8>,
66 flags: EngineFlags,
67) -> ScriptBuilderResult<Vec<u8>> {
68 let redeem_script_as_data = ScriptBuilder::with_flags(flags).add_data(&redeem_script)?.drain();
69 Ok(Vec::from_iter(signature.iter().copied().chain(redeem_script_as_data.iter().copied())))
70}
71
72pub fn extract_script_pub_key_address(script_public_key: &ScriptPublicKey, prefix: Prefix) -> Result<Address, TxScriptError> {
83 let class = ScriptClass::from_script(script_public_key);
84 if script_public_key.version() > class.version() {
85 return Err(TxScriptError::PubKeyFormat);
86 }
87 let script = script_public_key.script();
88 match class {
89 ScriptClass::NonStandard => Err(TxScriptError::PubKeyFormat),
90 ScriptClass::PubKey => Ok(Address::new(prefix, Version::PubKey, &script[1..33])),
91 ScriptClass::PubKeyECDSA => Ok(Address::new(prefix, Version::PubKeyECDSA, &script[1..34])),
92 ScriptClass::ScriptHash => Ok(Address::new(prefix, Version::ScriptHash, &script[2..34])),
93 }
94}
95
96pub mod test_helpers {
97 use super::*;
98 use crate::{MAX_TX_IN_SEQUENCE_NUM, opcodes::codes::OpTrue};
99 use kaspa_consensus_core::{
100 constants::TX_VERSION,
101 subnets::SUBNETWORK_ID_NATIVE,
102 tx::{Transaction, TransactionInput, TransactionOutpoint, TransactionOutput},
103 };
104
105 pub fn op_true_script() -> (ScriptPublicKey, Vec<u8>) {
108 let redeem_script = vec![OpTrue];
109 let script_public_key = pay_to_script_hash_script(&redeem_script);
110 (script_public_key, redeem_script)
111 }
112
113 pub fn create_transaction(tx_to_spend: &Transaction, fee: u64) -> Transaction {
117 let (script_public_key, redeem_script) = op_true_script();
118 let signature_script = pay_to_script_hash_signature_script(redeem_script, vec![]).expect("the script is canonical");
119 let previous_outpoint = TransactionOutpoint::new(tx_to_spend.id(), 0);
120 let input = TransactionInput::new(previous_outpoint, signature_script, MAX_TX_IN_SEQUENCE_NUM, 1);
121 let output = TransactionOutput::new(tx_to_spend.outputs[0].value - fee, script_public_key);
122 Transaction::new(TX_VERSION, vec![input], vec![output], 0, SUBNETWORK_ID_NATIVE, 0, vec![])
123 }
124
125 pub fn create_transaction_with_change<'a>(
133 txs_to_spend: impl Iterator<Item = &'a Transaction>,
134 output_indexes: Vec<usize>,
135 change: Option<u64>,
136 fee: u64,
137 ) -> Transaction {
138 let (script_public_key, redeem_script) = op_true_script();
139 let signature_script = pay_to_script_hash_signature_script(redeem_script, vec![]).expect("the script is canonical");
140 let mut inputs_value: u64 = 0;
141 let mut inputs = vec![];
142 for tx_to_spend in txs_to_spend {
143 for i in output_indexes.iter().copied() {
144 if i < tx_to_spend.outputs.len() {
145 let previous_outpoint = TransactionOutpoint::new(tx_to_spend.id(), i as u32);
146 inputs.push(TransactionInput::new(previous_outpoint, signature_script.clone(), MAX_TX_IN_SEQUENCE_NUM, 1));
147 inputs_value += tx_to_spend.outputs[i].value;
148 }
149 }
150 }
151 let outputs = match change {
152 Some(change) => vec![
153 TransactionOutput::new(inputs_value - fee - change, script_public_key.clone()),
154 TransactionOutput::new(change, script_public_key),
155 ],
156 None => vec![TransactionOutput::new(inputs_value - fee, script_public_key.clone())],
157 };
158 Transaction::new(TX_VERSION, inputs, outputs, 0, SUBNETWORK_ID_NATIVE, 0, vec![])
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use kaspa_utils::hex::FromHex;
166
167 #[test]
168 fn test_extract_address_and_encode_script() {
169 struct Test {
170 name: &'static str,
171 script_pub_key: ScriptPublicKey,
172 prefix: Prefix,
173 expected_address: Result<Address, TxScriptError>,
174 }
175
176 let tests = vec![
178 Test {
179 name: "Mainnet PubKey script and address",
180 script_pub_key: ScriptPublicKey::new(
181 ScriptClass::PubKey.version(),
182 ScriptVec::from_hex("207bc04196f1125e4f2676cd09ed14afb77223b1f62177da5488346323eaa91a69ac").unwrap(),
183 ),
184 prefix: Prefix::Mainnet,
185 expected_address: Ok("kaspa:qpauqsvk7yf9unexwmxsnmg547mhyga37csh0kj53q6xxgl24ydxjsgzthw5j".try_into().unwrap()),
186 },
187 Test {
188 name: "Testnet PubKeyECDSA script and address",
189 script_pub_key: ScriptPublicKey::new(
190 ScriptClass::PubKeyECDSA.version(),
191 ScriptVec::from_hex("21ba01fc5f4e9d9879599c69a3dafdb835a7255e5f2e934e9322ecd3af190ab0f60eab").unwrap(),
192 ),
193 prefix: Prefix::Testnet,
194 expected_address: Ok("kaspatest:qxaqrlzlf6wes72en3568khahq66wf27tuhfxn5nytkd8tcep2c0vrse6gdmpks".try_into().unwrap()),
195 },
196 Test {
197 name: "Testnet non standard script",
198 script_pub_key: ScriptPublicKey::new(
199 ScriptClass::PubKey.version(),
200 ScriptVec::from_hex("2001fc5f4e9d9879599c69a3dafdb835a7255e5f2e934e9322ecd3af190ab0f60eab").unwrap(),
201 ),
202 prefix: Prefix::Testnet,
203 expected_address: Err(TxScriptError::PubKeyFormat),
204 },
205 Test {
206 name: "Mainnet script with unknown version",
207 script_pub_key: ScriptPublicKey::new(
208 ScriptClass::PubKey.version() + 1,
209 ScriptVec::from_hex("207bc04196f1125e4f2676cd09ed14afb77223b1f62177da5488346323eaa91a69ac").unwrap(),
210 ),
211 prefix: Prefix::Mainnet,
212 expected_address: Err(TxScriptError::PubKeyFormat),
213 },
214 ];
215 for test in tests {
218 let extracted = extract_script_pub_key_address(&test.script_pub_key, test.prefix);
219 assert_eq!(extracted, test.expected_address, "extract address test failed for '{}'", test.name);
220 if let Ok(ref address) = extracted {
221 let encoded = pay_to_address_script(address);
222 assert_eq!(encoded, test.script_pub_key, "encode public key script test failed for '{}'", test.name);
223 }
224 }
225 }
226}