1use alloc::vec::Vec;
2use bitcoin::consensus::{encode, Decodable, Encodable};
3use bitcoin::psbt::{Error, Input, Output, Psbt};
4use serde_bolt::bitcoin;
5use serde_bolt::io::{self, Read, Write};
6
7#[derive(Debug)]
8pub struct PsbtWrapper {
9 pub inner: Psbt,
10}
11
12impl From<Psbt> for PsbtWrapper {
13 fn from(value: Psbt) -> Self {
14 Self { inner: value }
15 }
16}
17
18impl Encodable for PsbtWrapper {
19 fn consensus_encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
20 let buffer = self.inner.serialize();
21 writer.write(&buffer)
22 }
23}
24
25impl Decodable for PsbtWrapper {
26 fn consensus_decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, encode::Error> {
27 Self::consensus_decode_from_finite_reader(reader)
28 }
29
30 fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(
31 r: &mut R,
32 ) -> Result<Self, encode::Error> {
33 let mut buffer = Vec::new();
34 r.read_to_limit(&mut buffer, u64::MAX)?;
35 let psbt = Psbt::deserialize(&buffer)
36 .map_err(|_| encode::Error::ParseFailed("filed to parse the psbt"))?;
37 Ok(Self { inner: psbt })
38 }
39}
40
41#[derive(Debug)]
61pub struct StreamedPSBT {
62 pub psbt: PsbtWrapper,
64 pub segwit_flags: Vec<bool>,
66}
67
68impl StreamedPSBT {
69 pub fn new(psbt: Psbt) -> Self {
70 let segwit_flags = Vec::new();
71 Self { psbt: PsbtWrapper::from(psbt), segwit_flags }
72 }
73
74 pub fn psbt(&self) -> &Psbt {
75 &self.psbt.inner
76 }
77
78 pub fn consensus_decode_global<R: Read + ?Sized>(r: &mut R) -> Result<Psbt, encode::Error> {
79 let psbt = PsbtWrapper::consensus_decode_from_finite_reader(r)?;
81 Ok(psbt.inner)
82 }
83}
84
85impl Decodable for StreamedPSBT {
86 fn consensus_decode_from_finite_reader<R: Read + ?Sized>(
87 r: &mut R,
88 ) -> Result<Self, encode::Error> {
89 let mut global = Self::consensus_decode_global(r)?;
90 Self::unsigned_tx_checks(&global)
91 .map_err(|_| encode::Error::ParseFailed("txs checks fails"))?;
92
93 let mut segwit_flags: Vec<bool> = Vec::with_capacity(global.unsigned_tx.input.len());
94
95 let inputs: Vec<Input> = {
96 let inputs_len: usize = global.unsigned_tx.input.len();
97
98 let mut inputs: Vec<Input> = Vec::with_capacity(inputs_len);
99
100 for (ind, mut input) in global.inputs.into_iter().enumerate() {
101 if let Some(input_tx) = input.non_witness_utxo.take() {
103 let prevout = global.unsigned_tx.input[ind].previous_output;
104 if input_tx.compute_txid() != prevout.txid {
106 return Err(encode::Error::ParseFailed("missing utxo"));
107 }
108 if input_tx.output.len() <= prevout.vout as usize {
110 return Err(encode::Error::ParseFailed("missing utxo"));
111 }
112 let output = &input_tx.output[prevout.vout as usize];
114 if output.script_pubkey.is_witness_program() {
115 segwit_flags.push(true);
116 } else {
117 segwit_flags.push(false);
118 }
119
120 if let Some(ref txo) = input.witness_utxo {
121 if txo != output {
123 return Err(encode::Error::ParseFailed("missing utxo"));
124 }
125 } else {
126 input.witness_utxo = Some(output.clone());
127 }
128 } else {
129 segwit_flags.push(false);
130 }
131
132 inputs.push(input);
133 }
134
135 inputs
136 };
137
138 let outputs: Vec<Output> = {
139 let outputs_len: usize = global.unsigned_tx.output.len();
140
141 let mut outputs: Vec<Output> = Vec::with_capacity(outputs_len);
142
143 for o in global.outputs {
144 outputs.push(o);
145 }
146
147 outputs
148 };
149
150 global.inputs = inputs;
151 global.outputs = outputs;
152 Ok(StreamedPSBT { psbt: PsbtWrapper { inner: global }, segwit_flags })
153 }
154}
155
156impl StreamedPSBT {
157 fn unsigned_tx_checks(psbt: &Psbt) -> Result<(), Error> {
159 for txin in &psbt.unsigned_tx.input {
160 if !txin.script_sig.is_empty() {
161 return Err(Error::UnsignedTxHasScriptSigs);
162 }
163
164 if !txin.witness.is_empty() {
165 return Err(Error::UnsignedTxHasScriptWitnesses);
166 }
167 }
168
169 Ok(())
170 }
171}
172
173impl Encodable for StreamedPSBT {
174 fn consensus_encode<W: Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
175 self.psbt.consensus_encode(writer)
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use crate::psbt::PsbtWrapper;
182
183 use super::StreamedPSBT;
184 use bitcoin::consensus::{deserialize, encode::serialize_hex};
185 use bitcoin::hashes::hex::FromHex;
186 use bitcoin::psbt::{Input, Output, Psbt};
187 use bitcoin::*;
188 use core::str::FromStr;
189 use lightning_signer::bitcoin::transaction::Version;
190 use serde_bolt::bitcoin;
191 use std::collections::BTreeMap;
192 use txoo::bitcoin::absolute::Height;
193
194 macro_rules! hex (($hex:expr) => (Vec::from_hex($hex).unwrap()));
195 macro_rules! hex_script (($hex:expr) => (ScriptBuf::from(hex!($hex))));
196 macro_rules! hex_psbt {
197 ($s:expr) => {
198 deserialize::<PsbtWrapper>(&<Vec<u8> as FromHex>::from_hex($s).unwrap())
199 };
200 }
201 macro_rules! hex_streamed {
202 ($s:expr) => {
203 deserialize::<StreamedPSBT>(&<Vec<u8> as FromHex>::from_hex($s).unwrap())
204 };
205 }
206
207 #[test]
209 fn valid_vector_1() {
210 let input_tx = make_input_tx();
211
212 let unserialized = make_psbt(input_tx);
213
214 let base16str = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000";
215
216 assert_eq!(serialize_hex(&PsbtWrapper { inner: unserialized.clone() }), base16str);
217 assert_eq!(unserialized, hex_psbt!(base16str).unwrap().inner);
218 let streamed = hex_streamed!(base16str).unwrap();
219 assert_eq!(streamed.segwit_flags, vec![false]);
220 assert_eq!(
221 streamed.psbt.inner.inputs[0].witness_utxo,
222 Some(TxOut {
223 value: Amount::from_sat(200000000),
224 script_pubkey: hex_script!("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"),
225 })
226 );
227 assert_eq!(streamed.psbt.inner.extract_tx(), unserialized.extract_tx());
228 }
229
230 #[test]
231 fn segwit() {
232 let mut input_tx = make_input_tx();
233 let output_script = hex_script!("0014be18d152a9b012039daf3da7de4f53349eecb985");
234 input_tx.output[0].script_pubkey = output_script.clone();
235
236 let unserialized = make_psbt(input_tx);
237
238 assert!(unserialized.inputs[0].witness_utxo.is_none());
239 let serialized = serialize_hex(&PsbtWrapper { inner: unserialized.clone() });
240 let streamed = hex_streamed!(&serialized).unwrap();
241
242 assert_eq!(streamed.segwit_flags, vec![true]);
243 assert_eq!(
244 streamed.psbt.inner.inputs[0].witness_utxo,
245 Some(TxOut { value: Amount::from_sat(200000000), script_pubkey: output_script })
246 );
247 assert_eq!(streamed.psbt.inner.extract_tx(), unserialized.extract_tx());
248 }
249
250 #[test]
251 fn segwit_with_utxo() {
252 let mut input_tx = make_input_tx();
253 let output_script = hex_script!("0014be18d152a9b012039daf3da7de4f53349eecb985");
254 input_tx.output[0].script_pubkey = output_script.clone();
255
256 let mut unserialized = make_psbt(input_tx);
257 unserialized.inputs[0].witness_utxo = Some(TxOut {
258 value: Amount::from_sat(200000000),
259 script_pubkey: output_script.clone(),
260 });
261 let serialized = serialize_hex(&PsbtWrapper { inner: unserialized.clone() });
262 let streamed = hex_streamed!(&serialized).unwrap();
263
264 assert_eq!(streamed.segwit_flags, vec![true]);
265 assert_eq!(
266 streamed.psbt.inner.inputs[0].witness_utxo,
267 Some(TxOut { value: Amount::from_sat(200000000), script_pubkey: output_script })
268 );
269 assert_eq!(streamed.psbt.inner.extract_tx(), unserialized.extract_tx());
270 }
271
272 #[test]
273 fn segwit_with_bad_utxo() {
274 let mut input_tx = make_input_tx();
275 let output_script = hex_script!("0014be18d152a9b012039daf3da7de4f53349eecb985");
276 input_tx.output[0].script_pubkey = output_script.clone();
277
278 let mut unserialized = make_psbt(input_tx);
279 unserialized.inputs[0].witness_utxo = Some(TxOut {
280 value: Amount::from_sat(200000001),
281 script_pubkey: output_script.clone(),
282 });
283 let serialized = serialize_hex(&PsbtWrapper { inner: unserialized.clone() });
284 assert!(hex_streamed!(&serialized).is_err());
285 }
286
287 fn make_psbt(input_tx: Transaction) -> Psbt {
288 let unserialized = Psbt {
289 unsigned_tx: Transaction {
290 version: Version::TWO,
291 lock_time: absolute::LockTime::Blocks(Height::from_consensus(1257139).unwrap()),
292 input: vec![TxIn {
293 previous_output: OutPoint { txid: input_tx.compute_txid(), vout: 0 },
294 script_sig: ScriptBuf::new(),
295 sequence: Sequence::ENABLE_LOCKTIME_NO_RBF,
296 witness: Witness::default(),
297 }],
298 output: vec![
299 TxOut {
300 value: Amount::from_sat(99999699),
301 script_pubkey: hex_script!(
302 "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"
303 ),
304 },
305 TxOut {
306 value: Amount::from_sat(100000000),
307 script_pubkey: hex_script!(
308 "a9143545e6e33b832c47050f24d3eeb93c9c03948bc787"
309 ),
310 },
311 ],
312 },
313 xpub: Default::default(),
314 version: 0,
315 proprietary: BTreeMap::new(),
316 unknown: BTreeMap::new(),
317
318 inputs: vec![Input { non_witness_utxo: Some(input_tx), ..Default::default() }],
319 outputs: vec![Output { ..Default::default() }, Output { ..Default::default() }],
320 };
321 unserialized
322 }
323
324 fn make_input_tx() -> Transaction {
325 Transaction {
326 version: Version::ONE,
327 lock_time: absolute::LockTime::ZERO,
328 input: vec![TxIn {
329 previous_output: OutPoint {
330 txid: Txid::from_str(
331 "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389",
332 ).unwrap(),
333 vout: 1,
334 },
335 script_sig: hex_script!("160014be18d152a9b012039daf3da7de4f53349eecb985"),
336 sequence: Sequence::MAX,
337 witness: Witness::from_slice(&vec![
338 Vec::from_hex("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01").unwrap(),
339 Vec::from_hex("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").unwrap(),
340 ]),
341 },
342 TxIn {
343 previous_output: OutPoint {
344 txid: Txid::from_str(
345 "b490486aec3ae671012dddb2bb08466bef37720a533a894814ff1da743aaf886",
346 ).unwrap(),
347 vout: 1,
348 },
349 script_sig: hex_script!("160014fe3e9ef1a745e974d902c4355943abcb34bd5353"),
350 sequence: Sequence::MAX,
351 witness: Witness::from_slice(&vec![
352 Vec::from_hex("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").unwrap(),
353 Vec::from_hex("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").unwrap(),
354 ]),
355 }],
356 output: vec![
357 TxOut {
358 value: Amount::from_sat(200000000),
359 script_pubkey: hex_script!("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"),
360 },
361 TxOut {
362 value: Amount::from_sat(190303501938),
363 script_pubkey: hex_script!("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587"),
364 },
365 ],
366 }
367 }
368}