Skip to content

Commit c71931d

Browse files
committed
allow multiple tweaks per terminal
1 parent f015712 commit c71931d

File tree

6 files changed

+111
-59
lines changed

6 files changed

+111
-59
lines changed

Cargo.lock

+8-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ serde = ["serde_crate", "serde_yaml", "bp-std/serde", "rgb-psbt/serde"]
9696
features = ["all"]
9797

9898
[patch.crates-io]
99+
bp-std = { git = "https://github.com/zoedberg/bp-std", branch = "0.11.1-2" }
100+
bp-invoice = { git = "https://github.com/zoedberg/bp-std", branch = "0.11.1-2" }
101+
descriptors = { git = "https://github.com/zoedberg/bp-std", branch = "0.11.1-2" }
102+
psbt = { git = "https://github.com/zoedberg/bp-std", branch = "0.11.1-2" }
99103
bp-electrum = { git = "https://github.com/zoedberg/bp-electrum-client", branch = "remove_bpstd" }
100104
bp-esplora = { git = "https://github.com/zoedberg/bp-esplora-client", branch = "remove_bpstd" }
101105
bp-wallet = { git = "https://github.com/zoedberg/bp-wallet", branch = "0.11.1-2" }

src/descriptor.rs

+88-39
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
// See the License for the specific language governing permissions and
2020
// limitations under the License.
2121

22-
use std::collections::{BTreeSet, HashMap};
22+
use std::collections::{BTreeMap, BTreeSet, HashMap};
2323
use std::fmt::{self, Display, Formatter};
2424
use std::iter;
2525
use std::str::FromStr;
@@ -38,17 +38,9 @@ use bpstd::{
3838
use commit_verify::CommitVerify;
3939
use indexmap::IndexMap;
4040

41-
#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)]
42-
#[display("terminal derivation {0} already has a taptweak assigned")]
43-
pub struct TapTweakAlreadyAssigned(pub Terminal);
44-
4541
pub trait DescriptorRgb<K = XpubDerivable, V = ()>: Descriptor<K, V> {
4642
fn close_method(&self) -> CloseMethod;
47-
fn add_tapret_tweak(
48-
&mut self,
49-
terminal: Terminal,
50-
tweak: TapretCommitment,
51-
) -> Result<(), TapTweakAlreadyAssigned>;
43+
fn add_tapret_tweak(&mut self, terminal: Terminal, tweak: TapretCommitment);
5244
}
5345

5446
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
@@ -119,19 +111,24 @@ impl From<RgbKeychain> for Keychain {
119111
)]
120112
pub struct TapretKey<K: DeriveXOnly = XpubDerivable> {
121113
pub tr: TrKey<K>,
122-
// TODO: Allow multiple tweaks per index by introducing derivation using new Terminal trait
123-
pub tweaks: HashMap<Terminal, TapretCommitment>,
114+
pub tweaks: BTreeMap<Terminal, BTreeSet<TapretCommitment>>,
124115
}
125116

