rgbdescr/
lib.rs

1// Wallet Library for RGB smart contracts
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2019-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association, Switzerland.
9// Copyright (C) 2024-2025 LNP/BP Laboratories,
10//                         Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
11// Copyright (C) 2025 RGB Consortium, Switzerland.
12// Copyright (C) 2019-2025 Dr Maxim Orlovsky.
13// All rights under the above copyrights are reserved.
14//
15// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
16// in compliance with the License. You may obtain a copy of the License at
17//
18//        http://www.apache.org/licenses/LICENSE-2.0
19//
20// Unless required by applicable law or agreed to in writing, software distributed under the License
21// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
22// or implied. See the License for the specific language governing permissions and limitations under
23// the License.
24
25#![cfg_attr(docsrs, feature(doc_auto_cfg))]
26// #![cfg_attr(not(feature = "std"), no_std)]
27
28extern crate alloc;
29#[macro_use]
30extern crate amplify;
31#[cfg(feature = "serde")]
32#[macro_use]
33extern crate serde;
34extern crate core;
35
36use alloc::collections::{BTreeMap, BTreeSet};
37use core::fmt::{self, Display, Formatter};
38
39use amplify::{Bytes32, Wrapper, WrapperMut};
40use bpstd::dbc::tapret::TapretCommitment;
41use bpstd::seals::WTxoSeal;
42use bpstd::{
43    ControlBlock, Derive, DeriveCompr, DeriveKey, DeriveLegacy, DeriveSet, DeriveXOnly,
44    DerivedScript, Descriptor, KeyOrigin, Keychain, LegacyKeySig, LegacyPk, NormalIndex,
45    RedeemScript, SigScript, SpkClass, StdDescr, TapDerivation, TapScript, TapTree, TaprootKeySig,
46    Terminal, Tr, TrKey, Witness, WitnessScript, XOnlyPk, XpubAccount, XpubDerivable,
47};
48use commit_verify::CommitVerify;
49use indexmap::IndexMap;
50
51pub trait DescriptorRgb<K = XpubDerivable, V = ()>: Descriptor<K, V> {
52    fn add_seal(&self, seal: WTxoSeal);
53}
54
55#[derive(Wrapper, WrapperMut, Clone, Eq, PartialEq, Debug, Default, From)]
56#[wrapper(Deref)]
57#[wrapper_mut(DerefMut)]
58#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
59pub struct SealDescr(BTreeSet<WTxoSeal>);
60
61impl Display for SealDescr {
62    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
63        f.write_str("seals(")?;
64        let mut iter = self.0.iter().peekable();
65        while let Some(seal) = iter.next() {
66            Display::fmt(seal, f)?;
67            if iter.peek().is_some() {
68                f.write_str(",")?;
69            }
70        }
71        f.write_str(")")
72    }
73}
74
75#[derive(Wrapper, WrapperMut, Clone, PartialEq, Eq, Debug, Default, From)]
76#[wrapper(Deref)]
77#[wrapper_mut(DerefMut)]
78#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
79pub struct TapretWeaks(BTreeMap<Terminal, BTreeSet<TapretCommitment>>);
80
81impl Display for TapretWeaks {
82    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
83        f.write_str("tweaks(")?;
84        let mut iter1 = self.iter().peekable();
85        while let Some((term, tweaks)) = iter1.next() {
86            write!(f, "{term}=")?;
87            let mut iter2 = tweaks.iter().peekable();
88            while let Some(tweak) = iter2.next() {
89                write!(f, "{tweak}")?;
90                if iter2.peek().is_some() {
91                    f.write_str(",")?;
92                }
93            }
94            if iter1.peek().is_some() {
95                f.write_str(";")?;
96            }
97        }
98        f.write_str(")")
99    }
100}
101
102#[derive(Clone, Debug, Display, From)]
103#[cfg_attr(
104    feature = "serde",
105    derive(Serialize, Deserialize),
106    serde(
107        rename_all = "camelCase",
108        bound(
109            serialize = "K::Legacy: serde::Serialize, K::Compr: serde::Serialize, K::XOnly: \
110                         serde::Serialize",
111            deserialize = "K::Legacy: serde::Deserialize<'de>, K::Compr: serde::Deserialize<'de>, \
112                           K::XOnly: serde::Deserialize<'de>"
113        )
114    )
115)]
116enum RgbDeriver<K: DeriveSet = XpubDerivable> {
117    #[from]
118    #[display(inner)]
119    OpretOnly(StdDescr<K>),
120
121    #[display("{tr},{tweaks}")]
122    Universal {
123        tr: Tr<K::XOnly>,
124        tweaks: TapretWeaks,
125    },
126}
127
128impl<K: DeriveSet> Derive<DerivedScript> for RgbDeriver<K> {
129    fn default_keychain(&self) -> Keychain {
130        match self {
131            RgbDeriver::OpretOnly(d) => d.default_keychain(),
132            RgbDeriver::Universal { tr, tweaks: _ } => tr.default_keychain(),
133        }
134    }
135
136    fn keychains(&self) -> BTreeSet<Keychain> {
137        match self {
138            RgbDeriver::OpretOnly(d) => d.keychains(),
139            RgbDeriver::Universal { tr, tweaks: _ } => tr.keychains(),
140        }
141    }
142
143    fn derive(
144        &self,
145        keychain: impl Into<Keychain>,
146        index: impl Into<NormalIndex>,
147    ) -> impl Iterator<Item = DerivedScript> {
148        match self {
149            RgbDeriver::OpretOnly(d) => d.derive(keychain, index).collect::<Vec<_>>().into_iter(),
150            RgbDeriver::Universal { tr, tweaks } => {
151                let keychain = keychain.into();
152                let index = index.into();
153                let terminal = Terminal::new(keychain, index);
154                let mut vec = Vec::with_capacity(tweaks.0.len());
155                for internal_key in tr.as_internal_key().derive(keychain, index) {
156                    vec.push(DerivedScript::TaprootKeyOnly(internal_key.into()));
157                    for tweak in tweaks.get(&terminal).into_iter().flatten() {
158                        let script_commitment = TapScript::commit(tweak);
159                        let tap_tree = TapTree::with_single_leaf(script_commitment);
160                        let script = DerivedScript::TaprootScript(internal_key.into(), tap_tree);
161                        vec.push(script);
162                    }
163                }
164                vec.into_iter()
165            }
166        }
167    }
168}
169
170impl<K: DeriveSet<Legacy = K, Compr = K, XOnly = K> + DeriveLegacy + DeriveCompr + DeriveXOnly>
171    Descriptor<K> for RgbDeriver<K>
172{
173    fn class(&self) -> SpkClass {
174        match self {
175            RgbDeriver::OpretOnly(d) => d.class(),
176            RgbDeriver::Universal { tr, tweaks: _ } => tr.class(),
177        }
178    }
179    fn keys<'a>(&'a self) -> impl Iterator<Item = &'a K>
180    where K: 'a {
181        match self {
182            RgbDeriver::OpretOnly(d) => d.keys().collect::<Vec<_>>().into_iter(),
183            RgbDeriver::Universal { tr, tweaks: _ } => tr.keys().collect::<Vec<_>>().into_iter(),
184        }
185    }
186    fn vars<'a>(&'a self) -> impl Iterator<Item = &'a ()>
187    where (): 'a {
188        match self {
189            RgbDeriver::OpretOnly(d) => d.vars().collect::<Vec<_>>().into_iter(),
190            RgbDeriver::Universal { tr, tweaks: _ } => tr.vars().collect::<Vec<_>>().into_iter(),
191        }
192    }
193    fn xpubs(&self) -> impl Iterator<Item = &XpubAccount> {
194        match self {
195            RgbDeriver::OpretOnly(d) => d.xpubs().collect::<Vec<_>>().into_iter(),
196            RgbDeriver::Universal { tr, tweaks: _ } => tr.xpubs().collect::<Vec<_>>().into_iter(),
197        }
198    }
199    fn legacy_keyset(&self, terminal: Terminal) -> IndexMap<LegacyPk, KeyOrigin> {
200        match self {
201            RgbDeriver::OpretOnly(d) => d.legacy_keyset(terminal),
202            RgbDeriver::Universal { tr, tweaks: _ } => tr.legacy_keyset(terminal),
203        }
204    }
205    fn xonly_keyset(&self, terminal: Terminal) -> IndexMap<XOnlyPk, TapDerivation> {
206        match self {
207            RgbDeriver::OpretOnly(d) => d.xonly_keyset(terminal),
208            RgbDeriver::Universal { tr, tweaks: _ } => tr.xonly_keyset(terminal),
209        }
210    }
211    fn legacy_witness(
212        &self,
213        keysigs: IndexMap<&KeyOrigin, LegacyKeySig>,
214        redeem_script: Option<RedeemScript>,
215        witness_script: Option<WitnessScript>,
216    ) -> Option<(SigScript, Option<Witness>)> {
217        match self {
218            RgbDeriver::OpretOnly(d) => d.legacy_witness(keysigs, redeem_script, witness_script),
219            RgbDeriver::Universal { tr, tweaks: _ } => {
220                tr.legacy_witness(keysigs, redeem_script, witness_script)
221            }
222        }
223    }
224    fn taproot_witness(
225        &self,
226        cb: Option<&ControlBlock>,
227        keysigs: IndexMap<&KeyOrigin, TaprootKeySig>,
228    ) -> Option<Witness> {
229        match self {
230            RgbDeriver::OpretOnly(d) => d.taproot_witness(cb, keysigs),
231            RgbDeriver::Universal { tr, tweaks: _ } => tr.taproot_witness(cb, keysigs),
232        }
233    }
234}
235
236#[derive(Clone, Debug, Display)]
237#[display("rgb({deriver},{seals},noise({noise:x}))")]
238#[cfg_attr(
239    feature = "serde",
240    derive(Serialize, Deserialize),
241    serde(
242        rename_all = "camelCase",
243        bound(
244            serialize = "K::Legacy: serde::Serialize, K::Compr: serde::Serialize, K::XOnly: \
245                         serde::Serialize",
246            deserialize = "K::Legacy: serde::Deserialize<'de>, K::Compr: serde::Deserialize<'de>, \
247                           K::XOnly: serde::Deserialize<'de>"
248        )
249    )
250)]
251pub struct RgbDescr<K: DeriveSet = XpubDerivable> {
252    deriver: RgbDeriver<K>,
253    seals: SealDescr,
254    noise: Bytes32,
255    nonce: u64,
256}
257
258impl<K: DeriveSet> RgbDescr<K> {
259    pub fn new_unfunded(deriver: impl Into<StdDescr<K>>, noise: impl Into<[u8; 32]>) -> Self {
260        let deriver = match deriver.into() {
261            StdDescr::Wpkh(d) => RgbDeriver::OpretOnly(StdDescr::Wpkh(d)),
262            StdDescr::TrKey(tr) => RgbDeriver::Universal { tr: Tr::KeyOnly(tr), tweaks: empty!() },
263            _ => unreachable!(),
264        };
265        Self {
266            deriver,
267            seals: empty!(),
268            noise: noise.into().into(),
269            nonce: 0,
270        }
271    }
272
273    pub fn key_only_unfunded(internal_key: K, noise: impl Into<[u8; 32]>) -> Self
274    where K: DeriveSet<XOnly = K> + DeriveKey<XOnlyPk> {
275        Self {
276            deriver: RgbDeriver::Universal {
277                tr: Tr::KeyOnly(TrKey::from(internal_key)),
278                tweaks: empty!(),
279            },
280            seals: empty!(),
281            noise: noise.into().into(),
282            nonce: 0,
283        }
284    }
285
286    pub fn next_nonce(&mut self) -> u64 {
287        let nonce = self.nonce;
288        self.nonce += 1;
289        nonce
290    }
291
292    pub fn noise(&self) -> Bytes32 { self.noise }
293
294    pub fn seals(&self) -> impl Iterator<Item = WTxoSeal> + use<'_, K> {
295        self.seals.iter().copied()
296    }
297
298    pub fn add_seal(&mut self, seal: WTxoSeal) { self.seals.insert(seal); }
299
300    pub fn add_tweak(&mut self, terminal: Terminal, tweak: TapretCommitment) {
301        match &mut self.deriver {
302            RgbDeriver::OpretOnly(_) => {
303                panic!("attempting to add tapret tweaks to an opret-only wallet")
304            }
305            RgbDeriver::Universal { tr: _, tweaks } => {
306                tweaks.entry(terminal).or_default().insert(tweak);
307            }
308        }
309    }
310}
311
312impl<K: DeriveSet> Derive<DerivedScript> for RgbDescr<K> {
313    fn default_keychain(&self) -> Keychain { self.deriver.default_keychain() }
314    fn keychains(&self) -> BTreeSet<Keychain> { self.deriver.keychains() }
315    fn derive(
316        &self,
317        keychain: impl Into<Keychain>,
318        index: impl Into<NormalIndex>,
319    ) -> impl Iterator<Item = DerivedScript> {
320        self.deriver.derive(keychain, index)
321    }
322}
323
324impl<K: DeriveSet<Legacy = K, Compr = K, XOnly = K> + DeriveLegacy + DeriveCompr + DeriveXOnly>
325    Descriptor<K> for RgbDescr<K>
326{
327    fn class(&self) -> SpkClass { self.deriver.class() }
328    fn keys<'a>(&'a self) -> impl Iterator<Item = &'a K>
329    where K: 'a {
330        self.deriver.keys()
331    }
332    fn vars<'a>(&'a self) -> impl Iterator<Item = &'a ()>
333    where (): 'a {
334        self.deriver.vars()
335    }
336    fn xpubs(&self) -> impl Iterator<Item = &XpubAccount> { self.deriver.xpubs() }
337    fn legacy_keyset(&self, terminal: Terminal) -> IndexMap<LegacyPk, KeyOrigin> {
338        self.deriver.legacy_keyset(terminal)
339    }
340    fn xonly_keyset(&self, terminal: Terminal) -> IndexMap<XOnlyPk, TapDerivation> {
341        self.deriver.xonly_keyset(terminal)
342    }
343    fn legacy_witness(
344        &self,
345        keysigs: IndexMap<&KeyOrigin, LegacyKeySig>,
346        redeem_script: Option<RedeemScript>,
347        witness_script: Option<WitnessScript>,
348    ) -> Option<(SigScript, Option<Witness>)> {
349        self.deriver
350            .legacy_witness(keysigs, redeem_script, witness_script)
351    }
352    fn taproot_witness(
353        &self,
354        cb: Option<&ControlBlock>,
355        keysigs: IndexMap<&KeyOrigin, TaprootKeySig>,
356    ) -> Option<Witness> {
357        self.deriver.taproot_witness(cb, keysigs)
358    }
359}
360
361#[cfg(test)]
362mod test {
363    use core::str::FromStr;
364
365    use bpstd::Wpkh;
366
367    use super::*;
368
369    #[test]
370    fn descr_serde() {
371        let s = "[643a7adc/86'/1'/0']tpubDCNiWHaiSkgnQjuhsg9kjwaUzaxQjUcmhagvYzqQ3TYJTgFGJstVaqnu4yhtFktBhCVFmBNLQ5sN53qKzZbMksm3XEyGJsEhQPfVZdWmTE2/<0;1;9;10>/*";
372        let xpub = XpubDerivable::from_str(s).unwrap();
373        let descr = RgbDescr::<XpubDerivable>::new_unfunded(Wpkh::from(xpub), [0u8; 32]);
374
375        let toml = toml::to_string(&descr).unwrap();
376        let descr2: RgbDescr<XpubDerivable> = toml::from_str(&toml).unwrap();
377
378        assert_eq!(descr.to_string(), descr2.to_string());
379        assert_eq!(
380            toml,
381            r#"seals = []
382noise = "0000000000000000000000000000000000000000000000000000000000000000"
383nonce = 0
384
385[deriver.opretOnly]
386wpkh = "[643a7adc/86h/1h/0h]tpubDCNiWHaiSkgnQjuhsg9kjwaUzaxQjUcmhagvYzqQ3TYJTgFGJstVaqnu4yhtFktBhCVFmBNLQ5sN53qKzZbMksm3XEyGJsEhQPfVZdWmTE2/<0;1;9;10>/*"
387"#
388        );
389    }
390}