psrgbt/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Partially signed bitcoin transaction RGB extensions
//
// SPDX-License-Identifier: Apache-2.0
//
// Written in 2020-2023 by
//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
//
// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[macro_use]
extern crate amplify;

mod rgb;

use bp::dbc::opret::OpretProof;
use bp::dbc::tapret::TapretProof;
pub use bpstd::psbt::*;
pub use rgb::*;
use rgbstd::containers::{AnchorSet, Batch, CloseMethodSet, Fascia, PubWitness, XPubWitness};
use rgbstd::XChain;

pub use self::rgb::{
    ProprietaryKeyRgb, RgbExt, RgbInExt, RgbOutExt, RgbPsbtError, PSBT_GLOBAL_RGB_TRANSITION,
    PSBT_IN_RGB_CONSUMED_BY, PSBT_OUT_RGB_VELOCITY_HINT, PSBT_RGB_PREFIX,
};

#[derive(Clone, Eq, PartialEq, Debug, Display, Error)]
#[display(doc_comments)]
pub enum EmbedError {
    /// provided transaction batch references inputs which are absent from the
    /// PSBT. Possible it was created for a different PSBT.
    AbsentInputs,

    /// the provided PSBT is invalid since it doublespends on some of its
    /// inputs.
    PsbtRepeatedInputs,
}

#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
#[display(inner)]
pub enum CommitError {
    #[from]
    Rgb(RgbPsbtError),

    #[from]
    Dbc(DbcPsbtError),
}

#[derive(Clone, Eq, PartialEq, Debug, Display, Error)]
#[display(doc_comments)]
pub enum ExtractError {}

// TODO: Batch must be homomorphic by the outpoint type (chain)

pub trait RgbPsbt {
    fn rgb_embed(&mut self, batch: Batch) -> Result<(), EmbedError>;
    #[allow(clippy::result_large_err)]
    fn rgb_commit(&mut self) -> Result<Fascia, CommitError>;
    fn rgb_extract(&self) -> Result<Fascia, ExtractError>;
}

impl RgbPsbt for Psbt {
    fn rgb_embed(&mut self, batch: Batch) -> Result<(), EmbedError> {
        for info in batch {
            let contract_id = info.transition.contract_id;
            let mut inputs = info.inputs.release();
            for input in self.inputs_mut() {
                if inputs.remove(&XChain::Bitcoin(input.prevout().outpoint())) {
                    input
                        .set_rgb_consumer(contract_id, info.id)
                        .map_err(|_| EmbedError::PsbtRepeatedInputs)?;
                }
            }
            if !inputs.is_empty() {
                return Err(EmbedError::AbsentInputs);
            }
            self.push_rgb_transition(info.transition, info.method)
                .expect("transitions are unique since they are in BTreeMap indexed by opid");
        }
        Ok(())
    }

    fn rgb_commit(&mut self) -> Result<Fascia, CommitError> {
        // Convert RGB data to MPCs? Or should we do it at the moment we add them... No,
        // since we may require more DBC methods with each additional state transition
        let bundles = self.rgb_bundles_to_mpc()?;
        // DBC commitment for the required methods
        let methods = bundles
            .values()
            .flat_map(|b| b.iter())
            .map(|b| CloseMethodSet::from(b.close_method))
            .reduce(|methods, method| methods | method)
            .ok_or(RgbPsbtError::NoContracts)?;
        let (mut tapret_anchor, mut opret_anchor) = (None, None);
        if methods.has_tapret_first() {
            tapret_anchor = Some(self.dbc_commit::<TapretProof>()?);
        }
        if methods.has_opret_first() {
            opret_anchor = Some(self.dbc_commit::<OpretProof>()?);
        }
        let anchor = match (tapret_anchor, opret_anchor) {
            (None, None) => return Err(RgbPsbtError::NoContracts.into()),
            (Some(tapret), None) => AnchorSet::Tapret(tapret),
            (None, Some(opret)) => AnchorSet::Opret(opret),
            (Some(tapret), Some(opret)) => AnchorSet::Double { tapret, opret },
        };
        // TODO: Use signed transaction here!
        let witness = PubWitness::with(self.to_unsigned_tx().into());
        Ok(Fascia {
            witness: XPubWitness::Bitcoin(witness),
            anchor,
            bundles,
        })
    }

    fn rgb_extract(&self) -> Result<Fascia, ExtractError> {
        todo!("implement RGB PSBT fascia extraction for multi-party protocols")
    }
}