vls_protocol/
psbt.rs

1use alloc::vec::Vec;
2use bitcoin::consensus::{encode, Decodable, Encodable};
3use bitcoin::psbt::{Error, Input, Output, PartiallySignedTransaction};
4use serde_bolt::bitcoin;
5use serde_bolt::io::{self, Read, Write};
6
7// FIXME: devrandom suggested the name `PSBTWithSize`
8#[derive(Debug)]
9pub struct PsbtWrapper {
10    pub inner: PartiallySignedTransaction,
11}
12
13impl From<PartiallySignedTransaction> for PsbtWrapper {
14    fn from(value: PartiallySignedTransaction) -> Self {
15        Self { inner: value }
16    }
17}
18
19impl Encodable for PsbtWrapper {
20    fn consensus_encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
21        let buffer = self.inner.serialize();
22        writer.write(&buffer)
23    }
24}
25
26impl Decodable for PsbtWrapper {
27    fn consensus_decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, encode::Error> {
28        Self::consensus_decode_from_finite_reader(reader)
29    }
30
31    fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(
32        r: &mut R,
33    ) -> Result<Self, encode::Error> {
34        let mut buffer = Vec::new();
35        r.read_to_end(&mut buffer)?;
36        let psbt = PartiallySignedTransaction::deserialize(&buffer)
37            .map_err(|_| encode::Error::ParseFailed("filed to parse the psbt"))?;
38        Ok(Self { inner: psbt })
39    }
40}
41
42/// A PSBT that streams input transactions one at a time instead of
43/// holding them in memory.
44///
45/// The PSBT is still held in memory, but the input transactions
46/// are summarized and then discarded.
47///
48/// `segwit_flags` is a vector of booleans indicating whether we know
49/// that the TXO is segwit.
50///
51/// `psbt.input.witness_utxo` is populated if it is missing, and is otherwise
52/// compared with the input transaction to ensure that it matches.
53///
54/// Note that if the witness flag is false, we didn't validate `witness_utxo`.
55///
56/// This is useful for the following use cases:
57/// - in Lightning, where we must ensure that *all* outputs are segwit, or an attacker
58/// might malleate the funding transaction ID.
59/// - if we might statelessly sign different inputs at different times, and the attacker
60/// might lie about different input amounts at different times (this is fixed in taproot).
61#[derive(Debug)]
62pub struct StreamedPSBT {
63    /// The PSBT
64    pub psbt: PsbtWrapper,
65    /// For each input, whether we know that the TXO is segwit
66    pub segwit_flags: Vec<bool>,
67}
68
69impl StreamedPSBT {
70    pub fn new(psbt: PartiallySignedTransaction) -> Self {
71        let segwit_flags = Vec::new();
72        Self { psbt: PsbtWrapper::from(psbt), segwit_flags }
73    }
74
75    pub fn psbt(&self) -> &PartiallySignedTransaction {
76        &self.psbt.inner
77    }
78
79    pub fn consensus_decode_global<R: Read + ?Sized>(
80        r: &mut R,
81    ) -> Result<PartiallySignedTransaction, encode::Error> {
82        // TODO ask rust-bitcoin to implement a more memory efficient serializer / deserializer
83        let psbt = PsbtWrapper::consensus_decode_from_finite_reader(r)?;
84        Ok(psbt.inner)
85    }
86}
87
88impl Decodable for StreamedPSBT {
89    fn consensus_decode_from_finite_reader<R: Read + ?Sized>(
90        r: &mut R,
91    ) -> Result<Self, encode::Error> {
92        let mut global = Self::consensus_decode_global(r)?;
93        Self::unsigned_tx_checks(&global)
94            .map_err(|_| encode::Error::ParseFailed("txs checks fails"))?;
95
96        let mut segwit_flags: Vec<bool> = Vec::with_capacity(global.unsigned_tx.input.len());
97
98        let inputs: Vec<Input> = {
99            let inputs_len: usize = global.unsigned_tx.input.len();
100
101            let mut inputs: Vec<Input> = Vec::with_capacity(inputs_len);
102
103            for ind in 0..inputs_len {
104                let mut input: Input = global.inputs[ind].clone();
105
106                // take the non_witness_utxo from the input and summarize it
107                if let Some(input_tx) = input.non_witness_utxo.take() {
108                    let prevout = global.unsigned_tx.input[ind].previous_output;
109                    // check if the input txid matched the psbt tx input txid
110                    if input_tx.txid() != prevout.txid {
111                        return Err(encode::Error::ParseFailed("missing utxo"));
112                    }
113                    // check if the matching output exists
114                    if input_tx.output.len() <= prevout.vout as usize {
115                        return Err(encode::Error::ParseFailed("missing utxo"));
116                    }
117                    // check if the matching output is a witness output
118                    let output = &input_tx.output[prevout.vout as usize];
119                    if output.script_pubkey.is_witness_program() {
120                        segwit_flags.push(true);
121                    } else {
122                        segwit_flags.push(false);
123                    }
124
125                    if let Some(ref txo) = input.witness_utxo {
126                        // ensure that the witness utxo matches the output
127                        if txo != output {
128                            return Err(encode::Error::ParseFailed("missing utxo"));
129                        }
130                    } else {
131                        input.witness_utxo = Some(output.clone());
132                    }
133                } else {
134                    segwit_flags.push(false);
135                }
136
137                inputs.push(input);
138            }
139
140            inputs
141        };
142
143        let outputs: Vec<Output> = {
144            let outputs_len: usize = global.unsigned_tx.output.len();
145
146            let mut outputs: Vec<Output> = Vec::with_capacity(outputs_len);
147
148            for o in global.outputs {
149                outputs.push(o);
150            }
151
152            outputs
153        };
154
155        global.inputs = inputs;
156        global.outputs = outputs;
157        Ok(StreamedPSBT { psbt: PsbtWrapper { inner: global }, segwit_flags })
158    }
159}
160
161impl StreamedPSBT {
162    /// Checks that unsigned transaction does not have scriptSig's or witness data.
163    fn unsigned_tx_checks(psbt: &PartiallySignedTransaction) -> Result<(), Error> {
164        for txin in &psbt.unsigned_tx.input {
165            if !txin.script_sig.is_empty() {
166                return Err(Error::UnsignedTxHasScriptSigs);
167            }
168
169            if !txin.witness.is_empty() {
170                return Err(Error::UnsignedTxHasScriptWitnesses);
171            }
172        }
173
174        Ok(())
175    }
176}
177
178impl Encodable for StreamedPSBT {
179    fn consensus_encode<W: Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
180        self.psbt.consensus_encode(writer)
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use crate::psbt::PsbtWrapper;
187
188    use super::StreamedPSBT;
189    use bitcoin::consensus::{deserialize, encode::serialize_hex};
190    use bitcoin::hashes::hex::FromHex;
191    use bitcoin::psbt::{Input, Output, PartiallySignedTransaction};
192    use bitcoin::*;
193    use core::str::FromStr;
194    use serde_bolt::bitcoin;
195    use std::collections::BTreeMap;
196    use txoo::bitcoin::absolute::Height;
197
198    macro_rules! hex (($hex:expr) => (Vec::from_hex($hex).unwrap()));
199    macro_rules! hex_script (($hex:expr) => (ScriptBuf::from(hex!($hex))));
200    macro_rules! hex_psbt {
201        ($s:expr) => {
202            deserialize::<PsbtWrapper>(&<Vec<u8> as FromHex>::from_hex($s).unwrap())
203        };
204    }
205    macro_rules! hex_streamed {
206        ($s:expr) => {
207            deserialize::<StreamedPSBT>(&<Vec<u8> as FromHex>::from_hex($s).unwrap())
208        };
209    }
210
211    // non-segwit test vector from BIP-174
212    #[test]
213    fn valid_vector_1() {
214        let input_tx = make_input_tx();
215
216        let unserialized = make_psbt(input_tx);
217
218        let base16str = "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000";
219
220        assert_eq!(serialize_hex(&PsbtWrapper { inner: unserialized.clone() }), base16str);
221        assert_eq!(unserialized, hex_psbt!(base16str).unwrap().inner);
222        let streamed = hex_streamed!(base16str).unwrap();
223        assert_eq!(streamed.segwit_flags, vec![false]);
224        assert_eq!(
225            streamed.psbt.inner.inputs[0].witness_utxo,
226            Some(TxOut {
227                value: 200000000,
228                script_pubkey: hex_script!("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"),
229            })
230        );
231        assert_eq!(streamed.psbt.inner.extract_tx(), unserialized.extract_tx());
232    }
233
234    #[test]
235    fn segwit() {
236        let mut input_tx = make_input_tx();
237        let output_script = hex_script!("0014be18d152a9b012039daf3da7de4f53349eecb985");
238        input_tx.output[0].script_pubkey = output_script.clone();
239
240        let unserialized = make_psbt(input_tx);
241
242        assert!(unserialized.inputs[0].witness_utxo.is_none());
243        let serialized = serialize_hex(&PsbtWrapper { inner: unserialized.clone() });
244        let streamed = hex_streamed!(&serialized).unwrap();
245
246        assert_eq!(streamed.segwit_flags, vec![true]);
247        assert_eq!(
248            streamed.psbt.inner.inputs[0].witness_utxo,
249            Some(TxOut { value: 200000000, script_pubkey: output_script })
250        );
251        assert_eq!(streamed.psbt.inner.extract_tx(), unserialized.extract_tx());
252    }
253
254    #[test]
255    fn segwit_with_utxo() {
256        let mut input_tx = make_input_tx();
257        let output_script = hex_script!("0014be18d152a9b012039daf3da7de4f53349eecb985");
258        input_tx.output[0].script_pubkey = output_script.clone();
259
260        let mut unserialized = make_psbt(input_tx);
261        unserialized.inputs[0].witness_utxo =
262            Some(TxOut { value: 200000000, script_pubkey: output_script.clone() });
263        let serialized = serialize_hex(&PsbtWrapper { inner: unserialized.clone() });
264        let streamed = hex_streamed!(&serialized).unwrap();
265
266        assert_eq!(streamed.segwit_flags, vec![true]);
267        assert_eq!(
268            streamed.psbt.inner.inputs[0].witness_utxo,
269            Some(TxOut { value: 200000000, script_pubkey: output_script })
270        );
271        assert_eq!(streamed.psbt.inner.extract_tx(), unserialized.extract_tx());
272    }
273
274    #[test]
275    fn segwit_with_bad_utxo() {
276        let mut input_tx = make_input_tx();
277        let output_script = hex_script!("0014be18d152a9b012039daf3da7de4f53349eecb985");
278        input_tx.output[0].script_pubkey = output_script.clone();
279
280        let mut unserialized = make_psbt(input_tx);
281        unserialized.inputs[0].witness_utxo =
282            Some(TxOut { value: 200000001, script_pubkey: output_script.clone() });
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) -> PartiallySignedTransaction {
288        let unserialized = PartiallySignedTransaction {
289            unsigned_tx: Transaction {
290                version: 2,
291                lock_time: absolute::LockTime::Blocks(Height::from_consensus(1257139).unwrap()),
292                input: vec![TxIn {
293                    previous_output: OutPoint { txid: input_tx.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: 99999699,
301                        script_pubkey: hex_script!(
302                            "76a914d0c59903c5bac2868760e90fd521a4665aa7652088ac"
303                        ),
304                    },
305                    TxOut {
306                        value: 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: 1,
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: 200000000,
359                    script_pubkey: hex_script!("76a91485cff1097fd9e008bb34af709c62197b38978a4888ac"),
360                },
361                TxOut {
362                    value: 190303501938,
363                    script_pubkey: hex_script!("a914339725ba21efd62ac753a9bcd067d6c7a6a39d0587"),
364                },
365            ],
366        }
367    }
368}