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::LIB_NAME_BPCORE;
79
80/// Marker non-instantiable enum defining LNPBP-12 taproot OP_RETURN (`tapret`) protocol.
81pub enum TapretFirst {}
82
83impl CommitmentProtocol for TapretFirst {}
84
85/// Errors in constructing tapret path proof [`TapretPathProof`].
86#[derive(Clone, Eq, PartialEq, Hash, Debug, Display, Error)]
87#[display(doc_comments)]
88pub enum TapretPathError {
89    /// the length of the constructed tapret path proof exceeds taproot path
90    /// length limit.
91    MaxDepthExceeded,
92
93    /// the node partner {0} at the level 1 can't be proven not to contain an
94    /// alternative tapret commitment.
95    InvalidNodePartner(TapretNodePartner),
96}
97
98/// Right-side hashing partner in the taproot script tree, used by
99/// [`TapretNodePartner::RightBranch`] to ensure correct consensus ordering of
100/// the child elements.
101#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
102#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
103#[strict_type(lib = LIB_NAME_BPCORE)]
104#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
105#[display("{left_node_hash}:{right_node_hash}")]
106pub struct TapretRightBranch {
107    left_node_hash: TapNodeHash,
108    right_node_hash: TapNodeHash,
109}
110
111impl TapretRightBranch {
112    /// Constructs [`TapretRightBranch`] by putting `a` and `b` branches hashes
113    /// into the correct consensus order (i.e. lexicographically).
114    pub fn with(a: TapNodeHash, b: TapNodeHash) -> TapretRightBranch {
115        let (left, right) = if a < b { (a, b) } else { (b, a) };
116        TapretRightBranch {
117            left_node_hash: left,
118            right_node_hash: right,
119        }
120    }
121
122    /// Returns hash of the left-side child node of the branch (having smaller
123    /// hash value).
124    #[inline]
125    pub fn left_node_hash(self) -> TapNodeHash { self.left_node_hash }
126
127    /// Returns hash of the right-side child node of the branch (having smaller
128    /// hash value).
129    #[inline]
130    pub fn right_node_hash(self) -> TapNodeHash { self.right_node_hash }
131
132    /// Computes node hash of the partner node defined by this proof.
133    pub fn node_hash(&self) -> TapNodeHash {
134        TapBranchHash::with_nodes(self.left_node_hash, self.right_node_hash).into_tap_hash()
135    }
136}
137
138/*
139impl StrictDecode for TapretRightBranch {
140    fn strict_decode<D: Read>(
141        mut d: D,
142    ) -> Result<Self, strict_encoding::Error> {
143        let left_node_hash = StrictDecode::strict_decode(&mut d)?;
144        let right_node_hash = StrictDecode::strict_decode(d)?;
145        if left_node_hash > right_node_hash {
146            Err(strict_encoding::Error::DataIntegrityError(s!(
147                "non-cosensus ordering of hashes in TapretRightBranch"
148            )))
149        } else {
150            Ok(TapretRightBranch {
151                left_node_hash,
152                right_node_hash,
153            })
154        }
155    }
156}
157 */
158
159/// Information proving step of a tapret path in determined way within a given
160/// tap tree.
161///
162/// The structure hosts proofs that the right-side partner at the taproot script
163/// tree node does not contain an alternative OP-RETURN commitment script.
164#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
165#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
166#[strict_type(lib = LIB_NAME_BPCORE, tags = order, dumb = Self::RightLeaf(default!()))]
167#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
168#[display(inner)]
169pub enum TapretNodePartner {
170    /// Tapret commitment is on the right side of the tree; i.e the node
171    /// hashing partner can't contain an alternative commitment.
172    LeftNode(TapNodeHash),
173
174    /// Single script spending path was present before tapret commitment, which
175    /// becomes a second leaf at level 1.
176    #[from]
177    RightLeaf(LeafScript),
178
179    /// Multiple script spending paths were present; or a single script
180    /// spending path should be hidden from revealing the script in the proof.
181    ///
182    /// To prove that the 1-st level branch is not a script leafs containing
183    /// an alternative OP_RETURN commitment we have to reveal the presence of
184    /// two level 2 structures underneath.
185    RightBranch(TapretRightBranch),
186}
187
188impl TapretNodePartner {
189    /// Constructs right-side tapret branch proof structuring `a` and `b`
190    /// children node hashes in the correct consensus order (i.e.
191    /// lexicographically).
192    pub fn right_branch(a: TapNodeHash, b: TapNodeHash) -> TapretNodePartner {
193        TapretNodePartner::RightBranch(TapretRightBranch::with(a, b))
194    }
195
196    /// Checks that the sibling data does not contain another tapret commitment.
197    ///
198    /// The check ensures that if the sibling data are present, their first 31
199    /// bytes are not equal to [`TAPRET_SCRIPT_COMMITMENT_PREFIX`], and if
200    /// the sibling is another node, the hash of its first child in the proof
201    /// is smaller than the hash of the other.
202    pub fn check_no_commitment(&self) -> bool {
203        match self {
204            TapretNodePartner::LeftNode(_) => true,
205            TapretNodePartner::RightLeaf(LeafScript { script, .. }) if script.len() < 64 => true,
206            TapretNodePartner::RightLeaf(LeafScript { script, .. }) => {
207                script[..31] != TAPRET_SCRIPT_COMMITMENT_PREFIX[..]
208            }
209            TapretNodePartner::RightBranch(right_branch) => {
210                right_branch.left_node_hash()[..31] != TAPRET_SCRIPT_COMMITMENT_PREFIX[..]
211            }
212        }
213    }
214
215    /// Checks that the sibling has a correct ordering regarding some other
216    /// node.
217    pub fn check_ordering(&self, other_node: TapNodeHash) -> bool {
218        match self {
219            TapretNodePartner::LeftNode(left_node) => *left_node <= other_node,
220            TapretNodePartner::RightLeaf(leaf_script) => {
221                let right_node = leaf_script.tap_leaf_hash().into_tap_hash();
222                other_node <= right_node
223            }
224            TapretNodePartner::RightBranch(right_branch) => {
225                let right_node = right_branch.node_hash();
226                other_node <= right_node
227            }
228        }
229    }
230
231    /// Computes node hash of the partner node defined by this proof.
232    pub fn tap_node_hash(&self) -> TapNodeHash {
233        match self {
234            TapretNodePartner::LeftNode(hash) => *hash,
235            TapretNodePartner::RightLeaf(leaf_script) => {
236                leaf_script.tap_leaf_hash().into_tap_hash()
237            }
238            TapretNodePartner::RightBranch(right_branch) => right_branch.node_hash(),
239        }
240    }
241}
242
243/// Structure proving that a merkle path to the tapret commitment inside the
244/// taproot script tree does not have an alternative commitment.
245///
246/// Holds information about the sibling at level 1 of the tree in form of
247/// [`TapretNodePartner`].
248#[derive(Getters, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
249#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
250#[strict_type(lib = LIB_NAME_BPCORE)]
251#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
252pub struct TapretPathProof {
253    /// Information about the sibling at level 1 of the tree
254    partner_node: Option<TapretNodePartner>,
255
256    /// A nonce value used to put the tapret commitment into the right side of
257    /// the tree.
258    #[getter(as_copy)]
259    nonce: u8,
260}
261
262// Used by PSBT tapret keys
263impl StrictSerialize for TapretPathProof {}
264impl StrictDeserialize for TapretPathProof {}
265
266impl TapretPathProof {
267    /// Construct new empty path proof.
268    #[inline]
269    pub fn root(nonce: u8) -> TapretPathProof {
270        TapretPathProof {
271            partner_node: None,
272            nonce,
273        }
274    }
275
276    /// Adds element to the path proof.
277    pub fn with(elem: TapretNodePartner, nonce: u8) -> Result<TapretPathProof, TapretPathError> {
278        if !elem.check_no_commitment() {
279            return Err(TapretPathError::InvalidNodePartner(elem));
280        }
281        Ok(TapretPathProof {
282            partner_node: Some(elem),
283            nonce,
284        })
285    }
286
287    /// Checks that the sibling data does not contain another tapret commitment
288    /// for any step of the mekrle path.
289    #[inline]
290    pub fn check_no_commitment(&self) -> bool {
291        self.partner_node.as_ref().map(TapretNodePartner::check_no_commitment).unwrap_or(true)
292    }
293
294    /// Returns original merkle root of the tree before deterministic bitcoin
295    /// commitment. If originally there was no script path spendings, returns
296    /// `None`.
297    #[inline]
298    pub fn original_merkle_root(&self) -> Option<TapNodeHash> {
299        self.partner_node.as_ref().map(|partner| partner.tap_node_hash())
300    }
301}
302
303/*
304
305impl IntoIterator for TapretPathProof {
306    type Item = TapretNodePartner;
307    type IntoIter = std::vec::IntoIter<TapretNodePartner>;
308
309    #[inline]
310    fn into_iter(self) -> Self::IntoIter {
311        self.0.into_iter()
312    }
313}
314
315impl<'data> IntoIterator for &'data TapretPathProof {
316    type Item = TapretNodePartner;
317    type IntoIter = core::slice::Iter<'data, TapretNodePartner>;
318
319    #[inline]
320    fn into_iter(self) -> Self::IntoIter {
321        self.0.iter()
322    }
323}
324
325 */
326
327/// Information proving tapret determinism for a given tapret commitment.
328/// Used both in the commitment procedure for PSBTs and in
329/// client-side-validation of the commitment.
330#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
331#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
332#[strict_type(lib = LIB_NAME_BPCORE)]
333#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
334pub struct TapretProof {
335    /// A merkle path to the commitment inside the taproot script tree. For
336    /// each node it also must hold information about the sibling in form of
337    /// [`TapretNodePartner`].
338    pub path_proof: TapretPathProof,
339
340    /// The internal key used by the taproot output.
341    ///
342    /// We need to keep this information client-side since it can't be
343    /// retrieved from the mined transaction.
344    pub internal_pk: InternalPk,
345}
346
347impl StrictSerialize for TapretProof {}
348impl StrictDeserialize for TapretProof {}
349
350impl TapretProof {
351    /// Restores original scripPubkey before deterministic bitcoin commitment
352    /// applied.
353    #[inline]
354    pub fn original_pubkey_script(&self) -> ScriptPubkey {
355        let merkle_root = self.path_proof.original_merkle_root();
356        ScriptPubkey::p2tr(self.internal_pk, merkle_root)
357    }
358
359    /// Verifies tapret commitment agains the proof.
360    pub fn verify(&self, msg: &Commitment, tx: &Tx) -> Result<(), ConvolveVerifyError> {
361        ConvolveCommitProof::<_, Tx, _>::verify(self, msg, tx)
362    }
363}