lnp/channel/
funding.rs

1// LNP/BP Core Library implementing LNPBP specifications & standards
2// Written in 2020-2022 by
3//     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
4//
5// To the extent possible under law, the author(s) have dedicated all
6// copyright and related and neighboring rights to this software to
7// the public domain worldwide. This software is distributed without
8// any warranty.
9//
10// You should have received a copy of the MIT License
11// along with this software.
12// If not, see <https://opensource.org/licenses/MIT>.
13
14use bitcoin::util::psbt::raw::ProprietaryKey;
15use bitcoin::{OutPoint, Transaction, TxOut, Txid};
16use wallet::psbt::{Psbt, PsbtVersion};
17
18pub const PSBT_LNP_PROPRIETARY_PREFIX: &[u8] = b"LNP";
19pub const PSBT_OUT_LNP_CHANNEL_FUNDING: u8 = 0x01;
20
21#[derive(
22    Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error
23)]
24#[display(doc_comments)]
25pub enum Error {
26    /// no funding output found in the funding transaction. The funding output
27    /// must be marked with proprietary key having "LNP" prefix and 0x01
28    /// subtype.
29    NoFundingOutput,
30
31    /// funding transaction does not contain output #{0} specified as a
32    /// funding outpoint
33    WrongOutput(u16),
34}
35
36/// Information about channel funding
37#[derive(Getters, Clone, PartialEq, Eq, Debug, StrictEncode, StrictDecode)]
38#[cfg_attr(
39    feature = "serde",
40    derive(Serialize, Deserialize),
41    serde(crate = "serde_crate")
42)]
43pub struct Funding {
44    /// PSBT containing full information about the funding of the channel in a
45    /// structured way.
46    ///
47    /// Channel is always funded with a single input, that is why we need a
48    /// single PSBT. If channel needs to receive more funds, it will require a
49    /// new funding transaction to be created, spending previous funding
50    /// transaction output.
51    psbt: Psbt,
52
53    // Cached information extracted from PSBT, which is the master data source
54    #[getter(as_copy)]
55    txid: Txid,
56
57    #[getter(as_copy)]
58    output: u16,
59
60    #[getter(as_copy)]
61    amount: u64,
62
63    #[getter(as_copy)]
64    signing_parties: u8,
65
66    #[getter(as_copy)]
67    signing_threshold: u8,
68}
69
70impl Funding {
71    /// Constructs empty funding information. Can be used only during initial
72    /// channel setup.
73    #[inline]
74    pub(super) fn new() -> Funding {
75        let mut psbt = Psbt::with(
76            Transaction {
77                version: 2,
78                lock_time: bitcoin::PackedLockTime(0),
79                input: vec![],
80                output: vec![TxOut {
81                    value: 0,
82                    script_pubkey: Default::default(),
83                }],
84            },
85            PsbtVersion::V0,
86        )
87        .expect("dumb manual PSBT creation");
88        psbt.outputs[0]
89            .proprietary
90            .insert(lnp_out_channel_funding_key(), vec![]);
91        Funding::with(psbt).expect("dumb manual PSBT creation")
92    }
93
94    #[inline]
95    pub fn with(psbt: Psbt) -> Result<Funding, Error> {
96        psbt.extract_channel_funding()
97    }
98
99    #[inline]
100    pub(super) fn preliminary(funding_amount: u64) -> Funding {
101        let mut funding = Funding::new();
102        funding.amount = funding_amount;
103        funding
104    }
105
106    #[inline]
107    pub fn outpoint(&self) -> OutPoint {
108        OutPoint::new(self.txid, self.output as u32)
109    }
110}
111
112fn lnp_out_channel_funding_key() -> ProprietaryKey {
113    ProprietaryKey {
114        prefix: PSBT_LNP_PROPRIETARY_PREFIX.to_vec(),
115        subtype: PSBT_OUT_LNP_CHANNEL_FUNDING,
116        key: vec![],
117    }
118}
119
120pub trait PsbtLnpFunding {
121    fn channel_funding_output(&self) -> Option<usize>;
122    fn set_channel_funding_output(&mut self, vout: u16) -> Result<(), Error>;
123    fn channel_funding_outpoint(&self) -> Result<OutPoint, Error>;
124    fn extract_channel_funding(self) -> Result<Funding, Error>;
125}
126
127impl PsbtLnpFunding for Psbt {
128    fn channel_funding_output(&self) -> Option<usize> {
129        let funding_key = lnp_out_channel_funding_key();
130        self.outputs
131            .iter()
132            .enumerate()
133            .find(|(_, output)| output.proprietary.get(&funding_key).is_some())
134            .map(|(index, _)| index)
135    }
136
137    fn set_channel_funding_output(&mut self, vout: u16) -> Result<(), Error> {
138        self.outputs
139            .get_mut(vout as usize)
140            .map(|out| {
141                out.proprietary
142                    .insert(lnp_out_channel_funding_key(), vec![]);
143            })
144            .ok_or(Error::WrongOutput(vout))
145    }
146
147    fn channel_funding_outpoint(&self) -> Result<OutPoint, Error> {
148        let vout = self
149            .channel_funding_output()
150            .ok_or(Error::NoFundingOutput)?;
151        Ok(OutPoint::new(self.to_txid(), vout as u32))
152    }
153
154    fn extract_channel_funding(self) -> Result<Funding, Error> {
155        let vout = self
156            .channel_funding_output()
157            .ok_or(Error::NoFundingOutput)?;
158        let amount = self.outputs[vout].amount;
159        let txid = self.to_txid();
160        // TODO: Parse number of signing parties and signing threshold from
161        //       witness script attached to the funding output
162        Ok(Funding {
163            psbt: self,
164            txid,
165            output: vout as u16,
166            amount,
167            signing_parties: 2,
168            signing_threshold: 2,
169        })
170    }
171}