1use std::collections::{BTreeMap, BTreeSet};
23use std::fmt::{self, Display, Formatter};
24
25use amplify::Wrapper;
26#[cfg(feature = "bp")]
27use bpwallet::IdxBase;
28use psrgbt::Terminal;
29use rgbstd::bitcoin::key::UntweakedPublicKey;
30use rgbstd::bitcoin::PublicKey;
31use rgbstd::rgbcore::seals::txout::CloseMethod;
32use rgbstd::tapret::TapretCommitment;
33
34pub trait DescriptorRgb {
35 fn close_method(&self) -> CloseMethod;
36 fn add_tapret_tweak(&mut self, terminal: Terminal, tweak: TapretCommitment);
37}
38
39#[derive(Clone, Eq, PartialEq, Debug)]
40#[cfg_attr(
41 feature = "serde",
42 derive(Serialize, Deserialize),
43 serde(crate = "serde_crate", rename_all = "camelCase")
44)]
45pub struct TapretKey<K = UntweakedPublicKey> {
46 pub tr: K,
47 pub tweaks: BTreeMap<Terminal, BTreeSet<TapretCommitment>>,
48}
49
50impl<K: Display> Display for TapretKey<K> {
51 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
52 write!(f, "tapret({},tweaks(", self.tr)?;
53 let mut iter = self.tweaks.iter().peekable();
54 while let Some((term, tweaks)) = iter.next() {
55 write!(f, "{}/{}=", term.keychain, term.index)?;
56 let mut commitment_iter = tweaks.iter().peekable();
57 while let Some(tweak) = commitment_iter.next() {
58 write!(f, "{tweak}")?;
59 if commitment_iter.peek().is_some() {
60 f.write_str(",")?;
61 }
62 }
63 if iter.peek().is_some() {
64 f.write_str(";")?;
65 }
66 }
67 f.write_str("))")
68 }
69}
70
71impl<K> TapretKey<K> {
72 pub fn with_key(key: K) -> Self {
73 TapretKey {
74 tr: key,
75 tweaks: empty!(),
76 }
77 }
78}
79
80impl<K> DescriptorRgb for TapretKey<K> {
81 fn close_method(&self) -> CloseMethod { CloseMethod::TapretFirst }
82
83 fn add_tapret_tweak(&mut self, terminal: Terminal, tweak: TapretCommitment) {
84 self.tweaks.entry(terminal).or_default().insert(tweak);
85 }
86}
87
88#[derive(Clone, Eq, PartialEq, Debug)]
89#[cfg_attr(
90 feature = "serde",
91 derive(Serialize, Deserialize),
92 serde(crate = "serde_crate", rename_all = "camelCase")
93)]
94pub struct WpkhDescr<K = PublicKey>(K);
95
96impl<K: Display> Display for WpkhDescr<K> {
97 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "wpkh({})", self.0) }
98}
99
100impl<K> WpkhDescr<K> {
101 pub fn with_key(key: K) -> Self { WpkhDescr(key) }
102}
103
104#[derive(Clone, Eq, PartialEq, Debug)]
105#[cfg_attr(
106 feature = "serde",
107 derive(Serialize, Deserialize),
108 serde(crate = "serde_crate", rename_all = "camelCase",)
109)]
110#[non_exhaustive]
111pub enum RgbDescr<K> {
112 Wpkh(WpkhDescr<K>),
113 TapretKey(TapretKey<K>),
114}
115
116impl<K> From<WpkhDescr<K>> for RgbDescr<K> {
117 fn from(wpkh: WpkhDescr<K>) -> Self { RgbDescr::Wpkh(wpkh) }
118}
119
120impl<K> From<TapretKey<K>> for RgbDescr<K> {
121 fn from(tapret: TapretKey<K>) -> Self { RgbDescr::TapretKey(tapret) }
122}
123
124impl<K: Display> Display for RgbDescr<K> {
125 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
126 match self {
127 RgbDescr::Wpkh(wpkh) => Display::fmt(wpkh, f),
128 RgbDescr::TapretKey(tapret_key) => Display::fmt(tapret_key, f),
129 }
130 }
131}
132
133impl<K> DescriptorRgb for RgbDescr<K> {
134 fn close_method(&self) -> CloseMethod {
135 match self {
136 RgbDescr::Wpkh(_) => CloseMethod::OpretFirst,
137 RgbDescr::TapretKey(d) => d.close_method(),
138 }
139 }
140
141 fn add_tapret_tweak(&mut self, terminal: Terminal, tweak: TapretCommitment) {
142 match self {
143 RgbDescr::Wpkh(_) => panic!("adding tapret tweak to non-taproot descriptor"),
144 RgbDescr::TapretKey(d) => d.add_tapret_tweak(terminal, tweak),
145 }
146 }
147}
148
149#[cfg(feature = "bp")]
150pub mod bp_wallet_integration {
151 use std::collections::{BTreeSet, HashMap};
152 use std::iter;
153
154 use bpwallet::{
155 Derive, DeriveXOnly, DerivedScript, Descriptor, KeyOrigin, Keychain, LegacyKeySig,
156 LegacyPk, NormalIndex, SigScript, SpkClass, TapDerivation, TapScript, TapTree,
157 TaprootKeySig, TrKey, Witness, Wpkh, XOnlyPk, XpubAccount, XpubDerivable,
158 };
159 use indexmap::IndexMap;
160
161 use super::*;
162
163 impl<K> From<TrKey<K>> for TapretKey<K>
164 where K: Clone + DeriveXOnly
165 {
166 fn from(tr_key: TrKey<K>) -> Self {
167 let tr = tr_key
168 .keys()
169 .next()
170 .cloned()
171 .expect("TrKey should have a key");
172 TapretKey {
173 tr,
174 tweaks: empty!(),
175 }
176 }
177 }
178
179 impl Descriptor<XpubDerivable> for RgbDescr<XpubDerivable> {
180 fn class(&self) -> SpkClass {
181 match self {
182 RgbDescr::Wpkh(_) => SpkClass::P2wpkh,
183 RgbDescr::TapretKey(_) => SpkClass::P2tr,
184 }
185 }
186
187 fn keys<'a>(&'a self) -> impl Iterator<Item = &'a XpubDerivable>
188 where XpubDerivable: 'a {
189 match self {
190 RgbDescr::Wpkh(wpkh) => {
191 vec![&wpkh.0]
192 }
193 RgbDescr::TapretKey(tapret_key) => {
194 vec![&tapret_key.tr]
195 }
196 }
197 .into_iter()
198 }
199
200 fn vars<'a>(&'a self) -> impl Iterator<Item = &'a ()>
201 where (): 'a {
202 iter::empty()
203 }
204
205 fn xpubs(&self) -> impl Iterator<Item = &XpubAccount> {
206 match self {
207 RgbDescr::Wpkh(d) => vec![d.0.spec()],
208 RgbDescr::TapretKey(d) => vec![d.tr.spec()],
209 }
210 .into_iter()
211 }
212
213 fn legacy_keyset(&self, terminal: bpwallet::Terminal) -> IndexMap<LegacyPk, KeyOrigin> {
214 match self {
215 RgbDescr::Wpkh(d) => Wpkh::from(d.0.clone()).legacy_keyset(terminal),
216 RgbDescr::TapretKey(_) => IndexMap::new(),
217 }
218 }
219
220 fn xonly_keyset(&self, terminal: bpwallet::Terminal) -> IndexMap<XOnlyPk, TapDerivation> {
221 match self {
222 RgbDescr::Wpkh(_) => IndexMap::new(),
223 RgbDescr::TapretKey(d) => TrKey::from(d.tr.clone()).xonly_keyset(terminal),
224 }
225 }
226
227 fn legacy_witness(
228 &self,
229 keysigs: HashMap<&KeyOrigin, LegacyKeySig>,
230 ) -> Option<(SigScript, Witness)> {
231 match self {
232 RgbDescr::Wpkh(d) => Wpkh::from(d.0.clone()).legacy_witness(keysigs),
233 RgbDescr::TapretKey(_) => None,
234 }
235 }
236
237 fn taproot_witness(&self, keysigs: HashMap<&KeyOrigin, TaprootKeySig>) -> Option<Witness> {
238 match self {
239 RgbDescr::Wpkh(_) => None,
240 RgbDescr::TapretKey(d) => TrKey::from(d.tr.clone()).taproot_witness(keysigs),
241 }
242 }
243 }
244
245 impl Derive<DerivedScript> for RgbDescr<XpubDerivable> {
246 fn default_keychain(&self) -> Keychain {
247 match self {
248 RgbDescr::Wpkh(d) => Wpkh::from(d.0.clone()).default_keychain(),
249 RgbDescr::TapretKey(d) => TrKey::from(d.tr.clone()).default_keychain(),
250 }
251 }
252
253 fn keychains(&self) -> BTreeSet<Keychain> {
254 match self {
255 RgbDescr::Wpkh(d) => Wpkh::from(d.0.clone()).keychains(),
256 RgbDescr::TapretKey(d) => TrKey::from(d.tr.clone()).keychains(),
257 }
258 }
259
260 fn derive(
261 &self,
262 keychain: impl Into<Keychain>,
263 index: impl Into<NormalIndex>,
264 ) -> impl Iterator<Item = DerivedScript> {
265 let keychain = keychain.into();
266 let index = index.into();
267
268 match self {
270 RgbDescr::Wpkh(d) => Wpkh::from(d.0.clone()).derive(keychain, index).collect(),
271 RgbDescr::TapretKey(d) => {
272 let derivation = &d.tr;
273 let terminal = Terminal::new(keychain.into_inner(), index.index());
274 let derived_keys =
275 <XpubDerivable as Derive<XOnlyPk>>::derive(derivation, keychain, index);
276 let mut derived_scripts = Vec::with_capacity(d.tweaks.len() + 1);
277 for internal_key in derived_keys {
278 derived_scripts.push(DerivedScript::TaprootKeyOnly(internal_key.into()));
279 for tweak in d.tweaks.get(&terminal).into_iter().flatten() {
280 let commitment = tweak.commit();
281 let tap_script = TapScript::from_unsafe(commitment.into_bytes());
282 let tap_tree = TapTree::with_single_leaf(tap_script);
283 let script =
284 DerivedScript::TaprootScript(internal_key.into(), tap_tree);
285 derived_scripts.push(script);
286 }
287 }
288 derived_scripts
289 }
290 }
291 .into_iter()
292 }
293 }
294}
295
296#[cfg(feature = "bp")]
297#[cfg(test)]
298mod test {
299 use std::str::FromStr;
300
301 use bpwallet::{DeriveSet, Idx, Keychain, NormalIndex, Terminal, TrKey, XpubDerivable};
302 use rgbstd::rgbcore::commit_verify::mpc::Commitment;
303 use strict_types::StrictDumb;
304
305 use super::*;
306 use crate::DescriptorRgb;
307
308 #[test]
309 fn tapret_key_display() {
310 let xpub_str = "[643a7adc/86h/1h/0h]tpubDCNiWHaiSkgnQjuhsg9kjwaUzaxQjUcmhagvYzqQ3TYJTgFGJstVaqnu4yhtFktBhCVFmBNLQ5sN53qKzZbMksm3XEyGJsEhQPfVZdWmTE2/<0;1>/*";
311 let xpub = XpubDerivable::from_str(xpub_str).unwrap();
312 let internal_key: TrKey<<XpubDerivable as DeriveSet>::XOnly> = TrKey::from(xpub.clone());
313
314 let mut tapret_key = TapretKey::from(internal_key);
316 assert_eq!(format!("{tapret_key}"), format!("tapret({xpub_str},tweaks())"));
317
318 let terminal = Terminal::new(Keychain::INNER, NormalIndex::ZERO);
320 let tweak = TapretCommitment::with(Commitment::strict_dumb(), 2);
321 tapret_key.add_tapret_tweak(terminal.into(), tweak);
322 assert_eq!(
323 format!("{tapret_key}"),
324 format!("tapret({xpub_str},tweaks(1/0=00000000000000000000000000000000000000000s))")
325 );
326
327 let terminal = Terminal::new(Keychain::from(7), NormalIndex::from(12u8));
329 let tweak = TapretCommitment::with(Commitment::strict_dumb(), 5);
330 tapret_key.add_tapret_tweak(terminal.into(), tweak.clone());
331 assert_eq!(
332 format!("{tapret_key}"),
333 format!(
334 "tapret({xpub_str},tweaks(1/0=00000000000000000000000000000000000000000s;7/\
335 12=00000000000000000000000000000000000000001p))"
336 )
337 );
338
339 let tweak = TapretCommitment::with(Commitment::strict_dumb(), 2);
341 tapret_key.add_tapret_tweak(terminal.into(), tweak);
342 assert_eq!(
343 format!("{tapret_key}"),
344 format!(
345 "tapret({xpub_str},tweaks(1/0=00000000000000000000000000000000000000000s;7/\
346 12=00000000000000000000000000000000000000000s,\
347 00000000000000000000000000000000000000001p))"
348 )
349 );
350 }
351}