taproot_assets_core/verify/
tx.rs1extern crate alloc;
4
5use alloc::vec::Vec;
6
7use bitcoin::block::Header;
8use bitcoin::hashes::{Hash, sha256d::Hash as Sha256dHash};
9use bitcoin::{OutPoint, Script, Transaction, TxMerkleNode};
10use serde::{Deserialize, Serialize};
11use taproot_assets_types::asset::SerializedKey;
12use taproot_assets_types::proof::{Proof, TxMerkleProof};
13use thiserror::Error;
14
15use crate::{OpsError, TaprootOps};
16
17#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Error {
20 #[error("anchor tx missing prev out")]
22 AnchorTxMissingPrevOut,
23 #[error("outpoint hash does not match tx hash")]
25 OutpointHashMismatch,
26 #[error("output index {index} invalid for {output_count} outputs")]
28 OutputIndexInvalid {
29 index: u32,
31 output_count: usize,
33 },
34 #[error("output script does not match derived taproot output key")]
36 OutputScriptMismatch,
37 #[error("merkle proof shape mismatch: nodes={nodes}, bits={bits}")]
39 InvalidMerkleProofShape {
40 nodes: usize,
42 bits: usize,
44 },
45 #[error("invalid transaction merkle proof")]
47 InvalidTxMerkleProof,
48 #[error("invalid block header")]
50 InvalidBlockHeader,
51 #[error("invalid taproot output key")]
53 InvalidTaprootOutputKey,
54 #[error(transparent)]
56 Ops(#[from] OpsError),
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct VerifyMerkleProofInput {
62 pub txid: [u8; 32],
64 pub nodes: Vec<[u8; 32]>,
66 pub bits: Vec<bool>,
68 pub merkle_root: [u8; 32],
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct AnchorClaimInput {
75 pub anchor_tx: Transaction,
77 pub tx_merkle_proof: TxMerkleProof,
79 pub block_header: Header,
81 pub block_height: u32,
83 pub prev_out: OutPoint,
85 pub output_index: u32,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub struct AnchorClaimOutput {
92 pub anchor_txid: [u8; 32],
94 pub block_hash: [u8; 32],
96 pub block_height: u32,
98 pub output_index: u32,
100 pub taproot_output_key: [u8; 32],
102 pub p2tr_outputs: Vec<AnchorP2trOutput>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108pub struct AnchorP2trOutput {
109 pub output_index: u32,
111 pub taproot_output_key: [u8; 32],
113}
114
115pub trait MerkleHasher {
117 fn hash_nodes(&self, left: [u8; 32], right: [u8; 32]) -> [u8; 32];
119}
120
121#[derive(Debug, Clone, Copy, Default)]
123pub struct BitcoinMerkleHasher;
124
125impl MerkleHasher for BitcoinMerkleHasher {
126 fn hash_nodes(&self, left: [u8; 32], right: [u8; 32]) -> [u8; 32] {
128 let mut buf = [0u8; 64];
129 buf[..32].copy_from_slice(&left);
130 buf[32..].copy_from_slice(&right);
131 Sha256dHash::hash(&buf).to_byte_array()
132 }
133}
134
135pub trait HeaderVerifier {
137 fn verify_header(&self, header: &Header, height: u32) -> bool;
139}
140
141pub fn verify_anchor_tx<H: HeaderVerifier>(proof: &Proof, verifier: &H) -> Result<(), Error> {
143 if !tx_spends_prev_out(&proof.anchor_tx, &proof.prev_out) {
144 return Err(Error::AnchorTxMissingPrevOut);
145 }
146
147 verify_tx_merkle_proof(
148 &proof.anchor_tx,
149 &proof.tx_merkle_proof,
150 proof.block_header.merkle_root,
151 )?;
152
153 if !verifier.verify_header(&proof.block_header, proof.block_height) {
154 return Err(Error::InvalidBlockHeader);
155 }
156
157 Ok(())
158}
159
160pub fn verify_anchor_claim_with_hasher<H: MerkleHasher>(
162 input: &AnchorClaimInput,
163 hasher: &H,
164) -> Result<AnchorClaimOutput, Error> {
165 if !tx_spends_prev_out(&input.anchor_tx, &input.prev_out) {
166 return Err(Error::AnchorTxMissingPrevOut);
167 }
168
169 let nodes: Vec<[u8; 32]> = input
170 .tx_merkle_proof
171 .nodes
172 .iter()
173 .map(|node| node.to_byte_array())
174 .collect();
175
176 verify_tx_merkle_proof_with_hasher(
177 input.anchor_tx.compute_txid().to_byte_array(),
178 &nodes,
179 &input.tx_merkle_proof.bits,
180 input.block_header.merkle_root.to_byte_array(),
181 hasher,
182 )?;
183
184 if input.output_index as usize >= input.anchor_tx.output.len() {
185 return Err(Error::OutputIndexInvalid {
186 index: input.output_index,
187 output_count: input.anchor_tx.output.len(),
188 });
189 }
190
191 let output = &input.anchor_tx.output[input.output_index as usize];
192 let taproot_output_key = extract_taproot_output_key(output.script_pubkey.as_script())?;
193 let mut p2tr_outputs = Vec::new();
194 for (idx, output) in input.anchor_tx.output.iter().enumerate() {
195 if output.script_pubkey.is_p2tr() {
196 let taproot_output_key = extract_taproot_output_key(output.script_pubkey.as_script())?;
197 p2tr_outputs.push(AnchorP2trOutput {
198 output_index: idx as u32,
199 taproot_output_key,
200 });
201 }
202 }
203
204 Ok(AnchorClaimOutput {
205 anchor_txid: input.anchor_tx.compute_txid().to_byte_array(),
206 block_hash: input.block_header.block_hash().to_byte_array(),
207 block_height: input.block_height,
208 output_index: input.output_index,
209 taproot_output_key,
210 p2tr_outputs,
211 })
212}
213
214pub fn verify_tx_outpoint<O: TaprootOps>(
216 ops: &O,
217 tx: &Transaction,
218 outpoint: &OutPoint,
219 internal_key: &SerializedKey,
220 tapscript_root: Option<[u8; 32]>,
221) -> Result<(), Error> {
222 if outpoint.txid != tx.compute_txid() {
223 return Err(Error::OutpointHashMismatch);
224 }
225
226 if outpoint.vout as usize >= tx.output.len() {
227 return Err(Error::OutputIndexInvalid {
228 index: outpoint.vout,
229 output_count: tx.output.len(),
230 });
231 }
232
233 let output = &tx.output[outpoint.vout as usize];
234 let expected_key = derive_taproot_output_key(ops, internal_key, tapscript_root)?;
235 let expected_xonly = xonly_from_serialized_key(&expected_key)?;
236 let claimed_xonly = extract_taproot_output_key(output.script_pubkey.as_script())?;
237
238 if expected_xonly == claimed_xonly {
239 Ok(())
240 } else {
241 Err(Error::OutputScriptMismatch)
242 }
243}
244
245pub fn verify_tx_merkle_proof(
247 tx: &Transaction,
248 proof: &TxMerkleProof,
249 merkle_root: TxMerkleNode,
250) -> Result<(), Error> {
251 let nodes: Vec<[u8; 32]> = proof
252 .nodes
253 .iter()
254 .map(|node| node.to_byte_array())
255 .collect();
256 verify_tx_merkle_proof_with_hasher(
257 tx.compute_txid().to_byte_array(),
258 &nodes,
259 &proof.bits,
260 merkle_root.to_byte_array(),
261 &BitcoinMerkleHasher,
262 )
263}
264
265pub fn verify_tx_merkle_proof_input(input: &VerifyMerkleProofInput) -> Result<(), Error> {
267 verify_tx_merkle_proof_input_with_hasher(input, &BitcoinMerkleHasher)
268}
269
270pub fn verify_tx_merkle_proof_input_with_hasher<H: MerkleHasher>(
272 input: &VerifyMerkleProofInput,
273 hasher: &H,
274) -> Result<(), Error> {
275 verify_tx_merkle_proof_with_hasher(
276 input.txid,
277 &input.nodes,
278 &input.bits,
279 input.merkle_root,
280 hasher,
281 )
282}
283
284pub fn verify_tx_merkle_proof_with_hasher<H: MerkleHasher>(
286 txid: [u8; 32],
287 nodes: &[[u8; 32]],
288 bits: &[bool],
289 merkle_root: [u8; 32],
290 hasher: &H,
291) -> Result<(), Error> {
292 if nodes.len() != bits.len() {
293 return Err(Error::InvalidMerkleProofShape {
294 nodes: nodes.len(),
295 bits: bits.len(),
296 });
297 }
298
299 let mut current = txid;
300 for (node, is_right) in nodes.iter().zip(bits.iter()) {
301 let (left, right) = if *is_right {
302 (current, *node)
303 } else {
304 (*node, current)
305 };
306 current = hasher.hash_nodes(left, right);
307 }
308
309 if current == merkle_root {
310 Ok(())
311 } else {
312 Err(Error::InvalidTxMerkleProof)
313 }
314}
315
316pub fn tx_spends_prev_out(tx: &Transaction, prev_out: &OutPoint) -> bool {
318 tx.input
319 .iter()
320 .any(|input| input.previous_output == *prev_out)
321}
322
323fn derive_taproot_output_key<O: TaprootOps>(
325 ops: &O,
326 internal_key: &SerializedKey,
327 tapscript_root: Option<[u8; 32]>,
328) -> Result<SerializedKey, Error> {
329 let internal = ops.parse_internal_key(internal_key)?;
330 ops.taproot_output_key(&internal, tapscript_root)
331 .map_err(Error::from)
332}
333
334fn extract_taproot_output_key(script: &Script) -> Result<[u8; 32], Error> {
336 if !script.is_p2tr() {
337 return Err(Error::OutputScriptMismatch);
338 }
339
340 let bytes = script.as_bytes();
341 let mut key_bytes = [0u8; 32];
342 key_bytes.copy_from_slice(&bytes[2..34]);
343 Ok(key_bytes)
344}
345
346fn xonly_from_serialized_key(key: &SerializedKey) -> Result<[u8; 32], Error> {
348 match key.bytes[0] {
349 0x02 | 0x03 => {
350 let mut xonly = [0u8; 32];
351 xonly.copy_from_slice(&key.bytes[1..]);
352 Ok(xonly)
353 }
354 _ => Err(Error::InvalidTaprootOutputKey),
355 }
356}