1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
26extern 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}