taproot_assets_core/verify/
group_key_reveal.rs1use bitcoin::hashes::Hash;
4use taproot_assets_types::asset::{
5 Asset, AssetID, GroupKeyReveal, NonSpendLeafVersion, SerializedKey,
6};
7use taproot_assets_types::proof::Proof;
8
9use crate::{OpsError, TaprootOps};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Error {
14 MissingGroupKey,
16 MissingGenesis,
18 InvalidGroupKeyLength {
20 expected: usize,
22 actual: usize,
24 },
25 GroupKeyMismatch,
27 MissingTapscriptRoot {
29 version: NonSpendLeafVersion,
31 },
32 Ops(OpsError),
34}
35
36impl From<OpsError> for Error {
37 fn from(err: OpsError) -> Self {
39 Self::Ops(err)
40 }
41}
42
43impl core::fmt::Display for Error {
44 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 match self {
47 Error::MissingGroupKey => {
48 write!(f, "group key reveal present but asset has no group key")
49 }
50 Error::MissingGenesis => write!(f, "group key reveal requires genesis information"),
51 Error::InvalidGroupKeyLength { expected, actual } => write!(
52 f,
53 "asset group key length must be {}, got {}",
54 expected, actual
55 ),
56 Error::GroupKeyMismatch => write!(f, "group key reveal mismatch"),
57 Error::MissingTapscriptRoot { version } => write!(
58 f,
59 "missing tapscript root for group key reveal version {:?}",
60 version
61 ),
62 Error::Ops(err) => core::fmt::Display::fmt(err, f),
63 }
64 }
65}
66
67pub fn group_pubkey_from_reveal<O: TaprootOps>(
69 ops: &O,
70 reveal: &GroupKeyReveal,
71 asset_id: &AssetID,
72) -> Result<SerializedKey, Error> {
73 let tapscript_root = reveal.tapscript_root.map(|root| root.to_byte_array());
74 let custom_subtree_root = reveal.custom_subtree_root.map(|root| root.to_byte_array());
75
76 match reveal.version {
77 None => derive_group_pubkey_v0(ops, &reveal.raw_group_key, asset_id, tapscript_root),
78 Some(version) => derive_group_pubkey_v1(
79 ops,
80 version,
81 &reveal.raw_group_key,
82 asset_id,
83 tapscript_root,
84 custom_subtree_root,
85 ),
86 }
87}
88
89pub fn verify_group_key_reveal<O: TaprootOps>(ops: &O, proof: &Proof) -> Result<(), Error> {
91 verify_group_key_reveal_with_asset(ops, &proof.asset, proof.group_key_reveal.as_ref())
92 .map(|_| ())
93}
94
95pub fn verify_group_key_reveal_with_asset<O: TaprootOps>(
97 ops: &O,
98 asset: &Asset,
99 reveal: Option<&GroupKeyReveal>,
100) -> Result<Option<SerializedKey>, Error> {
101 let reveal = match reveal {
102 Some(reveal) => reveal,
103 None => return Ok(None),
104 };
105 let asset_group = asset.asset_group.as_ref().ok_or(Error::MissingGroupKey)?;
106 let asset_id = asset
107 .asset_genesis
108 .as_ref()
109 .ok_or(Error::MissingGenesis)?
110 .asset_id;
111 let expected_key = if !asset_group.tweaked_group_key.is_empty() {
112 &asset_group.tweaked_group_key
113 } else {
114 &asset_group.raw_group_key
115 };
116 if expected_key.len() != 33 {
117 return Err(Error::InvalidGroupKeyLength {
118 expected: 33,
119 actual: expected_key.len(),
120 });
121 }
122
123 let derived_key = group_pubkey_from_reveal(ops, reveal, &asset_id)?;
124 if expected_key.as_slice() != &derived_key.bytes[..] {
125 return Err(Error::GroupKeyMismatch);
126 }
127
128 Ok(Some(derived_key))
129}
130
131fn derive_group_pubkey_v0<O: TaprootOps>(
133 ops: &O,
134 raw_key: &SerializedKey,
135 asset_id: &AssetID,
136 tapscript_root: Option<[u8; 32]>,
137) -> Result<SerializedKey, Error> {
138 let raw_pubkey = ops.parse_group_key(raw_key)?;
139 let tweak = asset_id.to_byte_array();
140 let internal_key = ops.add_tweak(&raw_pubkey, tweak)?;
141
142 let output_key = ops.taproot_output_key(&internal_key, tapscript_root)?;
143 Ok(output_key)
144}
145
146fn derive_group_pubkey_v1<O: TaprootOps>(
148 ops: &O,
149 version: NonSpendLeafVersion,
150 internal_key: &SerializedKey,
151 _asset_id: &AssetID,
152 tapscript_root: Option<[u8; 32]>,
153 _custom_subtree_root: Option<[u8; 32]>,
154) -> Result<SerializedKey, Error> {
155 let root = tapscript_root.ok_or(Error::MissingTapscriptRoot { version })?;
156 let internal_pubkey = ops.parse_internal_key(internal_key)?;
157
158 let output_key = ops.taproot_output_key(&internal_pubkey, Some(root))?;
159 Ok(output_key)
160}