Skip to main content

taproot_assets/
verify.rs

1//! Host-side verification utilities backed by bitcoin/secp256k1.
2
3use bitcoin::TapNodeHash;
4use bitcoin::hashes::Hash;
5use bitcoin::key::TapTweak;
6use bitcoin::secp256k1::{self, PublicKey as SecpPublicKey, Scalar, Secp256k1};
7use taproot_assets_core::{OpsError, TaprootOps, verify, verify::group_key_reveal};
8use taproot_assets_types::asset::{AssetID, GroupKeyReveal, SerializedKey};
9use taproot_assets_types::proof::Proof;
10
11/// Taproot operations implemented with bitcoin/secp256k1 types.
12#[derive(Debug)]
13pub struct BitcoinTaprootOps {
14    /// Secp256k1 context used for verification-only operations.
15    secp: Secp256k1<secp256k1::VerifyOnly>,
16}
17
18impl BitcoinTaprootOps {
19    /// Creates a new Taproot operations backend.
20    pub fn new() -> Self {
21        Self {
22            secp: Secp256k1::verification_only(),
23        }
24    }
25}
26
27impl TaprootOps for BitcoinTaprootOps {
28    type PubKey = SecpPublicKey;
29
30    /// Parses a raw group key into the backend representation.
31    fn parse_group_key(&self, key: &SerializedKey) -> Result<Self::PubKey, OpsError> {
32        SecpPublicKey::from_slice(&key.bytes).map_err(|_| OpsError::InvalidRawGroupKey)
33    }
34
35    /// Parses an internal key into the backend representation.
36    fn parse_internal_key(&self, key: &SerializedKey) -> Result<Self::PubKey, OpsError> {
37        SecpPublicKey::from_slice(&key.bytes).map_err(|_| OpsError::InvalidInternalKey)
38    }
39
40    /// Adds a scalar tweak to a public key.
41    fn add_tweak(&self, pubkey: &Self::PubKey, tweak: [u8; 32]) -> Result<Self::PubKey, OpsError> {
42        let tweak = Scalar::from_be_bytes(tweak).map_err(|_| OpsError::AssetIdTweakOutOfRange)?;
43        pubkey
44            .add_exp_tweak(&self.secp, &tweak)
45            .map_err(|_| OpsError::InvalidGroupKeyTweak)
46    }
47
48    /// Computes the Taproot output key for an internal key and optional tapscript root.
49    fn taproot_output_key(
50        &self,
51        internal_key: &Self::PubKey,
52        tapscript_root: Option<[u8; 32]>,
53    ) -> Result<SerializedKey, OpsError> {
54        let merkle_root = tapscript_root.map(TapNodeHash::from_byte_array);
55        let (xonly_key, _) = internal_key.x_only_public_key();
56        let (tweaked, parity) = xonly_key.tap_tweak(&self.secp, merkle_root);
57        let output_key =
58            SecpPublicKey::from_x_only_public_key(tweaked.to_x_only_public_key(), parity);
59
60        Ok(SerializedKey {
61            bytes: output_key.serialize(),
62        })
63    }
64}
65
66/// Derives the compressed group key bytes using the bitcoin backend.
67pub fn group_pubkey_from_reveal(
68    reveal: &GroupKeyReveal,
69    asset_id: &AssetID,
70) -> Result<SerializedKey, verify::Error> {
71    let ops = BitcoinTaprootOps::new();
72    group_key_reveal::group_pubkey_from_reveal(&ops, reveal, asset_id).map_err(verify::Error::from)
73}
74
75/// Verifies that the group key reveal derives the asset's group key using the bitcoin backend.
76pub fn verify_group_key_reveal(proof: &Proof) -> Result<(), verify::Error> {
77    let ops = BitcoinTaprootOps::new();
78    group_key_reveal::verify_group_key_reveal(&ops, proof).map_err(verify::Error::from)
79}