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}