rgb/
descriptor.rs

1// RGB smart contracts for Bitcoin & Lightning
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::collections::{BTreeMap, BTreeSet};
23use std::fmt::{self, Display, Formatter};
24use std::ops::Range;
25
26use bitcoin::bip32::{ChildNumber, ExtendedPubKey};
27use bitcoin::secp256k1::SECP256K1;
28use bitcoin::ScriptBuf;
29use bp::dbc::tapret::{TapretCommitment, TapretPathProof, TapretProof};
30use bp::{ScriptPubkey, TapNodeHash};
31use commit_verify::ConvolveCommit;
32
33#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)]
34#[display("*/{app}/{index}")]
35#[derive(Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct TerminalPath {
38    pub app: u32,
39    pub index: u32,
40}
41
42#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
43pub struct DeriveInfo {
44    pub terminal: TerminalPath,
45    pub tweak: Option<TapretCommitment>,
46}
47
48impl Display for DeriveInfo {
49    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
50        Display::fmt(&self.terminal, f)?;
51        if let Some(tweak) = &self.tweak {
52            write!(f, "%{tweak}")?;
53        }
54        Ok(())
55    }
56}
57
58impl DeriveInfo {
59    pub fn with(app: u32, index: u32, tweak: Option<TapretCommitment>) -> Self {
60        DeriveInfo {
61            terminal: TerminalPath { app, index },
62            tweak,
63        }
64    }
65}
66
67#[derive(Clone, PartialEq, Eq, Hash, Debug)]
68#[derive(Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct Tapret {
71    pub xpub: ExtendedPubKey,
72    // pub script: Option<>,
73    pub taprets: BTreeMap<TerminalPath, BTreeSet<TapretCommitment>>,
74}
75
76impl Display for Tapret {
77    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
78        f.write_str("tapret(")?;
79        Display::fmt(&self.xpub, f)?;
80        let mut first = true;
81        if f.alternate() {
82            for (terminal, taprets) in &self.taprets {
83                if first {
84                    f.write_str(",")?;
85                    first = false;
86                } else {
87                    f.write_str(" ")?;
88                }
89                Display::fmt(terminal, f)?;
90                for tapret in taprets {
91                    f.write_str("&")?;
92                    Display::fmt(tapret, f)?;
93                }
94            }
95        } else {
96            f.write_str(", ...")?;
97        }
98        f.write_str(")")
99    }
100}
101
102#[derive(Clone, PartialEq, Eq, Hash, Debug, Display)]
103#[display(inner)]
104#[derive(Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub enum RgbDescr {
107    Tapret(Tapret),
108}
109
110pub trait SpkDescriptor {
111    // TODO: Replace index with UnhardenedIndex
112    fn derive(&self, app: u32, indexes: Range<u32>) -> BTreeMap<DeriveInfo, ScriptBuf>;
113}
114
115impl SpkDescriptor for Tapret {
116    fn derive(&self, app: u32, indexes: Range<u32>) -> BTreeMap<DeriveInfo, ScriptBuf> {
117        let mut spks = BTreeMap::new();
118
119        for index in indexes {
120            let key = self
121                .xpub
122                .derive_pub(SECP256K1, &[
123                    ChildNumber::from_normal_idx(app)
124                        .expect("application index must be unhardened"),
125                    ChildNumber::from_normal_idx(index)
126                        .expect("derivation index must be unhardened"),
127                ])
128                .expect("unhardened derivation");
129
130            let xonly = key.to_x_only_pub();
131            spks.insert(
132                DeriveInfo::with(app, index, None),
133                ScriptBuf::new_v1_p2tr(SECP256K1, xonly, None),
134            );
135            for tweak in self
136                .taprets
137                .get(&TerminalPath { app, index })
138                .into_iter()
139                .flatten()
140            {
141                let script = ScriptPubkey::p2tr(xonly.into(), None::<TapNodeHash>);
142                let proof = TapretProof {
143                    path_proof: TapretPathProof::root(tweak.nonce),
144                    internal_pk: xonly.into(),
145                };
146                let (spk, _) = script
147                    .convolve_commit(&proof, &tweak.mpc)
148                    .expect("malicious tapret value - an inverse of a key");
149                spks.insert(
150                    DeriveInfo::with(app, index, Some(tweak.clone())),
151                    ScriptBuf::from_bytes(spk.to_inner()),
152                );
153            }
154        }
155        spks
156    }
157}
158
159impl SpkDescriptor for RgbDescr {
160    fn derive(&self, app: u32, indexes: Range<u32>) -> BTreeMap<DeriveInfo, ScriptBuf> {
161        match self {
162            RgbDescr::Tapret(tapret) => tapret.derive(app, indexes),
163        }
164    }
165}