126117
impl<K: DeriveXOnly + Display> Display for TapretKey<K> {
127118
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
128119
write!(f, "tapret({},tweaks(", self.tr.as_internal_key())?;
129120
let mut iter = self.tweaks.iter().peekable();
130-
while let Some((term, tweak)) = iter.next() {
121+
while let Some((term, tweaks)) = iter.next() {
131122
if term.keychain != RgbKeychain::Tapret.into() {
132-
write!(f, "{}/", term.keychain)?;
123+
write!(f, "{}/{}=", term.keychain, term.index)?;
124+
}
125+
let mut commitment_iter = tweaks.iter().peekable();
126+
while let Some(tweak) = commitment_iter.next() {
127+
write!(f, "{tweak}")?;
128+
if commitment_iter.peek().is_some() {
129+
f.write_str(",")?;
130+
}
133131
}
134-
write!(f, "{}={tweak}", term.index)?;
135132
if iter.peek().is_some() {
136133
f.write_str(";")?;
137134
}
@@ -159,19 +156,21 @@ impl<K: DeriveXOnly> Derive<DerivedScript> for TapretKey<K> {
159156
&self,
160157
keychain: impl Into<Keychain>,
161158
index: impl Into<NormalIndex>,
162-
) -> DerivedScript {
159+
) -> impl Iterator<Item = DerivedScript> {
163160
let keychain = keychain.into();
164161
let index = index.into();
165162
let terminal = Terminal::new(keychain, index);
166-
let internal_key = self.tr.as_internal_key().derive(keychain, index);
167-
if keychain.into_inner() == RgbKeychain::Tapret as u8 {
168-
if let Some(tweak) = self.tweaks.get(&terminal) {
163+
let mut derived_scripts = Vec::with_capacity(self.tweaks.len() + 1);
164+
for internal_key in self.tr.as_internal_key().derive(keychain, index) {
165+
derived_scripts.push(DerivedScript::TaprootKeyOnly(internal_key.into()));
166+
for tweak in self.tweaks.get(&terminal).into_iter().flatten() {
169167
let script_commitment = TapScript::commit(tweak);
170168
let tap_tree = TapTree::with_single_leaf(script_commitment);
171-
return DerivedScript::TaprootScript(internal_key.into(), tap_tree);
169+
let script = DerivedScript::TaprootScript(internal_key.into(), tap_tree);
170+
derived_scripts.push(script);
172171
}
173172
}
174-
DerivedScript::TaprootKeyOnly(internal_key.into())
173+
derived_scripts.into_iter()
175174
}
176175
}
177176

@@ -229,16 +228,8 @@ impl<K: DeriveXOnly> Descriptor<K> for TapretKey<K> {
229228
impl<K: DeriveXOnly> DescriptorRgb<K> for TapretKey<K> {
230229
fn close_method(&self) -> CloseMethod { CloseMethod::TapretFirst }
231230

232-
fn add_tapret_tweak(
233-
&mut self,
234-
terminal: Terminal,
235-
tweak: TapretCommitment,
236-
) -> Result<(), TapTweakAlreadyAssigned> {
237-
if self.tweaks.contains_key(&terminal) {
238-
return Err(TapTweakAlreadyAssigned(terminal));
239-
}
240-
self.tweaks.insert(terminal, tweak);
241-
Ok(())
231+
fn add_tapret_tweak(&mut self, terminal: Terminal, tweak: TapretCommitment) {
232+
self.tweaks.entry(terminal).or_default().insert(tweak);
242233
}
243234
}
244235

@@ -292,10 +283,15 @@ impl<S: DeriveSet> Derive<DerivedScript> for RgbDescr<S> {
292283
}
293284
}
294285

295-
fn derive(&self, change: impl Into<Keychain>, index: impl Into<NormalIndex>) -> DerivedScript {
286+
fn derive(
287+
&self,
288+
change: impl Into<Keychain>,
289+
index: impl Into<NormalIndex>,
290+
) -> impl Iterator<Item = DerivedScript> {
291+
// collecting as a workaround for different opaque types
296292
match self {
297-
RgbDescr::Wpkh(d) => d.derive(change, index),
298-
RgbDescr::TapretKey(d) => d.derive(change, index),
293+
RgbDescr::Wpkh(d) => d.derive(change, index).collect::<Vec<_>>().into_iter(),
294+
RgbDescr::TapretKey(d) => d.derive(change, index).collect::<Vec<_>>().into_iter(),
299295
}
300296
}
301297
}
@@ -375,11 +371,7 @@ where Self: Derive<DerivedScript>
375371
}
376372
}
377373

378-
fn add_tapret_tweak(
379-
&mut self,
380-
terminal: Terminal,
381-
tweak: TapretCommitment,
382-
) -> Result<(), TapTweakAlreadyAssigned> {
374+
fn add_tapret_tweak(&mut self, terminal: Terminal, tweak: TapretCommitment) {
383375
match self {
384376
RgbDescr::Wpkh(_) => panic!("adding tapret tweak to non-taproot descriptor"),
385377
RgbDescr::TapretKey(d) => d.add_tapret_tweak(terminal, tweak),
@@ -396,3 +388,60 @@ impl From<StdDescr> for RgbDescr {
396388
}
397389
}
398390
}
391+
392+
#[cfg(test)]
393+
mod test {
394+
use std::str::FromStr;
395+
396+
use bp::dbc::tapret::TapretCommitment;
397+
use bpstd::{DeriveSet, Idx, Keychain, NormalIndex, Terminal, TrKey, XpubDerivable};
398+
use commit_verify::mpc::Commitment;
399+
use strict_types::StrictDumb;
400+
401+
use super::TapretKey;
402+
use crate::DescriptorRgb;
403+
404+
#[test]
405+
fn tapret_key_display() {
406+
let xpub_str = "[643a7adc/86h/1h/0h]tpubDCNiWHaiSkgnQjuhsg9kjwaUzaxQjUcmhagvYzqQ3TYJTgFGJstVaqnu4yhtFktBhCVFmBNLQ5sN53qKzZbMksm3XEyGJsEhQPfVZdWmTE2/<0;1>/*";
407+
let xpub = XpubDerivable::from_str(xpub_str).unwrap();
408+
let internal_key: TrKey<<XpubDerivable as DeriveSet>::XOnly> = TrKey::from(xpub.clone());
409+
410+
// no tweaks
411+
let mut tapret_key = TapretKey::from(internal_key);
412+
assert_eq!(format!("{tapret_key}"), format!("tapret({xpub_str},tweaks())"));
413+
414+
// add a tweak to a new terminal
415+
let terminal = Terminal::new(Keychain::INNER, NormalIndex::ZERO);
416+
let tweak = TapretCommitment::with(Commitment::strict_dumb(), 2);
417+
tapret_key.add_tapret_tweak(terminal, tweak);
418+
assert_eq!(
419+
format!("{tapret_key}"),
420+
format!("tapret({xpub_str},tweaks(1/0=00000000000000000000000000000000000000000s))")
421+
);
422+
423+
// add another tweak to a new terminal
424+
let terminal = Terminal::new(Keychain::from(7), NormalIndex::from(12u8));
425+
let tweak = TapretCommitment::with(Commitment::strict_dumb(), 5);
426+
tapret_key.add_tapret_tweak(terminal, tweak.clone());
427+
assert_eq!(
428+
format!("{tapret_key}"),
429+
format!(
430+
"tapret({xpub_str},tweaks(1/0=00000000000000000000000000000000000000000s;7/\
431+
12=00000000000000000000000000000000000000001p))"
432+
)
433+
);
434+
435+
// add another tweak to an existing terminal
436+
let tweak = TapretCommitment::with(Commitment::strict_dumb(), 2);
437+
tapret_key.add_tapret_tweak(terminal, tweak);
438+
assert_eq!(
439+
format!("{tapret_key}"),
440+
format!(
441+
"tapret({xpub_str},tweaks(1/0=00000000000000000000000000000000000000000s;7/\
442+
12=00000000000000000000000000000000000000000s,\
443+
00000000000000000000000000000000000000001p))"
444+
)
445+
);
446+
}
447+
}

