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