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}