src/errors.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use rgbstd::persistence::{
3636
use rgbstd::{AssignmentType, ChainNet};
3737
use strict_types::encoding::Ident;
3838

39-
use crate::{validation, TapTweakAlreadyAssigned};
39+
use crate::validation;
4040

4141
#[derive(Debug, Display, Error, From)]
4242
#[display(inner)]
@@ -211,10 +211,6 @@ pub enum CompletionError {
211211
/// the provided PSBT has conflicting descriptor in the taptweak output.
212212
InconclusiveDerivation,
213213

214-
#[from]
215-
#[display(inner)]
216-
MultipleTweaks(TapTweakAlreadyAssigned),
217-
218214
#[from]
219215
#[display(inner)]
220216
TapretKey(TapretKeyError),
@@ -229,3 +225,7 @@ pub enum CompletionError {
229225
#[display(inner)]
230226
Stock(String),
231227
}
228+
229+
impl From<Infallible> for CompletionError {
230+
fn from(_: Infallible) -> Self { unreachable!() }
231+
}

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub mod pay;
3131
mod errors;
3232
mod wallet;
3333

34-
pub use descriptor::{DescriptorRgb, RgbDescr, RgbKeychain, TapTweakAlreadyAssigned, TapretKey};
34+
pub use descriptor::{DescriptorRgb, RgbDescr, RgbKeychain, TapretKey};
3535
pub use errors::{CompletionError, CompositionError, PayError, WalletError};
3636
pub use pay::{TransferParams, WalletProvider};
3737
pub use rgbstd::*;

src/pay.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// limitations under the License.
2121

2222
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
23+
use std::convert::Infallible;
2324
use std::marker::PhantomData;
2425

2526
use amplify::confinement::{Confined, U24};
@@ -493,7 +494,10 @@ where Self::Descr: DescriptorRgb<K>
493494
.ok_or(CompletionError::InconclusiveDerivation)?;
494495
let tapret_commitment = output.tapret_commitment()?;
495496
self.with_descriptor_mut(|descr| {
496-
descr.with_descriptor_mut(|d| d.add_tapret_tweak(terminal, tapret_commitment))
497+
descr.with_descriptor_mut(|d| {
498+
d.add_tapret_tweak(terminal, tapret_commitment);
499+
Ok::<_, Infallible>(())
500+
})
497501
})?;
498502
}
499503

0 commit comments

Comments
 (0)