1use 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 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 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}