lwk/
pset.rs

1use crate::{types::AssetId, Issuance, LwkError, Script, Transaction, Txid};
2use elements::pset::{Input, PartiallySignedTransaction};
3use elements::{hashes::Hash, BlockHash};
4use lwk_wollet::elements_miniscript::psbt::finalize;
5use lwk_wollet::EC;
6use std::{fmt::Display, sync::Arc};
7
8/// A Partially Signed Elements Transaction
9#[derive(uniffi::Object, PartialEq, Debug, Clone)]
10#[uniffi::export(Display)]
11pub struct Pset {
12    inner: PartiallySignedTransaction,
13}
14
15impl From<PartiallySignedTransaction> for Pset {
16    fn from(inner: PartiallySignedTransaction) -> Self {
17        Self { inner }
18    }
19}
20
21impl Display for Pset {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "{}", self.inner)
24    }
25}
26
27impl AsRef<PartiallySignedTransaction> for Pset {
28    fn as_ref(&self) -> &PartiallySignedTransaction {
29        &self.inner
30    }
31}
32
33#[uniffi::export]
34impl Pset {
35    /// Construct a Watch-Only wallet object
36    #[uniffi::constructor]
37    pub fn new(base64: &str) -> Result<Arc<Self>, LwkError> {
38        let inner: PartiallySignedTransaction = base64.trim().parse()?;
39        Ok(Arc::new(Pset { inner }))
40    }
41
42    /// Finalize and extract the PSET
43    pub fn finalize(&self) -> Result<Arc<Transaction>, LwkError> {
44        let mut pset = self.inner.clone();
45        finalize(&mut pset, &EC, BlockHash::all_zeros())?;
46        let tx: Transaction = pset.extract_tx()?.into();
47        Ok(Arc::new(tx))
48    }
49
50    /// Extract the Transaction from a Pset by filling in
51    /// the available signature information in place.
52    pub fn extract_tx(&self) -> Result<Arc<Transaction>, LwkError> {
53        let tx: Transaction = self.inner.extract_tx()?.into();
54        Ok(Arc::new(tx))
55    }
56
57    /// Attempt to combine with another `Pset`.
58    pub fn combine(&self, other: &Pset) -> Result<Pset, LwkError> {
59        let mut pset = self.inner.clone();
60        pset.merge(other.inner.clone())?;
61        Ok(pset.into())
62    }
63
64    /// Get the unique id of the PSET as defined by [BIP-370](https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#unique-identification)
65    ///
66    /// The unique id is the txid of the PSET with sequence numbers of inputs set to 0
67    pub fn unique_id(&self) -> Result<Txid, LwkError> {
68        let txid = self.inner.unique_id()?;
69        Ok(txid.into())
70    }
71
72    /// Return a copy of the inputs of this PSET
73    pub fn inputs(&self) -> Vec<Arc<PsetInput>> {
74        self.inner
75            .inputs()
76            .iter()
77            .map(|i| Arc::new(i.clone().into()))
78            .collect()
79    }
80}
81
82impl Pset {
83    pub(crate) fn inner(&self) -> PartiallySignedTransaction {
84        self.inner.clone()
85    }
86}
87
88/// PSET input
89#[derive(uniffi::Object, Debug)]
90pub struct PsetInput {
91    inner: Input,
92}
93
94impl From<Input> for PsetInput {
95    fn from(inner: Input) -> Self {
96        Self { inner }
97    }
98}
99
100#[uniffi::export]
101impl PsetInput {
102    /// Prevout TXID of the input
103    pub fn previous_txid(&self) -> Arc<Txid> {
104        Arc::new(self.inner.previous_txid.into())
105    }
106
107    /// Prevout vout of the input
108    pub fn previous_vout(&self) -> u32 {
109        self.inner.previous_output_index
110    }
111
112    /// Prevout scriptpubkey of the input
113    pub fn previous_script_pubkey(&self) -> Option<Arc<Script>> {
114        self.inner
115            .witness_utxo
116            .as_ref()
117            .map(|txout| Arc::new(txout.script_pubkey.clone().into()))
118    }
119
120    /// Redeem script of the input
121    pub fn redeem_script(&self) -> Option<Arc<Script>> {
122        self.inner
123            .redeem_script
124            .as_ref()
125            .map(|s| Arc::new(s.clone().into()))
126    }
127
128    /// If the input has an issuance, the asset id
129    pub fn issuance_asset(&self) -> Option<AssetId> {
130        self.inner
131            .has_issuance()
132            .then(|| self.inner.issuance_ids().0.into())
133    }
134
135    /// If the input has an issuance, the token id
136    pub fn issuance_token(&self) -> Option<AssetId> {
137        self.inner
138            .has_issuance()
139            .then(|| self.inner.issuance_ids().1.into())
140    }
141
142    /// If the input has a (re)issuance, the issuance object
143    pub fn issuance(&self) -> Option<Arc<Issuance>> {
144        self.inner
145            .has_issuance()
146            .then(|| Arc::new(lwk_common::Issuance::new(&self.inner).into()))
147    }
148
149    /// Input sighash
150    pub fn sighash(&self) -> u32 {
151        self.inner.sighash_type.map(|s| s.to_u32()).unwrap_or(1)
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::Pset;
158
159    #[test]
160    fn pset_roundtrip() {
161        let pset_string =
162            include_str!("../../lwk_jade/test_data/pset_to_be_signed.base64").to_string();
163        let pset = Pset::new(&pset_string).unwrap();
164
165        let tx_expected =
166            include_str!("../../lwk_jade/test_data/pset_to_be_signed_transaction.hex").to_string();
167        let tx = pset.extract_tx().unwrap();
168        assert_eq!(tx_expected, tx.to_string());
169
170        assert_eq!(pset_string, pset.to_string());
171
172        assert_eq!(pset.inputs().len(), tx.inputs().len());
173        let pset_in = &pset.inputs()[0];
174        let tx_in = &tx.inputs()[0];
175        assert_eq!(pset_in.previous_txid(), tx_in.outpoint().txid());
176        assert_eq!(pset_in.previous_vout(), tx_in.outpoint().vout());
177        assert!(pset_in.previous_script_pubkey().is_some());
178        assert!(pset_in.redeem_script().is_none());
179
180        assert!(pset_in.issuance_asset().is_none());
181        assert!(pset_in.issuance_token().is_none());
182    }
183
184    #[test]
185    fn pset_combine() {
186        let psets = lwk_test_util::psets_to_combine().1;
187        let psets: Vec<Pset> = psets.into_iter().map(Into::into).collect();
188        psets[0].finalize().unwrap_err(); // not enough signatures
189
190        let pset01 = psets[0].combine(&psets[1]).unwrap();
191        pset01.finalize().unwrap_err(); // not enough signatures
192        let pset012 = pset01.combine(&psets[2]).unwrap();
193        pset012.finalize().unwrap(); // enough signatures
194    }
195
196    #[test]
197    fn pset_unique_id() {
198        let psets = lwk_test_util::psets_to_combine().1;
199        let psets: Vec<Pset> = psets.into_iter().map(Into::into).collect();
200
201        let unique_id = psets[0].unique_id().unwrap();
202        for pset in psets.iter().skip(1) {
203            assert_eq!(unique_id, pset.unique_id().unwrap());
204
205            // sequence number is 0xffffffff, unique id set it to 0 before computing the hash, so txid is different
206            assert_ne!(unique_id, *pset.extract_tx().unwrap().txid());
207        }
208    }
209}