dbc/tapret/mod.rs
1// Deterministic bitcoin commitments library.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2024 by
6// Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14// http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22//! Taproot OP_RETURN-based deterministic bitcoin commitment scheme ("tapret").
23//!
24//! **Embed-commit by constructor:**
25//! a) `TapTree, Msg -> TapTree', TapRightPartner`, defined in `taptree` mod;
26//! b) `(psbt::Output, TxOut), Msg -> (psbt::Output, TxOut)', TapretProof`,
27//! defined in `output` mod;
28//! c) `PSBT, Msg -> PSBT', TapretProof`, defined in `psbt` mod;
29//! **Convolve-commit by receiver:**
30//! d) `UntweakedPublicKey, TapRightPartner, Msg -> TweakedPublicKey'` in
31//! `xonlypk`;
32//! e) `PubkeyScript, TapretProof, Msg -> PubkeyScript'` in `scriptpk`;
33//! f) `TxOut, TapretProof, Msg -> TxOut'` in `txout`;
34//! g) `Tx, TapretProof, Msg -> Tx'` in `tx`.
35//!
36//! **Verify by constructor:**
37//! a) `TapRightPartner, Msg, TapTree' -> bool`;
38//! b) `TapretProof, Msg, (psbt::Output, TxOut)' -> bool`;
39//! c) `TapretProof, Msg, PSBT' -> bool`.
40//! **Verify by receiver:**
41//! d) `TweakedPublicKey, TapretProof, Msg -> bool`;
42//! e) `PubkeyScript', TapretProof, Msg -> bool`;
43//! f) `TxOut', TapretProof, Msg -> bool`;
44//! g) `Tx', TapretProof, Msg -> bool`.
45//!
46//! **Find:** `descriptor::Tr<PublicKey> + TapretTweak -> descriptor::Tapret`
47//!
48//! **Spend:** `TapretTweak + ControlBlock -> ControlBlock'`
49//!
50//! Find & spend procedures are wallet-specific, embed-commit and verify -
51//! are not.
52//!
53//! **Possible data type conversions:**
54//! - `TapTree', UntweakedPublicKey -> TweakedPublicKey'`
55//! - `TapRightPartner, UntweakedPublicKey -> TweakedPublicKey`
56//! - `TapRightPartner, Msg -> TapretTweak`
57//! - `TapretProof, Msg -> TweakedPublicKey'`
58//!
59//! **Embed-commitment containers and proofs (container/proof):**
60//! a) `TapTree` / `TapRightPartner`
61//! b) `TapretProof` / `TweakedPublicKey'`
62//! b) `XOnlyPublicKey` / `TapretProof`
63
64mod tapscript;
65mod tx;
66mod txout;
67mod spk;
68mod xonlypk;
69
70use bc::{InternalPk, IntoTapHash, LeafScript, ScriptPubkey, TapBranchHash, TapNodeHash, Tx};
71use commit_verify::mpc::Commitment;
72use commit_verify::{CommitmentProtocol, ConvolveCommitProof, ConvolveVerifyError};
73use strict_encoding::{StrictDeserialize, StrictSerialize};
74pub use tapscript::{TapretCommitment, TAPRET_SCRIPT_COMMITMENT_PREFIX};
75pub use tx::TapretError;
76pub use xonlypk::TapretKeyError;
77
78use crate::proof::Method;
79use crate::{Proof, LIB_NAME_BPCORE};
80
81/// Marker non-instantiable enum defining LNPBP-12 taproot OP_RETURN (`tapret`)
82/// protocol.
83pub enum TapretFirst {}
84
85impl CommitmentProtocol for TapretFirst {}
86
87/// Errors in constructing tapret path proof [`TapretPathProof`].
88#[derive(Clone, Eq, PartialEq, Hash, Debug, Display, Error)]
89#[display(doc_comments)]
90pub enum TapretPathError {
91 /// the length of the constructed tapret path proof exceeds taproot path
92 /// length limit.
93 MaxDepthExceeded,
94
95 /// the node partner {0} at the level 1 can't be proven not to contain an
96 /// alternative tapret commitment.
97 InvalidNodePartner(TapretNodePartner),
98}
99
100/// Right-side hashing partner in the taproot script tree, used by
101/// [`TapretNodePartner::RightBranch`] to ensure correct consensus ordering of
102/// the child elements.
103#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
104#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
105#[strict_type(lib = LIB_NAME_BPCORE)]
106#[cfg_attr(
107 feature = "serde",
108 derive(Serialize, Deserialize),
109 serde(crate = "serde_crate", rename_all = "camelCase")
110)]
111#[display("{left_node_hash}:{right_node_hash}")]
112pub struct TapretRightBranch {
113 left_node_hash: TapNodeHash,
114 right_node_hash: TapNodeHash,
115}
116
117impl TapretRightBranch {
118 /// Constructs [`TapretRightBranch`] by putting `a` and `b` branches hashes
119 /// into the correct consensus order (i.e. lexicographically).
120 pub fn with(a: TapNodeHash, b: TapNodeHash) -> TapretRightBranch {
121 let (left, right) = if a < b { (a, b) } else { (b, a) };
122 TapretRightBranch {
123 left_node_hash: left,
124 right_node_hash: right,
125 }
126 }
127
128 /// Returns hash of the left-side child node of the branch (having smaller
129 /// hash value).
130 #[inline]
131 pub fn left_node_hash(self) -> TapNodeHash { self.left_node_hash }
132
133 /// Returns hash of the right-side child node of the branch (having smaller
134 /// hash value).
135 #[inline]
136 pub fn right_node_hash(self) -> TapNodeHash { self.right_node_hash }
137
138 /// Computes node hash of the partner node defined by this proof.
139 pub fn node_hash(&self) -> TapNodeHash {
140 TapBranchHash::with_nodes(self.left_node_hash, self.right_node_hash).into_tap_hash()
141 }
142}
143
144/*
145impl StrictDecode for TapretRightBranch {
146 fn strict_decode<D: Read>(
147 mut d: D,
148 ) -> Result<Self, strict_encoding::Error> {
149 let left_node_hash = StrictDecode::strict_decode(&mut d)?;
150 let right_node_hash = StrictDecode::strict_decode(d)?;
151 if left_node_hash > right_node_hash {
152 Err(strict_encoding::Error::DataIntegrityError(s!(
153 "non-cosensus ordering of hashes in TapretRightBranch"
154 )))
155 } else {
156 Ok(TapretRightBranch {
157 left_node_hash,
158 right_node_hash,
159 })
160 }
161 }
162}
163 */
164
165/// Information proving step of a tapret path in determined way within a given
166/// tap tree.
167///
168/// The structure hosts proofs that the right-side partner at the taproot script
169/// tree node does not contain an alternative OP-RETURN commitment script.
170#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
171#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
172#[strict_type(lib = LIB_NAME_BPCORE, tags = order, dumb = Self::RightLeaf(default!()))]
173#[cfg_attr(
174 feature = "serde",
175 derive(Serialize, Deserialize),
176 serde(crate = "serde_crate", rename_all = "camelCase")
177)]
178#[display(inner)]
179pub enum TapretNodePartner {
180 /// Tapret commitment is on the right side of the tree; i.e the node
181 /// hashing partner can't contain an alternative commitment.
182 LeftNode(TapNodeHash),
183
184 /// Single script spending path was present before tapret commitment, which
185 /// becomes a second leaf at level 1.
186 #[from]
187 RightLeaf(LeafScript),
188
189 /// Multiple script spending paths were present; or a single script
190 /// spending path should be hidden from revealing the script in the proof.
191 ///
192 /// To prove that the 1-st level branch is not a script leafs containing
193 /// an alternative OP_RETURN commitment we have to reveal the presence of
194 /// two level 2 structures underneath.
195 RightBranch(TapretRightBranch),
196}
197
198impl TapretNodePartner {
199 /// Constructs right-side tapret branch proof structuring `a` and `b`
200 /// children node hashes in the correct consensus order (i.e.
201 /// lexicographically).
202 pub fn right_branch(a: TapNodeHash, b: TapNodeHash) -> TapretNodePartner {
203 TapretNodePartner::RightBranch(TapretRightBranch::with(a, b))
204 }
205
206 /// Checks that the sibling data does not contain another tapret commitment.
207 ///
208 /// The check ensures that if the sibling data are present, their first 31
209 /// bytes are not equal to [`TAPRET_SCRIPT_COMMITMENT_PREFIX`], and if
210 /// the sibling is another node, the hash of its first child in the proof
211 /// is smaller than the hash of the other.
212 pub fn check_no_commitment(&self) -> bool {
213 match self {
214 TapretNodePartner::LeftNode(_) => true,
215 TapretNodePartner::RightLeaf(LeafScript { script, .. }) if script.len() < 64 => true,
216 TapretNodePartner::RightLeaf(LeafScript { script, .. }) => {
217 script[..31] != TAPRET_SCRIPT_COMMITMENT_PREFIX[..]
218 }
219 TapretNodePartner::RightBranch(right_branch) => {
220 right_branch.left_node_hash()[..31] != TAPRET_SCRIPT_COMMITMENT_PREFIX[..]
221 }
222 }
223 }
224
225 /// Checks that the sibling has a correct ordering regarding some other
226 /// node.
227 pub fn check_ordering(&self, other_node: TapNodeHash) -> bool {
228 match self {
229 TapretNodePartner::LeftNode(left_node) => *left_node <= other_node,
230 TapretNodePartner::RightLeaf(leaf_script) => {
231 let right_node = leaf_script.tap_leaf_hash().into_tap_hash();
232 other_node <= right_node
233 }
234 TapretNodePartner::RightBranch(right_branch) => {
235 let right_node = right_branch.node_hash();
236 other_node <= right_node
237 }
238 }
239 }
240
241 /// Computes node hash of the partner node defined by this proof.
242 pub fn tap_node_hash(&self) -> TapNodeHash {
243 match self {
244 TapretNodePartner::LeftNode(hash) => *hash,
245 TapretNodePartner::RightLeaf(leaf_script) => {
246 leaf_script.tap_leaf_hash().into_tap_hash()
247 }
248 TapretNodePartner::RightBranch(right_branch) => right_branch.node_hash(),
249 }
250 }
251}
252
253/// Structure proving that a merkle path to the tapret commitment inside the
254/// taproot script tree does not have an alternative commitment.
255///
256/// Holds information about the sibling at level 1 of the tree in form of
257/// [`TapretNodePartner`].
258#[derive(Getters, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
259#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
260#[strict_type(lib = LIB_NAME_BPCORE)]
261#[cfg_attr(
262 feature = "serde",
263 derive(Serialize, Deserialize),
264 serde(crate = "serde_crate", rename_all = "camelCase")
265)]
266pub struct TapretPathProof {
267 /// Information about the sibling at level 1 of the tree
268 partner_node: Option<TapretNodePartner>,
269
270 /// A nonce value used to put the tapret commitment into the right side of
271 /// the tree.
272 #[getter(as_copy)]
273 nonce: u8,
274}
275
276// Used by PSBT tapret keys
277impl StrictSerialize for TapretPathProof {}
278impl StrictDeserialize for TapretPathProof {}
279
280impl TapretPathProof {
281 /// Construct new empty path proof.
282 #[inline]
283 pub fn root(nonce: u8) -> TapretPathProof {
284 TapretPathProof {
285 partner_node: None,
286 nonce,
287 }
288 }
289
290 /// Adds element to the path proof.
291 pub fn with(elem: TapretNodePartner, nonce: u8) -> Result<TapretPathProof, TapretPathError> {
292 if !elem.check_no_commitment() {
293 return Err(TapretPathError::InvalidNodePartner(elem));
294 }
295 Ok(TapretPathProof {
296 partner_node: Some(elem),
297 nonce,
298 })
299 }
300
301 /// Checks that the sibling data does not contain another tapret commitment
302 /// for any step of the mekrle path.
303 #[inline]
304 pub fn check_no_commitment(&self) -> bool {
305 self.partner_node.as_ref().map(TapretNodePartner::check_no_commitment).unwrap_or(true)
306 }
307
308 /// Returns original merkle root of the tree before deterministic bitcoin
309 /// commitment. If originally there was no script path spendings, returns
310 /// `None`.
311 #[inline]
312 pub fn original_merkle_root(&self) -> Option<TapNodeHash> {
313 self.partner_node.as_ref().map(|partner| partner.tap_node_hash())
314 }
315}
316
317/*
318
319impl IntoIterator for TapretPathProof {
320 type Item = TapretNodePartner;
321 type IntoIter = std::vec::IntoIter<TapretNodePartner>;
322
323 #[inline]
324 fn into_iter(self) -> Self::IntoIter {
325 self.0.into_iter()
326 }
327}
328
329impl<'data> IntoIterator for &'data TapretPathProof {
330 type Item = TapretNodePartner;
331 type IntoIter = core::slice::Iter<'data, TapretNodePartner>;
332
333 #[inline]
334 fn into_iter(self) -> Self::IntoIter {
335 self.0.iter()
336 }
337}
338
339 */
340
341/// Information proving tapret determinism for a given tapret commitment.
342/// Used both in the commitment procedure for PSBTs and in
343/// client-side-validation of the commitment.
344#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
345#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
346#[strict_type(lib = LIB_NAME_BPCORE)]
347#[cfg_attr(
348 feature = "serde",
349 derive(Serialize, Deserialize),
350 serde(crate = "serde_crate", rename_all = "camelCase")
351)]
352pub struct TapretProof {
353 /// A merkle path to the commitment inside the taproot script tree. For
354 /// each node it also must hold information about the sibling in form of
355 /// [`TapretNodePartner`].
356 pub path_proof: TapretPathProof,
357
358 /// The internal key used by the taproot output.
359 ///
360 /// We need to keep this information client-side since it can't be
361 /// retrieved from the mined transaction.
362 pub internal_pk: InternalPk,
363}
364
365impl StrictSerialize for TapretProof {}
366impl StrictDeserialize for TapretProof {}
367
368impl TapretProof {
369 /// Restores original scripPubkey before deterministic bitcoin commitment
370 /// applied.
371 #[inline]
372 pub fn original_pubkey_script(&self) -> ScriptPubkey {
373 let merkle_root = self.path_proof.original_merkle_root();
374 ScriptPubkey::p2tr(self.internal_pk, merkle_root)
375 }
376}
377
378impl Proof<Method> for TapretProof {
379 type Error = ConvolveVerifyError;
380
381 fn method(&self) -> Method { Method::TapretFirst }
382
383 fn verify(&self, msg: &Commitment, tx: &Tx) -> Result<(), ConvolveVerifyError> {
384 ConvolveCommitProof::<_, Tx, _>::verify(self, msg, tx)
385 }
386}