psrgbt/
lib.rs

1// Partially signed bitcoin transaction RGB extensions
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2020-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
22#[macro_use]
23extern crate amplify;
24
25#[cfg(feature = "bp")]
26mod bp;
27#[cfg(feature = "bp")]
28pub mod bp_conversion_utils;
29mod rb;
30
31use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
32use std::str::FromStr;
33
34use amplify::confinement::{Confined, NonEmptyOrdMap, SmallVec, U16, U24, U32};
35use amplify::num::u5;
36use amplify::{confinement, FromSliceError, Wrapper};
37use rgbstd::bitcoin::bip32::DerivationPath;
38use rgbstd::bitcoin::key::UntweakedPublicKey;
39use rgbstd::bitcoin::{ScriptBuf, Transaction};
40use rgbstd::containers::{Batch, Fascia, PubWitness, SealWitness};
41use rgbstd::opret::OpretProof;
42use rgbstd::rgbcore::commit_verify::mpc::{
43    self, Commitment, Message, ProtocolId, MPC_MINIMAL_DEPTH,
44};
45use rgbstd::rgbcore::commit_verify::{CommitId, TryCommitVerify};
46use rgbstd::tapret::{TapretCommitment, TapretPathProof, TapretProof};
47use rgbstd::txout::CloseMethod;
48use rgbstd::{
49    AssignmentType, ContractId, KnownTransition, MergeReveal, MergeRevealError, OpId, Operation,
50    Opout, Proof, Transition, TransitionBundle, Txid,
51};
52use strict_encoding::{DeserializeError, StrictDeserialize, StrictSerialize};
53
54// TODO: Instead of storing whole RGB contract in PSBT create a shortened
55//       contract version which skips all info not important for hardware
56//       signers
57// /// Proprietary key subtype for storing RGB contract consignment in
58// /// global map.
59// pub const PSBT_GLOBAL_RGB_CONTRACT: u8 = 0x00;
60
61/// PSBT proprietary key prefix used for MPC.
62pub const PSBT_MPC_PREFIX: [u8; 3] = [77, 80, 67];
63pub const PSBT_MPC_PREFIX_STR: &str = "MPC";
64/// Proprietary key subtype for storing MPC single commitment message under
65/// some protocol in global map.
66pub const PSBT_OUT_MPC_MESSAGE: u8 = 0x00;
67/// Proprietary key subtype for storing MPC entropy constant.
68pub const PSBT_OUT_MPC_ENTROPY: u8 = 0x01;
69/// Proprietary key subtype for storing MPC requirement for a minimal tree
70/// size.
71pub const PSBT_OUT_MPC_MIN_TREE_DEPTH: u8 = 0x04;
72/// The final multi-protocol commitment value.
73pub const PSBT_OUT_MPC_COMMITMENT: u8 = 0x10;
74/// The multi-protocol commitment proof.
75pub const PSBT_OUT_MPC_PROOF: u8 = 0x11;
76
77/// PSBT proprietary key prefix used for opret commitment.
78pub const PSBT_OPRET_PREFIX: [u8; 5] = [79, 80, 82, 69, 84];
79pub const PSBT_OPRET_PREFIX_STR: &str = "OPRET";
80/// Proprietary key subtype marking PSBT outputs which may host opret
81/// commitment.
82pub const PSBT_OUT_OPRET_HOST: u8 = 0x00;
83/// Proprietary key subtype holding 32-byte commitment which will be put into
84/// opret data.
85pub const PSBT_OUT_OPRET_COMMITMENT: u8 = 0x01;
86
87/// PSBT proprietary key prefix used for tapreturn commitment.
88pub const PSBT_TAPRET_PREFIX: [u8; 5] = [84, 65, 82, 69, 84];
89pub const PSBT_TAPRET_PREFIX_STR: &str = "TAPRET";
90/// Proprietary key subtype marking PSBT outputs which may host tapreturn
91/// commitment.
92pub const PSBT_OUT_TAPRET_HOST: u8 = 0x00;
93/// Proprietary key subtype holding 32-byte commitment which will be put into
94/// tapret tweak.
95pub const PSBT_OUT_TAPRET_COMMITMENT: u8 = 0x01;
96/// Proprietary key subtype holding merkle branch path to tapreturn tweak inside
97/// the taptree structure.
98pub const PSBT_OUT_TAPRET_PROOF: u8 = 0x02;
99
100/// PSBT proprietary key prefix used for RGB.
101pub const PSBT_RGB_PREFIX: [u8; 3] = [82, 71, 66];
102pub const PSBT_RGB_PREFIX_STR: &str = "RGB";
103/// Proprietary key subtype for storing RGB state transition in global map.
104pub const PSBT_GLOBAL_RGB_TRANSITION: u8 = 0x01;
105/// Proprietary key subtype for storing information on which close method
106/// should be used.
107pub const PSBT_GLOBAL_RGB_CLOSE_METHOD: u8 = 0x02;
108/// Proprietary key subtype to signal that tapret host has been put on change.
109pub const PSBT_GLOBAL_RGB_TAP_HOST_CHANGE: u8 = 0x03;
110/// Proprietary key subtype for storing RGB input allocation and ID of the
111/// transition spending it.
112pub const PSBT_GLOBAL_RGB_CONSUMED_BY: u8 = 0x04;
113
114/// Errors processing MPC-related proprietary PSBT keys and their values.
115#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
116#[display(doc_comments)]
117pub enum MpcPsbtError {
118    /// the key contains invalid value.
119    #[from(FromSliceError)]
120    InvalidKeyValue,
121
122    /// message map produced from PSBT inputs exceeds maximum size bounds.
123    #[from]
124    MessageMapTooLarge(confinement::Error),
125
126    /// key is already present.
127    KeyAlreadyPresent,
128
129    /// output already contains commitment; there must be a single commitment
130    /// per output.
131    OutputAlreadyHasCommitment,
132
133    #[from]
134    #[display(inner)]
135    Mpc(mpc::Error),
136
137    /// multi-protocol commitment is already finalized.
138    Finalized,
139}
140
141/// Errors processing opret-related proprietary PSBT keys and their values.
142#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)]
143#[display(doc_comments)]
144pub enum OpretKeyError {
145    /// output already contains commitment; there must be a single commitment
146    /// per output.
147    OutputAlreadyHasCommitment,
148
149    /// the output can't host a commitment since it does not contain OP_RETURN
150    /// script
151    NonOpReturnOutput,
152
153    /// the output is not marked to host opret commitments. Please first set
154    /// PSBT_OUT_OPRET_HOST flag.
155    OpretProhibited,
156
157    /// the output contains no valid opret commitment.
158    NoCommitment,
159
160    /// the value of opret commitment has invalid length.
161    InvalidCommitment,
162
163    /// the script format doesn't match requirements for opret commitment.
164    InvalidOpReturnScript,
165}
166
167/// Errors processing tapret-related proprietary PSBT keys and their values.
168#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error, From)]
169#[display(doc_comments)]
170pub enum TapretKeyError {
171    /// output already contains commitment; there must be a single commitment
172    /// per output.
173    OutputAlreadyHasCommitment,
174
175    /// the output is not marked to host tapret commitments. Please first set
176    /// PSBT_OUT_TAPRET_HOST flag.
177    TapretProhibited,
178
179    /// the provided output is not a taproot output and can't host a tapret
180    /// commitment.
181    NotTaprootOutput,
182
183    /// the output contains no valid tapret commitment.
184    NoCommitment,
185
186    /// the value of tapret commitment has invalid length.
187    InvalidCommitment,
188
189    /// use of taproot script descriptors is not yet supported.
190    TapTreeNonEmpty,
191
192    /// taproot output doesn't specify internal key.
193    NoInternalKey,
194}
195
196/// Errors processing RGB-related proprietary PSBT keys and their values.
197#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
198#[display(doc_comments)]
199pub enum RgbPsbtError {
200    /// the opout is already signalled as spent by a different opid
201    DoubleSpend,
202
203    /// state transition {0} already present in PSBT is not related to the state
204    /// transition {1} which has to be added to RGB
205    UnrelatedTransitions(OpId, OpId, MergeRevealError),
206
207    /// PSBT contains no contract information
208    NoContracts,
209
210    /// PSBT contains no contract consumers information
211    NoContractConsumers,
212
213    /// contract {0} listed in the PSBT has an invalid number of known transitions {1}.
214    InvalidTransitionsNumber(ContractId, usize),
215
216    /// inputs listed in the PSBT have an invalid number {0}.
217    InvalidInputsNumber(usize),
218
219    /// invalid contract id data.
220    #[from(FromSliceError)]
221    InvalidContractId,
222
223    /// invalid opout and opids data: {0}.
224    InvalidOpoutAndOpidsData(String),
225
226    /// data inconsistency in bundle's known transitions
227    KnownTransitionsInconsistency,
228
229    /// PSBT doesn't provide information about close method.
230    NoCloseMethod,
231
232    /// PSBT provides invalid close method information.
233    InvalidCloseMethod,
234
235    /// PSBT doesn't specify an output which can host {0} commitment.
236    NoHostOutput(CloseMethod),
237
238    /// PSBT contains too many contracts.
239    TooManyContracts,
240
241    /// PSBT contains too many state transitions for a bundle.
242    #[from(confinement::Error)]
243    TooManyTransitionsInBundle,
244
245    /// the transition with opid {0} is too big.
246    TransitionTooBig(OpId),
247
248    /// state transition data in PSBT are invalid. Details: {0}
249    #[from]
250    InvalidTransition(DeserializeError),
251
252    /// MPC PSBT error
253    #[from]
254    #[display(inner)]
255    Mpc(MpcPsbtError),
256}
257
258#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
259#[display(doc_comments)]
260pub enum EmbedError {
261    #[from]
262    Rgb(RgbPsbtError),
263}
264
265/// Errors processing DBC-related proprietary PSBT keys and their values.
266#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
267#[display(doc_comments)]
268pub enum DbcPsbtError {
269    /// the first output valid for a DBC commitment is not marked as a commitment host.
270    NoHostOutput,
271
272    /// the transactions contains no output valid for {0} DBC commitment.
273    NoProperOutput(CloseMethod),
274
275    /// DBC commitment is already present.
276    AlreadyPresent,
277
278    /// transaction outputs are marked as modifiable, thus deterministic bitcoin commitment can't
279    /// be created.
280    TxOutputsModifiable,
281
282    /// MPC PSBT error
283    #[from]
284    #[display(inner)]
285    Mpc(MpcPsbtError),
286
287    /// Tapret key error
288    #[from]
289    #[display(inner)]
290    Tapret(TapretKeyError),
291
292    /// Opret key error
293    #[from]
294    #[display(inner)]
295    Opret(OpretKeyError),
296}
297
298#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
299#[display(inner)]
300pub enum CommitError {
301    #[from]
302    Rgb(RgbPsbtError),
303
304    #[from]
305    Dbc(DbcPsbtError),
306}
307
308#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display)]
309#[display("&{keychain}/{index}")]
310pub struct Terminal {
311    pub keychain: u8,
312    pub index: u32,
313}
314
315impl Terminal {
316    pub fn new(keychain: u8, index: u32) -> Self { Self { keychain, index } }
317
318    pub fn from_derivation_path(derivation_path: &DerivationPath) -> Option<Self> {
319        let mut path = derivation_path.to_u32_vec();
320        path.reverse();
321        let index = path.pop()?;
322        let keychain = u8::try_from(path.pop()?).ok()?;
323        Some(Self::new(keychain, index))
324    }
325}
326
327#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
328#[display(doc_comments)]
329pub enum TerminalParseError {
330    /// terminal derivation path must start with keychain index prefixed with '&'.
331    NoKeychain,
332
333    /// keychain or index in terminal derivation path is not a number.
334    #[from]
335    InvalidTerminal(std::num::ParseIntError),
336
337    /// derivation path '{0}' is not a terminal path - terminal path must contain exactly two
338    /// components.
339    InvalidComponents(String),
340}
341
342impl FromStr for Terminal {
343    type Err = TerminalParseError;
344
345    fn from_str(s: &str) -> Result<Self, Self::Err> {
346        let mut iter = s.split('/');
347        match (iter.next(), iter.next(), iter.next()) {
348            (Some(keychain), Some(index), None) => {
349                if !keychain.starts_with('&') {
350                    return Err(TerminalParseError::NoKeychain);
351                }
352                Ok(Terminal::new(u8::from_str(keychain.trim_start_matches('&'))?, index.parse()?))
353            }
354            _ => Err(TerminalParseError::InvalidComponents(s.to_owned())),
355        }
356    }
357}
358
359#[cfg(feature = "serde")]
360mod _serde {
361    use serde_crate::de::Error;
362    use serde_crate::{Deserialize, Deserializer, Serialize, Serializer};
363
364    use super::*;
365
366    impl Serialize for Terminal {
367        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
368        where S: Serializer {
369            if serializer.is_human_readable() {
370                self.to_string().serialize(serializer)
371            } else {
372                let tuple = (self.keychain, self.index);
373                tuple.serialize(serializer)
374            }
375        }
376    }
377
378    impl<'de> Deserialize<'de> for Terminal {
379        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
380        where D: Deserializer<'de> {
381            if deserializer.is_human_readable() {
382                let s = String::deserialize(deserializer)?;
383                Self::from_str(&s).map_err(D::Error::custom)
384            } else {
385                let d = <(u8, u32)>::deserialize(deserializer)?;
386                Ok(Self {
387                    keychain: d.0,
388                    index: d.1,
389                })
390            }
391        }
392    }
393}
394
395pub trait DbcPsbtProof: Proof {
396    const METHOD: CloseMethod;
397    fn dbc_commit<P: RgbPropKeyExt, O: RgbOutExt<P>>(
398        psbt: &mut impl RgbPsbtExt<P, O>,
399    ) -> Result<(mpc::MerkleBlock, Self), DbcPsbtError>;
400}
401
402impl DbcPsbtProof for OpretProof {
403    const METHOD: CloseMethod = CloseMethod::OpretFirst;
404
405    fn dbc_commit<P: RgbPropKeyExt, O: RgbOutExt<P>>(
406        psbt: &mut impl RgbPsbtExt<P, O>,
407    ) -> Result<(mpc::MerkleBlock, Self), DbcPsbtError> {
408        let (idx, output) = psbt
409            .dbc_output_mut::<Self>()
410            .ok_or(DbcPsbtError::NoProperOutput(Self::METHOD))?;
411
412        let (commitment, mpc_proof) = output.mpc_commit()?;
413        output.opret_commit(commitment)?;
414
415        psbt.set_opret_commitment(idx);
416
417        Ok((mpc_proof, OpretProof::default()))
418    }
419}
420
421impl DbcPsbtProof for TapretProof {
422    const METHOD: CloseMethod = CloseMethod::TapretFirst;
423
424    fn dbc_commit<P: RgbPropKeyExt, O: RgbOutExt<P>>(
425        psbt: &mut impl RgbPsbtExt<P, O>,
426    ) -> Result<(mpc::MerkleBlock, Self), DbcPsbtError> {
427        let (idx, output) = psbt
428            .dbc_output_mut::<Self>()
429            .ok_or(DbcPsbtError::NoProperOutput(Self::METHOD))?;
430
431        let (commitment, mpc_proof) = output.mpc_commit()?;
432        let tapret_proof = output.tapret_commit(commitment)?;
433
434        psbt.set_tapret_commitment(idx);
435
436        Ok((mpc_proof, tapret_proof))
437    }
438}
439
440#[derive(Wrapper, WrapperMut, Clone, PartialEq, Eq, Debug, From)]
441#[wrapper(Deref)]
442#[wrapper_mut(DerefMut)]
443pub struct OpoutAndOpids(BTreeMap<Opout, OpId>);
444
445impl OpoutAndOpids {
446    pub fn new(items: BTreeMap<Opout, OpId>) -> Self { Self(items) }
447
448    pub fn serialize(&self) -> Vec<u8> {
449        let mut bytes = Vec::new();
450        for (opout, opid) in &self.0 {
451            bytes.extend(opout.op.to_byte_array());
452            bytes.extend(opout.ty.to_le_bytes());
453            bytes.extend(opout.no.to_le_bytes());
454            bytes.extend(opid.to_byte_array());
455        }
456        bytes
457    }
458
459    #[allow(clippy::result_large_err)]
460    pub fn deserialize(bytes: &[u8]) -> Result<Self, RgbPsbtError> {
461        let opid_size = std::mem::size_of::<OpId>();
462        let assignment_type_size = std::mem::size_of::<u16>();
463        let u16_size = std::mem::size_of::<u16>();
464        let item_size = opid_size + assignment_type_size + u16_size + opid_size;
465        let bytes_len = bytes.len();
466        if bytes_len % item_size != 0 {
467            return Err(RgbPsbtError::InvalidOpoutAndOpidsData(format!(
468                "Input data length {bytes_len} is not a multiple of {item_size}"
469            )));
470        }
471        let mut items = BTreeMap::new();
472        for chunk in bytes.chunks_exact(item_size) {
473            let mut cursor = 0;
474            let op = OpId::copy_from_slice(&chunk[cursor..cursor + opid_size]).map_err(|e| {
475                RgbPsbtError::InvalidOpoutAndOpidsData(format!(
476                    "Error deserializing Opout.op: {e:?}",
477                ))
478            })?;
479            cursor += opid_size;
480            let ty_bytes = &chunk[cursor..cursor + assignment_type_size];
481            let ty_u16 = u16::from_le_bytes([ty_bytes[0], ty_bytes[1]]);
482            let ty = AssignmentType::with(ty_u16);
483            cursor += assignment_type_size;
484            let no_bytes = &chunk[cursor..cursor + u16_size];
485            let no = u16::from_le_bytes([no_bytes[0], no_bytes[1]]);
486            cursor += u16_size;
487            let opid = OpId::copy_from_slice(&chunk[cursor..cursor + opid_size]).map_err(|e| {
488                RgbPsbtError::InvalidOpoutAndOpidsData(format!(
489                    "Error deserializing consuming OpId: {e:?}"
490                ))
491            })?;
492            let opout = Opout::new(op, ty, no);
493            items.insert(opout, opid);
494        }
495        Ok(OpoutAndOpids::new(items))
496    }
497}
498
499#[allow(clippy::result_large_err)]
500fn insert_transitions_sorted(
501    transitions: &HashMap<OpId, Transition>,
502    known_transitions: &mut SmallVec<KnownTransition>,
503) -> Result<(), RgbPsbtError> {
504    #[allow(clippy::result_large_err)]
505    fn visit_and_insert(
506        opid: OpId,
507        transitions: &HashMap<OpId, Transition>,
508        known_transitions: &mut SmallVec<KnownTransition>,
509        visited: &mut HashSet<OpId>,
510        visiting: &mut HashSet<OpId>,
511    ) -> Result<(), RgbPsbtError> {
512        if visited.contains(&opid) {
513            return Ok(());
514        }
515        if visiting.contains(&opid) {
516            return Err(RgbPsbtError::KnownTransitionsInconsistency);
517        }
518        if let Some(transition) = transitions.get(&opid) {
519            visiting.insert(opid);
520            for input in transition.inputs() {
521                if transitions.contains_key(&input.op) {
522                    visit_and_insert(input.op, transitions, known_transitions, visited, visiting)?;
523                }
524            }
525            visiting.remove(&opid);
526            visited.insert(opid);
527            known_transitions
528                .push(KnownTransition {
529                    opid,
530                    transition: transition.clone(),
531                })
532                .map_err(|_| {
533                    RgbPsbtError::InvalidTransitionsNumber(
534                        transition.contract_id,
535                        transitions.len(),
536                    )
537                })?;
538        }
539        Ok(())
540    }
541
542    let mut visited = HashSet::new();
543    let mut visiting = HashSet::new();
544    for &opid in transitions.keys() {
545        visit_and_insert(opid, transitions, known_transitions, &mut visited, &mut visiting)?;
546    }
547    Ok(())
548}
549
550/// Extension trait for static functions returning RGB-related proprietary keys.
551pub trait RgbPropKeyExt {
552    /// Constructs [`PSBT_OUT_MPC_MESSAGE`] proprietary key.
553    fn mpc_message(protocol_id: ProtocolId) -> Self;
554    /// Constructs [`PSBT_OUT_MPC_ENTROPY`] proprietary key.
555    fn mpc_entropy() -> Self;
556    /// Constructs [`PSBT_OUT_MPC_MIN_TREE_DEPTH`] proprietary key.
557    fn mpc_min_tree_depth() -> Self;
558    /// Constructs [`PSBT_OUT_MPC_COMMITMENT`] proprietary key.
559    fn mpc_commitment() -> Self;
560    /// Constructs [`PSBT_OUT_MPC_PROOF`] proprietary key.
561    fn mpc_proof() -> Self;
562    /// Constructs [`PSBT_OUT_OPRET_HOST`] proprietary key.
563    fn opret_host() -> Self;
564    /// Constructs [`PSBT_OUT_TAPRET_HOST`] proprietary key.
565    fn tapret_host() -> Self;
566    /// Constructs [`PSBT_OUT_OPRET_COMMITMENT`] proprietary key.
567    fn opret_commitment() -> Self;
568    /// Constructs [`PSBT_OUT_TAPRET_COMMITMENT`] proprietary key.
569    fn tapret_commitment() -> Self;
570    /// Constructs [`PSBT_OUT_TAPRET_PROOF`] proprietary key.
571    fn tapret_proof() -> Self;
572    /// Constructs [`PSBT_GLOBAL_RGB_TRANSITION`] proprietary key.
573    fn rgb_transition(opid: OpId) -> Self;
574    /// Constructs [`PSBT_GLOBAL_RGB_CLOSE_METHOD`] proprietary key.
575    fn rgb_close_method() -> Self;
576    /// Constructs [`PSBT_GLOBAL_RGB_CONSUMED_BY`] proprietary key.
577    fn rgb_consumed_by(contract_id: ContractId) -> Self;
578    /// Constructs [`PSBT_GLOBAL_RGB_TAP_HOST_CHANGE`] proprietary key.
579    fn rgb_tapret_host_on_change() -> Self;
580}
581
582pub trait RgbOutExt<P: RgbPropKeyExt> {
583    fn is_opret_host(&self) -> bool { self.proprietary_contains(&P::opret_host()) }
584
585    fn is_tapret_host(&self) -> bool { self.proprietary_contains(&P::tapret_host()) }
586
587    fn set_opret_host(&mut self) -> bool { self.proprietary_push(P::opret_host(), vec![]).is_err() }
588
589    fn set_tapret_host(&mut self) -> bool {
590        self.proprietary_push(P::tapret_host(), vec![]).is_err()
591    }
592
593    /// Returns valid tapret commitment from the [`PSBT_OUT_TAPRET_COMMITMENT`]
594    /// key, if present. If the commitment is absent or invalid, returns
595    /// [`TapretKeyError::NoCommitment`].
596    fn tapret_commitment(&self) -> Result<TapretCommitment, TapretKeyError> {
597        let data = self
598            .proprietary_get(&P::tapret_commitment())
599            .ok_or(TapretKeyError::NoCommitment)?;
600        TapretCommitment::from_strict_serialized::<U16>(
601            Confined::try_from(data.to_vec()).map_err(|_| TapretKeyError::InvalidCommitment)?,
602        )
603        .map_err(|_| TapretKeyError::InvalidCommitment)
604    }
605
606    /// Assigns value of the opreturn commitment to this PSBT output, by
607    /// adding [`PSBT_OUT_OPRET_COMMITMENT`] proprietary key containing the
608    /// 32-byte commitment as its value. Also modifies the output script and removes
609    /// [`PSBT_OUT_OPRET_HOST`] key.
610    ///
611    /// Opret commitment can be set only once.
612    ///
613    /// Errors with [`OpretKeyError::OutputAlreadyHasCommitment`] if the
614    /// commitment is already present in the output.
615    ///
616    /// Errors if output script is not OP_RETURN script or opret commitments are not
617    /// enabled for this output.
618    fn opret_commit(&mut self, commitment: mpc::Commitment) -> Result<(), OpretKeyError> {
619        if !self.is_opret_host() {
620            return Err(OpretKeyError::OpretProhibited);
621        }
622        self.proprietary_push(P::opret_commitment(), commitment.to_vec())
623            .map_err(|_| OpretKeyError::OutputAlreadyHasCommitment)?;
624        self.proprietary_remove(&P::opret_host());
625        Ok(())
626    }
627
628    fn get_internal_pk(&self) -> Option<UntweakedPublicKey>;
629
630    fn is_tap_tree_empty(&self) -> bool;
631
632    fn set_tap_tree(&mut self, script_commitment: &ScriptBuf);
633
634    /// Assigns value of the tapreturn commitment to this PSBT output, by
635    /// adding [`PSBT_OUT_TAPRET_COMMITMENT`] and [`PSBT_OUT_TAPRET_PROOF`]
636    /// proprietary keys containing the 32-byte commitment as its proof.
637    ///
638    /// Errors with [`TapretKeyError::OutputAlreadyHasCommitment`] if the
639    /// commitment is already present in the output, and with
640    /// [`TapretKeyError::TapretProhibited`] if tapret commitments are not
641    /// enabled for this output.
642    fn tapret_commit(
643        &mut self,
644        commitment: mpc::Commitment,
645    ) -> Result<TapretProof, TapretKeyError> {
646        if !self.is_tapret_host() {
647            return Err(TapretKeyError::TapretProhibited);
648        }
649
650        // TODO #10: support non-empty tap trees
651        if !self.is_tap_tree_empty() {
652            return Err(TapretKeyError::TapTreeNonEmpty);
653        }
654        let nonce = 0;
655        let tapret_commitment = &TapretCommitment::with(commitment, nonce);
656        let script_commitment = tapret_commitment.commit();
657
658        self.set_tap_tree(&script_commitment);
659
660        let internal_pk = self
661            .get_internal_pk()
662            .ok_or(TapretKeyError::NoInternalKey)?;
663        let tapret_proof = TapretProof {
664            path_proof: TapretPathProof::root(nonce),
665            internal_pk,
666        };
667
668        let tapret_proof_serialized = tapret_proof
669            .to_strict_serialized::<U16>()
670            .expect("tapret proof too long")
671            .to_vec();
672        self.proprietary_push(P::tapret_commitment(), tapret_commitment.to_vec())
673            .and_then(|_| self.proprietary_push(P::tapret_proof(), tapret_proof_serialized))
674            .map_err(|_| TapretKeyError::OutputAlreadyHasCommitment)?;
675        self.proprietary_remove(&P::tapret_host());
676
677        Ok(tapret_proof)
678    }
679
680    fn bip32_derivation_terminals(&self) -> Vec<Terminal>;
681
682    fn tap_bip32_derivation_terminals(&self) -> Vec<Terminal>;
683
684    fn terminal_derivation(&self) -> Option<Terminal> {
685        let terminal = self
686            .bip32_derivation_terminals()
687            .into_iter()
688            .chain(self.tap_bip32_derivation_terminals())
689            .collect::<BTreeSet<_>>();
690        if terminal.len() != 1 {
691            return None;
692        }
693        terminal.first().copied()
694    }
695
696    fn proprietary_mpc_messages<'a>(&'a self) -> impl Iterator<Item = (&'a [u8], &'a [u8])> + 'a;
697
698    /// Returns [`mpc::MessageMap`] constructed from the proprietary key data.
699    fn mpc_message_map(&self) -> Result<mpc::MessageMap, MpcPsbtError> {
700        let map = self
701            .proprietary_mpc_messages()
702            .map(|(protocol_id_bytes, message_bytes)| {
703                Ok((
704                    ProtocolId::copy_from_slice(protocol_id_bytes)?,
705                    Message::copy_from_slice(message_bytes)?,
706                ))
707            })
708            .collect::<Result<BTreeMap<_, _>, MpcPsbtError>>()?;
709        Confined::try_from(map).map_err(MpcPsbtError::from)
710    }
711
712    /// Returns a valid LNPBP-4 entropy value, if present.
713    ///
714    /// We do not error on invalid data in order to support future update of
715    /// this proprietary key to a standard one. In this case, the invalid
716    /// data will be filtered at the moment of PSBT deserialization and this
717    /// function will return `None` only in situations when the key is absent.
718    fn mpc_entropy(&self) -> Option<u64> {
719        let key = P::mpc_entropy();
720        let data = self.proprietary_get(&key)?;
721        if data.len() != 8 {
722            return None;
723        }
724        let mut buf = [0u8; 8];
725        buf.copy_from_slice(data);
726        Some(u64::from_le_bytes(buf))
727    }
728
729    fn mpc_min_tree_depth(&self) -> Option<u8> {
730        let key = P::mpc_min_tree_depth();
731        let data = self.proprietary_get(&key)?;
732        if data.len() != 1 {
733            return None;
734        }
735        Some(data[0])
736    }
737
738    /// Sets MPC entropy value.
739    ///
740    /// # Returns
741    ///
742    /// `true`, if the entropy was set successfully, `false` if this entropy
743    /// value was already set.
744    ///
745    /// # Errors
746    ///
747    /// If the entropy was already set with a different value than the provided
748    /// one.
749    fn set_mpc_entropy(&mut self, entropy: u64) -> Result<bool, MpcPsbtError> {
750        if self.proprietary_contains(&P::mpc_commitment()) {
751            return Err(MpcPsbtError::Finalized);
752        }
753        let key = P::mpc_entropy();
754        let val = entropy.to_le_bytes().to_vec();
755        if let Some(v) = self.proprietary_get(&key) {
756            if v != val {
757                return Err(MpcPsbtError::InvalidKeyValue);
758            }
759            return Ok(false);
760        }
761        self.proprietary_push(key, val)?;
762        Ok(true)
763    }
764
765    /// Sets MPC [`Message`] for the given [`ProtocolId`].
766    ///
767    /// # Returns
768    ///
769    /// `true`, if the message was set successfully, `false` if this message was
770    /// already present for this protocol.
771    ///
772    /// # Errors
773    ///
774    /// If the key for the given [`ProtocolId`] is already present and the
775    /// message is different.
776    fn set_mpc_message(
777        &mut self,
778        protocol_id: ProtocolId,
779        message: Message,
780    ) -> Result<bool, MpcPsbtError> {
781        if self.proprietary_contains(&P::mpc_commitment()) {
782            return Err(MpcPsbtError::Finalized);
783        }
784        let key = P::mpc_message(protocol_id);
785        let val = message.to_vec();
786        if let Some(v) = self.proprietary_get(&key) {
787            if v != val {
788                return Err(MpcPsbtError::InvalidKeyValue);
789            }
790            return Ok(false);
791        }
792        self.proprietary_push(key, val)?;
793        Ok(true)
794    }
795
796    fn mpc_commit(&mut self) -> Result<(Commitment, mpc::MerkleBlock), MpcPsbtError> {
797        let messages = self.mpc_message_map()?;
798        let min_depth = self
799            .mpc_min_tree_depth()
800            .map(u5::with)
801            .unwrap_or(MPC_MINIMAL_DEPTH);
802        let source = mpc::MultiSource {
803            min_depth,
804            messages,
805            static_entropy: self.mpc_entropy(),
806        };
807        let merkle_tree = mpc::MerkleTree::try_commit(&source)?;
808        let entropy = merkle_tree.entropy();
809        self.set_mpc_entropy(entropy)?;
810        let commitment = merkle_tree.commit_id();
811        let mpc_proof = mpc::MerkleBlock::from(merkle_tree);
812        let mpc_proof_serialized = mpc_proof.to_strict_serialized::<U32>().expect("max length");
813
814        self.proprietary_push(P::mpc_commitment(), commitment.to_vec())
815            .and_then(|_| {
816                self.proprietary_push(P::mpc_proof(), mpc_proof_serialized.to_unconfined())
817            })
818            .map_err(|_| MpcPsbtError::OutputAlreadyHasCommitment)?;
819
820        Ok((commitment, mpc_proof))
821    }
822
823    fn proprietary_push(&mut self, key: P, value: Vec<u8>) -> Result<(), MpcPsbtError> {
824        if self.proprietary_contains(&key) {
825            return Err(MpcPsbtError::KeyAlreadyPresent);
826        }
827        self.proprietary_insert(key, value);
828        Ok(())
829    }
830
831    fn proprietary_insert(&mut self, key: P, value: Vec<u8>);
832
833    fn proprietary_contains_key(&self, key: &P) -> bool;
834
835    fn proprietary_contains(&self, key: &P) -> bool { self.proprietary_contains_key(key) }
836
837    fn proprietary_get_value(&self, key: &P) -> Option<&[u8]>;
838
839    fn proprietary_get(&self, key: &P) -> Option<&[u8]> { self.proprietary_get_value(key) }
840
841    fn proprietary_remove(&mut self, key: &P);
842}
843
844#[allow(clippy::result_large_err)]
845pub trait RgbPsbtExt<P: RgbPropKeyExt, O: RgbOutExt<P>> {
846    fn get_txid(&self) -> Txid;
847
848    fn modifiable_outputs(&self) -> bool;
849
850    fn set_as_unmodifiable(&mut self);
851
852    fn unsigned_tx(&self) -> Transaction;
853
854    fn set_opret_host(&mut self) -> bool;
855
856    fn dbc_output<D: DbcPsbtProof>(&self) -> Option<&O>;
857
858    fn dbc_output_mut<D: DbcPsbtProof>(&mut self) -> Option<(usize, &mut O)>;
859
860    fn dbc_commit<D: DbcPsbtProof>(&mut self) -> Result<(mpc::MerkleBlock, D), DbcPsbtError>
861    where Self: std::marker::Sized {
862        if self.modifiable_outputs() {
863            return Err(DbcPsbtError::TxOutputsModifiable);
864        }
865
866        D::dbc_commit(self)
867    }
868
869    fn set_opret_commitment(&mut self, idx: usize);
870
871    fn set_tapret_commitment(&mut self, idx: usize);
872
873    fn rgb_embed(&mut self, batch: Batch) -> Result<(), EmbedError> {
874        for transition in batch {
875            self.push_rgb_transition(transition)?;
876        }
877        Ok(())
878    }
879
880    fn rgb_commit(&mut self) -> Result<Fascia, CommitError>
881    where Self: std::marker::Sized {
882        // Convert RGB data to MPCs? Or should we do it at the moment we add them... No,
883        // since we may require more DBC methods with each additional state transition
884        let bundles = self.rgb_bundles_to_mpc()?;
885        // DBC commitment for the correct close method
886        let close_method = self
887            .rgb_close_method()?
888            .ok_or(RgbPsbtError::NoCloseMethod)?;
889        let (merkle_block, dbc_proof) = match close_method {
890            CloseMethod::TapretFirst => self
891                .dbc_commit::<TapretProof>()
892                .map(|(mb, proof)| (mb, proof.into()))?,
893            CloseMethod::OpretFirst => self
894                .dbc_commit::<OpretProof>()
895                .map(|(mb, proof)| (mb, proof.into()))?,
896        };
897        let witness = PubWitness::with(self.unsigned_tx());
898        let seal_witness = SealWitness::new(witness, merkle_block, dbc_proof);
899        Ok(Fascia {
900            seal_witness,
901            bundles,
902        })
903    }
904
905    fn proprietary_rgb_contract_consumer_keys<'a>(&'a self) -> impl Iterator<Item = &'a [u8]> + 'a;
906
907    fn rgb_contract_ids(&self) -> Result<BTreeSet<ContractId>, FromSliceError> {
908        self.proprietary_rgb_contract_consumer_keys()
909            .map(ContractId::copy_from_slice)
910            .collect()
911    }
912
913    fn rgb_contract_consumers(
914        &self,
915        contract_id: ContractId,
916    ) -> Result<BTreeMap<Opout, OpId>, RgbPsbtError> {
917        let Some(data) = self.proprietary_get(&P::rgb_consumed_by(contract_id)) else {
918            return Ok(BTreeMap::new());
919        };
920        Ok(OpoutAndOpids::deserialize(data)?.into_inner())
921    }
922
923    fn rgb_transition(&self, opid: OpId) -> Result<Option<Transition>, RgbPsbtError> {
924        let Some(data) = self.proprietary_get(&P::rgb_transition(opid)) else {
925            return Ok(None);
926        };
927        let data = Confined::try_from_iter(data.iter().copied())?;
928        let transition = Transition::from_strict_serialized::<U24>(data)?;
929        Ok(Some(transition))
930    }
931
932    fn rgb_close_method(&self) -> Result<Option<CloseMethod>, RgbPsbtError> {
933        let Some(m) = self.proprietary_get(&P::rgb_close_method()) else {
934            return Ok(None);
935        };
936        if m.len() == 1 {
937            if let Ok(method) = CloseMethod::try_from(m[0]) {
938                return Ok(Some(method));
939            }
940        }
941        Err(RgbPsbtError::InvalidCloseMethod)
942    }
943
944    fn rgb_tapret_host_on_change(&self) -> bool {
945        self.proprietary_contains(&P::rgb_tapret_host_on_change())
946    }
947
948    fn set_rgb_close_method(&mut self, close_method: CloseMethod) {
949        let _ = self.proprietary_push(P::rgb_close_method(), vec![close_method as u8]);
950    }
951
952    fn set_rgb_tapret_host_on_change(&mut self) {
953        let _ = self.proprietary_push(P::rgb_tapret_host_on_change(), vec![]);
954    }
955
956    /// Adds information about an RGB input allocation and the ID of the state
957    /// transition spending it.
958    ///
959    /// # Returns
960    ///
961    /// `Ok(false)`, if the same opout under the same contract was already
962    /// present with the provided state transition ID. `Ok(true)`, if the
963    /// opout was successfully added.
964    ///
965    /// # Errors
966    ///
967    /// If the [`Opout`] already exists but it's referencing a different [`OpId`].
968    fn set_rgb_contract_consumer(
969        &mut self,
970        contract_id: ContractId,
971        opout: Opout,
972        opid: OpId,
973    ) -> Result<bool, RgbPsbtError> {
974        let key = P::rgb_consumed_by(contract_id);
975        if let Some(existing_data) = self.proprietary_get(&key) {
976            let mut items = OpoutAndOpids::deserialize(existing_data)?;
977            if let Some(existing_opid) = items.get(&opout) {
978                if *existing_opid != opid {
979                    return Err(RgbPsbtError::DoubleSpend);
980                }
981                return Ok(false);
982            }
983            items.insert(opout, opid);
984            self.proprietary_insert(key, items.serialize());
985        } else {
986            let items = OpoutAndOpids::new(bmap![opout => opid]);
987            let _ = self.proprietary_push(key, items.serialize());
988        }
989        Ok(true)
990    }
991
992    fn push_rgb_transition(&mut self, mut transition: Transition) -> Result<bool, RgbPsbtError> {
993        let opid = transition.id();
994
995        let prev_transition = self.rgb_transition(opid)?;
996        if let Some(ref prev_transition) = prev_transition {
997            transition.merge_reveal(prev_transition).map_err(|err| {
998                RgbPsbtError::UnrelatedTransitions(prev_transition.id(), opid, err)
999            })?;
1000        }
1001        let serialized_transition = transition
1002            .to_strict_serialized::<U24>()
1003            .map_err(|_| RgbPsbtError::TransitionTooBig(opid))?;
1004
1005        // Since we update transition it's ok to ignore the fact that it previously
1006        // existed
1007        let _ = self.proprietary_push(P::rgb_transition(opid), serialized_transition.release());
1008
1009        for opout in transition.inputs() {
1010            self.set_rgb_contract_consumer(transition.contract_id, opout, opid)?;
1011        }
1012
1013        Ok(prev_transition.is_none())
1014    }
1015
1016    fn rgb_bundles(&self) -> Result<BTreeMap<ContractId, TransitionBundle>, RgbPsbtError> {
1017        let mut map = BTreeMap::new();
1018        for contract_id in self.rgb_contract_ids()? {
1019            let contract_consumers = self.rgb_contract_consumers(contract_id)?;
1020            if contract_consumers.is_empty() {
1021                return Err(RgbPsbtError::NoContractConsumers);
1022            }
1023            let inputs_len = contract_consumers.len();
1024            let input_map = NonEmptyOrdMap::try_from(contract_consumers)
1025                .map_err(|_| RgbPsbtError::InvalidInputsNumber(inputs_len))?;
1026            let mut transitions_map: HashMap<OpId, Transition> = HashMap::new();
1027            for opid in input_map.values() {
1028                if let Some(transition) = self.rgb_transition(*opid)? {
1029                    transitions_map.insert(*opid, transition);
1030                }
1031            }
1032            let known_transitions_len = transitions_map.values().len();
1033            let mut known_transitions: SmallVec<KnownTransition> =
1034                SmallVec::with_capacity(known_transitions_len);
1035            insert_transitions_sorted(&transitions_map, &mut known_transitions)?;
1036
1037            let bundle = TransitionBundle {
1038                input_map,
1039                known_transitions: Confined::try_from(known_transitions.release()).map_err(
1040                    |_| RgbPsbtError::InvalidTransitionsNumber(contract_id, known_transitions_len),
1041                )?,
1042            };
1043            map.insert(contract_id, bundle);
1044        }
1045        Ok(map)
1046    }
1047
1048    fn outputs_iter_mut<'a>(&'a mut self) -> impl Iterator<Item = &'a mut O>
1049    where O: 'a;
1050
1051    fn rgb_bundles_to_mpc(
1052        &mut self,
1053    ) -> Result<Confined<BTreeMap<ContractId, TransitionBundle>, 1, U24>, RgbPsbtError> {
1054        let bundles = self.rgb_bundles()?;
1055
1056        let close_method = self
1057            .rgb_close_method()?
1058            .ok_or(RgbPsbtError::NoCloseMethod)?;
1059
1060        let host = self
1061            .outputs_iter_mut()
1062            .find(|output| match close_method {
1063                CloseMethod::OpretFirst => output.is_opret_host(),
1064                CloseMethod::TapretFirst => output.is_tapret_host(),
1065            })
1066            .ok_or(RgbPsbtError::NoHostOutput(close_method))?;
1067
1068        for (contract_id, bundle) in &bundles {
1069            let protocol_id = mpc::ProtocolId::from(*contract_id);
1070            let message = mpc::Message::from(bundle.bundle_id());
1071            host.set_mpc_message(protocol_id, message)?;
1072        }
1073
1074        let map = Confined::try_from(bundles).map_err(|_| RgbPsbtError::NoContracts)?;
1075
1076        Ok(map)
1077    }
1078
1079    fn proprietary_insert(&mut self, key: P, value: Vec<u8>);
1080
1081    fn proprietary_push(&mut self, key: P, value: Vec<u8>) -> Result<(), MpcPsbtError> {
1082        if self.proprietary_contains(&key) {
1083            return Err(MpcPsbtError::KeyAlreadyPresent);
1084        }
1085        self.proprietary_insert(key, value);
1086        Ok(())
1087    }
1088
1089    fn proprietary_contains_key(&self, key: &P) -> bool;
1090
1091    fn proprietary_contains(&self, key: &P) -> bool { self.proprietary_contains_key(key) }
1092
1093    fn proprietary_get_value(&self, key: &P) -> Option<&[u8]>;
1094
1095    fn proprietary_get(&self, key: &P) -> Option<&[u8]> { self.proprietary_get_value(key) }
1096}