Skip to main content

rgb/
bp_provider.rs

1// RGB API library for smart contracts on Bitcoin & Lightning network
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2023 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use std::convert::Infallible;
23
24use bpwallet::psbt::{
25    Beneficiary as BpBeneficiary, Output as BpOutput, PropKey, PsbtConstructor,
26    PsbtMeta as BpPsbtMeta, TxParams as BpTxParams,
27};
28use bpwallet::{Address, IdxBase, LockTime, NormalIndex, Psbt as BpPsbt, Sats, SeqNo, Wallet};
29use psrgbt::bp_conversion_utils::{
30    address_network_bitcoin_to_bp, address_payload_bp_from_script_pubkey, outpoint_bitcoin_to_bp,
31    script_buf_to_script_pubkey, txid_bitcoin_to_bp, untweakedpublickey_to_internal_pk,
32};
33use psrgbt::{RgbOutExt, RgbPsbtExt, Terminal};
34use rgbstd::containers::Transfer;
35use rgbstd::invoice::{Beneficiary, RgbInvoice};
36use rgbstd::rgbcore::commit_verify::mpc::{Message, ProtocolId};
37use rgbstd::rgbcore::dbc::tapret::TapretCommitment;
38use rgbstd::rgbcore::seals::txout::CloseMethod;
39use rgbstd::validation::DbcProof;
40use rgbstd::{Operation, Outpoint, Txid};
41
42use super::*;
43use crate::pay::{PsbtMeta, TxParams};
44use crate::{CompositionError, DescriptorRgb, WalletError};
45
46impl From<BpTxParams> for TxParams {
47    fn from(value: BpTxParams) -> Self {
48        Self {
49            fee_sats: value.fee.sats(),
50            lock_time: value.lock_time.map(|l| l.into_consensus_u32()),
51            seq_no: value.seq_no.to_consensus_u32(),
52            change_shift: value.change_shift,
53            change_keychain: value.change_keychain.into(),
54        }
55    }
56}
57
58impl From<TxParams> for BpTxParams {
59    fn from(value: TxParams) -> Self {
60        Self {
61            fee: Sats::from_sats(value.fee_sats),
62            lock_time: value.lock_time.map(LockTime::from_consensus_u32),
63            seq_no: SeqNo::from_consensus_u32(value.seq_no),
64            change_shift: value.change_shift,
65            change_keychain: value.change_keychain.into(),
66        }
67    }
68}
69
70impl From<BpPsbtMeta> for PsbtMeta {
71    fn from(value: BpPsbtMeta) -> Self {
72        Self {
73            beneficiary_vout: None,
74            change_vout: value.change_vout.map(|v| v.into_u32()),
75        }
76    }
77}
78
79impl<K, D: DescriptorRgb + bpwallet::Descriptor<K>> WalletProvider for Wallet<K, D> {
80    type P = PropKey;
81    type O = BpOutput;
82    type Psbt = BpPsbt;
83
84    fn close_method(&self) -> CloseMethod { self.descriptor().close_method() }
85
86    fn is_unspent(&self, outpoint: Outpoint) -> bool {
87        self.is_unspent(outpoint_bitcoin_to_bp(outpoint))
88    }
89
90    fn has_outpoint(&self, outpoint: Outpoint) -> bool {
91        self.has_outpoint(outpoint_bitcoin_to_bp(outpoint))
92    }
93
94    fn should_include_witness(&self, witness_id: Option<Txid>) -> bool {
95        let witness_id = witness_id.map(txid_bitcoin_to_bp);
96        self.history()
97            .any(|row| !row.our_inputs.is_empty() && witness_id == Some(row.txid))
98    }
99
100    fn add_tapret_tweak(
101        &mut self,
102        terminal: Terminal,
103        tweak: TapretCommitment,
104    ) -> Result<(), Infallible> {
105        self.descriptor_mut(|descr| {
106            descr.with_descriptor_mut(|d| {
107                d.add_tapret_tweak(terminal, tweak);
108                Ok::<_, Infallible>(())
109            })
110        })
111    }
112
113    fn try_add_tapret_tweak(
114        &mut self,
115        transfer: Transfer,
116        txid: &Txid,
117    ) -> Result<(), Box<WalletError>> {
118        let contract_id = transfer.genesis.contract_id();
119        for keychain in self.keychains() {
120            let last_index = self.next_derivation_index(keychain, false).index() as u16;
121            let descr = self.descriptor();
122            if let Some((idx, tweak)) = transfer
123                .bundles
124                .iter()
125                .find(|bw| bw.witness_id() == *txid)
126                .and_then(|bw| {
127                    let bundle_id = bw.bundle().bundle_id();
128                    if let DbcProof::Tapret(tapret) = bw.anchor.dbc_proof.clone() {
129                        let internal_pk = untweakedpublickey_to_internal_pk(tapret.internal_pk);
130                        let commitment = bw
131                            .anchor
132                            .mpc_proof
133                            .clone()
134                            .convolve(ProtocolId::from(contract_id), Message::from(bundle_id))
135                            .unwrap();
136                        let tweak = TapretCommitment::with(commitment, tapret.path_proof.nonce());
137                        (0..last_index)
138                            .rev()
139                            .map(NormalIndex::normal)
140                            .find(|i| {
141                                descr
142                                    .derive(keychain, i)
143                                    .any(|ds| ds.to_internal_pk() == Some(internal_pk))
144                            })
145                            .map(|idx| (idx, tweak))
146                    } else {
147                        None
148                    }
149                })
150            {
151                let terminal = bpwallet::Terminal::new(keychain, idx);
152                self.add_tapret_tweak(terminal.into(), tweak).unwrap();
153                return Ok(());
154            }
155        }
156        Err(Box::new(WalletError::NoTweakTerminal))
157    }
158
159    fn create_psbt(
160        &mut self,
161        invoice: &RgbInvoice,
162        close_method: CloseMethod,
163        prev_outpoints: impl IntoIterator<Item = Outpoint>,
164        params: TransferParams,
165    ) -> Result<(Self::Psbt, PsbtMeta), CompositionError> {
166        let (beneficiaries, beneficiary_script) = match invoice.beneficiary.into_inner() {
167            Beneficiary::BlindedSeal(_) => (vec![], None),
168            Beneficiary::WitnessVout(pay2vout, _) => {
169                let script_pubkey = script_buf_to_script_pubkey(pay2vout.to_script());
170                let address = Address::new(
171                    address_payload_bp_from_script_pubkey(&script_pubkey),
172                    address_network_bitcoin_to_bp(invoice.address_network()),
173                );
174                let bp_beneficiary =
175                    BpBeneficiary::new(address, Sats::from_sats(params.min_amount));
176                (vec![bp_beneficiary], Some(script_pubkey))
177            }
178        };
179
180        let prev_outpoints = prev_outpoints
181            .into_iter()
182            .map(outpoint_bitcoin_to_bp)
183            .collect::<Vec<_>>();
184
185        let bp_tx_params: BpTxParams = params.tx.into();
186        let (mut psbt, mut meta) =
187            self.construct_psbt(prev_outpoints, &beneficiaries, bp_tx_params)?;
188
189        let change_script = meta
190            .change_vout
191            .and_then(|vout| psbt.output(vout.to_usize()))
192            .map(|output| output.script.clone());
193
194        match close_method {
195            CloseMethod::TapretFirst => {
196                let tap_out_script = if let Some(change_script) = change_script.clone() {
197                    psbt.set_rgb_tapret_host_on_change();
198                    change_script
199                } else {
200                    match invoice.beneficiary.into_inner() {
201                        Beneficiary::WitnessVout(_, Some(ikey)) => {
202                            let beneficiary_script = beneficiary_script.unwrap();
203                            let ikey = untweakedpublickey_to_internal_pk(ikey);
204                            psbt.outputs_mut()
205                                .find(|o| o.script == beneficiary_script)
206                                .unwrap()
207                                .tap_internal_key = Some(ikey);
208                            beneficiary_script
209                        }
210                        _ => return Err(CompositionError::NoOutputForTapretCommitment),
211                    }
212                };
213                psbt.outputs_mut()
214                    .find(|o| o.script.is_p2tr() && o.script == tap_out_script)
215                    .map(|o| o.set_tapret_host());
216                // TODO: Add descriptor id to the tapret host data
217                psbt.sort_outputs_by(|output| !output.is_tapret_host())
218                    .expect("PSBT must be modifiable at this stage");
219            }
220            CloseMethod::OpretFirst => {
221                psbt.set_opret_host();
222                psbt.sort_outputs_by(|output| !output.is_opret_host())
223                    .expect("PSBT must be modifiable at this stage");
224            }
225        }
226
227        if let Some(ref change_script) = change_script {
228            for output in psbt.outputs() {
229                if output.script == *change_script {
230                    meta.change_vout = Some(output.vout());
231                    break;
232                }
233            }
234        }
235
236        let beneficiary_vout = match invoice.beneficiary.into_inner() {
237            Beneficiary::WitnessVout(pay2vout, _) => {
238                let s = script_buf_to_script_pubkey((*pay2vout).to_script());
239                let vout = psbt
240                    .outputs()
241                    .find(|output| output.script == s)
242                    .map(BpOutput::vout)
243                    .expect("PSBT without beneficiary address");
244                debug_assert_ne!(Some(vout), meta.change_vout);
245                Some(vout)
246            }
247            Beneficiary::BlindedSeal(_) => None,
248        };
249
250        let mut meta: PsbtMeta = meta.into();
251        meta.beneficiary_vout = beneficiary_vout.map(|v| v.into_u32());
252
253        Ok((psbt, meta))
254    }
255}