extern crate alloc;
use alloc::vec::Vec;
use bitcoin::block::Header;
use bitcoin::hashes::{Hash, sha256d::Hash as Sha256dHash};
use bitcoin::{OutPoint, Script, Transaction, TxMerkleNode};
use serde::{Deserialize, Serialize};
use taproot_assets_types::asset::SerializedKey;
use taproot_assets_types::proof::{Proof, TxMerkleProof};
use thiserror::Error;
use crate::{OpsError, TaprootOps};
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
#[error("anchor tx missing prev out")]
AnchorTxMissingPrevOut,
#[error("outpoint hash does not match tx hash")]
OutpointHashMismatch,
#[error("output index {index} invalid for {output_count} outputs")]
OutputIndexInvalid {
index: u32,
output_count: usize,
},
#[error("output script does not match derived taproot output key")]
OutputScriptMismatch,
#[error("merkle proof shape mismatch: nodes={nodes}, bits={bits}")]
InvalidMerkleProofShape {
nodes: usize,
bits: usize,
},
#[error("invalid transaction merkle proof")]
InvalidTxMerkleProof,
#[error("invalid block header")]
InvalidBlockHeader,
#[error("invalid taproot output key")]
InvalidTaprootOutputKey,
#[error(transparent)]
Ops(#[from] OpsError),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VerifyMerkleProofInput {
pub txid: [u8; 32],
pub nodes: Vec<[u8; 32]>,
pub bits: Vec<bool>,
pub merkle_root: [u8; 32],
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnchorClaimInput {
pub anchor_tx: Transaction,
pub tx_merkle_proof: TxMerkleProof,
pub block_header: Header,
pub block_height: u32,
pub prev_out: OutPoint,
pub output_index: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnchorClaimOutput {
pub anchor_txid: [u8; 32],
pub block_hash: [u8; 32],
pub block_height: u32,
pub output_index: u32,
pub taproot_output_key: [u8; 32],
pub p2tr_outputs: Vec<AnchorP2trOutput>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnchorP2trOutput {
pub output_index: u32,
pub taproot_output_key: [u8; 32],
}
pub trait MerkleHasher {
fn hash_nodes(&self, left: [u8; 32], right: [u8; 32]) -> [u8; 32];
}
#[derive(Debug, Clone, Copy, Default)]
pub struct BitcoinMerkleHasher;
impl MerkleHasher for BitcoinMerkleHasher {
fn hash_nodes(&self, left: [u8; 32], right: [u8; 32]) -> [u8; 32] {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(&left);
buf[32..].copy_from_slice(&right);
Sha256dHash::hash(&buf).to_byte_array()
}
}
pub trait HeaderVerifier {
fn verify_header(&self, header: &Header, height: u32) -> bool;
}
pub fn verify_anchor_tx<H: HeaderVerifier>(proof: &Proof, verifier: &H) -> Result<(), Error> {
if !tx_spends_prev_out(&proof.anchor_tx, &proof.prev_out) {
return Err(Error::AnchorTxMissingPrevOut);
}
verify_tx_merkle_proof(
&proof.anchor_tx,
&proof.tx_merkle_proof,
proof.block_header.merkle_root,
)?;
if !verifier.verify_header(&proof.block_header, proof.block_height) {
return Err(Error::InvalidBlockHeader);
}
Ok(())
}
pub fn verify_anchor_claim_with_hasher<H: MerkleHasher>(
input: &AnchorClaimInput,
hasher: &H,
) -> Result<AnchorClaimOutput, Error> {
if !tx_spends_prev_out(&input.anchor_tx, &input.prev_out) {
return Err(Error::AnchorTxMissingPrevOut);
}
let nodes: Vec<[u8; 32]> = input
.tx_merkle_proof
.nodes
.iter()
.map(|node| node.to_byte_array())
.collect();
verify_tx_merkle_proof_with_hasher(
input.anchor_tx.compute_txid().to_byte_array(),
&nodes,
&input.tx_merkle_proof.bits,
input.block_header.merkle_root.to_byte_array(),
hasher,
)?;
if input.output_index as usize >= input.anchor_tx.output.len() {
return Err(Error::OutputIndexInvalid {
index: input.output_index,
output_count: input.anchor_tx.output.len(),
});
}
let output = &input.anchor_tx.output[input.output_index as usize];
let taproot_output_key = extract_taproot_output_key(output.script_pubkey.as_script())?;
let mut p2tr_outputs = Vec::new();
for (idx, output) in input.anchor_tx.output.iter().enumerate() {
if output.script_pubkey.is_p2tr() {
let taproot_output_key = extract_taproot_output_key(output.script_pubkey.as_script())?;
p2tr_outputs.push(AnchorP2trOutput {
output_index: idx as u32,
taproot_output_key,
});
}
}
Ok(AnchorClaimOutput {
anchor_txid: input.anchor_tx.compute_txid().to_byte_array(),
block_hash: input.block_header.block_hash().to_byte_array(),
block_height: input.block_height,
output_index: input.output_index,
taproot_output_key,
p2tr_outputs,
})
}
pub fn verify_tx_outpoint<O: TaprootOps>(
ops: &O,
tx: &Transaction,
outpoint: &OutPoint,
internal_key: &SerializedKey,
tapscript_root: Option<[u8; 32]>,
) -> Result<(), Error> {
if outpoint.txid != tx.compute_txid() {
return Err(Error::OutpointHashMismatch);
}
if outpoint.vout as usize >= tx.output.len() {
return Err(Error::OutputIndexInvalid {
index: outpoint.vout,
output_count: tx.output.len(),
});
}
let output = &tx.output[outpoint.vout as usize];
let expected_key = derive_taproot_output_key(ops, internal_key, tapscript_root)?;
let expected_xonly = xonly_from_serialized_key(&expected_key)?;
let claimed_xonly = extract_taproot_output_key(output.script_pubkey.as_script())?;
if expected_xonly == claimed_xonly {
Ok(())
} else {
Err(Error::OutputScriptMismatch)
}
}
pub fn verify_tx_merkle_proof(
tx: &Transaction,
proof: &TxMerkleProof,
merkle_root: TxMerkleNode,
) -> Result<(), Error> {
let nodes: Vec<[u8; 32]> = proof
.nodes
.iter()
.map(|node| node.to_byte_array())
.collect();
verify_tx_merkle_proof_with_hasher(
tx.compute_txid().to_byte_array(),
&nodes,
&proof.bits,
merkle_root.to_byte_array(),
&BitcoinMerkleHasher,
)
}
pub fn verify_tx_merkle_proof_input(input: &VerifyMerkleProofInput) -> Result<(), Error> {
verify_tx_merkle_proof_input_with_hasher(input, &BitcoinMerkleHasher)
}
pub fn verify_tx_merkle_proof_input_with_hasher<H: MerkleHasher>(
input: &VerifyMerkleProofInput,
hasher: &H,
) -> Result<(), Error> {
verify_tx_merkle_proof_with_hasher(
input.txid,
&input.nodes,
&input.bits,
input.merkle_root,
hasher,
)
}
pub fn verify_tx_merkle_proof_with_hasher<H: MerkleHasher>(
txid: [u8; 32],
nodes: &[[u8; 32]],
bits: &[bool],
merkle_root: [u8; 32],
hasher: &H,
) -> Result<(), Error> {
if nodes.len() != bits.len() {
return Err(Error::InvalidMerkleProofShape {
nodes: nodes.len(),
bits: bits.len(),
});
}
let mut current = txid;
for (node, is_right) in nodes.iter().zip(bits.iter()) {
let (left, right) = if *is_right {
(current, *node)
} else {
(*node, current)
};
current = hasher.hash_nodes(left, right);
}
if current == merkle_root {
Ok(())
} else {
Err(Error::InvalidTxMerkleProof)
}
}
pub fn tx_spends_prev_out(tx: &Transaction, prev_out: &OutPoint) -> bool {
tx.input
.iter()
.any(|input| input.previous_output == *prev_out)
}
fn derive_taproot_output_key<O: TaprootOps>(
ops: &O,
internal_key: &SerializedKey,
tapscript_root: Option<[u8; 32]>,
) -> Result<SerializedKey, Error> {
let internal = ops.parse_internal_key(internal_key)?;
ops.taproot_output_key(&internal, tapscript_root)
.map_err(Error::from)
}
fn extract_taproot_output_key(script: &Script) -> Result<[u8; 32], Error> {
if !script.is_p2tr() {
return Err(Error::OutputScriptMismatch);
}
let bytes = script.as_bytes();
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(&bytes[2..34]);
Ok(key_bytes)
}
fn xonly_from_serialized_key(key: &SerializedKey) -> Result<[u8; 32], Error> {
match key.bytes[0] {
0x02 | 0x03 => {
let mut xonly = [0u8; 32];
xonly.copy_from_slice(&key.bytes[1..]);
Ok(xonly)
}
_ => Err(Error::InvalidTaprootOutputKey),
}
}