csv_adapter_core/
tapret_verify.rs1use bitcoin::hashes::Hash as _;
15use bitcoin::key::{TapTweak, XOnlyPublicKey};
16use bitcoin::opcodes::all::OP_RETURN;
17use bitcoin::script::{Builder, PushBytesBuf, ScriptBuf};
18use bitcoin::secp256k1::{Secp256k1, Verification};
19use bitcoin::taproot::TapNodeHash;
20use sha2::{Digest, Sha256};
21
22use crate::hash::Hash;
23
24#[derive(Debug)]
26pub struct TapretVerificationResult {
27 pub is_valid: bool,
29 pub output_key: Option<[u8; 32]>,
31 pub internal_key: Option<[u8; 32]>,
33 pub commitment_found: bool,
35 pub script_valid: bool,
37 pub error: Option<String>,
39}
40
41pub fn verify_tapret_script(
58 tapret_script: &ScriptBuf,
59 expected_commitment: Hash,
60) -> TapretVerificationResult {
61 let script_valid = verify_tapret_script_structure(tapret_script);
63
64 if !script_valid {
65 return TapretVerificationResult {
66 is_valid: false,
67 output_key: None,
68 internal_key: None,
69 commitment_found: false,
70 script_valid: false,
71 error: Some("Invalid Tapret script structure".to_string()),
72 };
73 }
74
75 let commitment_found = verify_commitment_in_script(tapret_script, expected_commitment);
77
78 if !commitment_found {
79 return TapretVerificationResult {
80 is_valid: false,
81 output_key: None,
82 internal_key: None,
83 commitment_found: false,
84 script_valid: true,
85 error: Some("Commitment not found in Tapret script".to_string()),
86 };
87 }
88
89 TapretVerificationResult {
90 is_valid: true,
91 output_key: None,
92 internal_key: None,
93 commitment_found: true,
94 script_valid: true,
95 error: None,
96 }
97}
98
99pub fn verify_tapret_output_key<C: Verification>(
112 secp: &Secp256k1<C>,
113 internal_key: XOnlyPublicKey,
114 merkle_root: Option<[u8; 32]>,
115 expected_output_key: XOnlyPublicKey,
116) -> bool {
117 let merkle_root_hash = merkle_root.map(TapNodeHash::from_byte_array);
118
119 let (tweaked_key, _parity) = internal_key.tap_tweak(secp, merkle_root_hash);
120 let tweaked_xonly = tweaked_key.to_inner();
121
122 tweaked_xonly == expected_output_key
123}
124
125fn verify_tapret_script_structure(script: &ScriptBuf) -> bool {
130 let bytes = script.as_bytes();
131
132 if bytes.len() < 67 {
134 return false;
135 }
136
137 if bytes[0] != 0x6a {
139 return false;
140 }
141
142 if bytes[1] != 0x41 {
144 return false;
145 }
146
147 bytes.len() == 67
149}
150
151fn verify_commitment_in_script(script: &ScriptBuf, expected: Hash) -> bool {
156 let bytes = script.as_bytes();
157
158 if bytes.len() < 67 {
159 return false;
160 }
161
162 let commitment_offset = 2 + 33;
164
165 if bytes.len() < commitment_offset + 32 {
166 return false;
167 }
168
169 let embedded_commitment = &bytes[commitment_offset..commitment_offset + 32];
170 embedded_commitment == expected.as_bytes()
171}
172
173pub fn create_tapret_script(protocol_id: [u8; 32], nonce: u8, commitment: Hash) -> ScriptBuf {
180 let mut data = [0u8; 65];
181 data[..32].copy_from_slice(&protocol_id);
182 data[32] = nonce;
183 data[33..65].copy_from_slice(commitment.as_bytes());
184
185 let push_bytes = PushBytesBuf::try_from(data.to_vec()).unwrap();
186 Builder::new()
187 .push_opcode(OP_RETURN)
188 .push_slice(push_bytes)
189 .into_script()
190}
191
192pub fn compute_tap_tweak_hash(internal_key: [u8; 32], merkle_root: Option<[u8; 32]>) -> [u8; 32] {
196 let mut hasher = Sha256::new();
197 hasher.update(internal_key);
198 if let Some(root) = merkle_root {
199 hasher.update(root);
200 }
201 hasher.finalize().into()
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_valid_tapret_script_structure() {
210 let commitment = Hash::new([0xAB; 32]);
211 let script = create_tapret_script([0x01; 32], 0x42, commitment);
212 assert!(verify_tapret_script_structure(&script));
213 }
214
215 #[test]
216 fn test_invalid_script_too_short() {
217 let short_script = ScriptBuf::from_bytes(vec![0x6a, 0x41, 0x01]);
218 assert!(!verify_tapret_script_structure(&short_script));
219 }
220
221 #[test]
222 fn test_invalid_script_not_op_return() {
223 let script = ScriptBuf::from_bytes(vec![0x00; 67]);
224 assert!(!verify_tapret_script_structure(&script));
225 }
226
227 #[test]
228 fn test_invalid_script_wrong_push() {
229 let mut bytes = vec![0x6a, 0x40]; bytes.resize(67, 0x00);
231 let script = ScriptBuf::from_bytes(bytes);
232 assert!(!verify_tapret_script_structure(&script));
233 }
234
235 #[test]
236 fn test_commitment_in_script() {
237 let commitment = Hash::new([0xCD; 32]);
238 let script = create_tapret_script([0x01; 32], 0x42, commitment);
239 assert!(verify_commitment_in_script(&script, commitment));
240
241 let wrong_commitment = Hash::new([0xFF; 32]);
243 assert!(!verify_commitment_in_script(&script, wrong_commitment));
244 }
245
246 #[test]
247 fn test_full_tapret_verification_valid() {
248 let commitment = Hash::new([0xAB; 32]);
249 let script = create_tapret_script([0x01; 32], 0x42, commitment);
250 let result = verify_tapret_script(&script, commitment);
251 assert!(result.is_valid);
252 assert!(result.commitment_found);
253 assert!(result.script_valid);
254 assert!(result.error.is_none());
255 }
256
257 #[test]
258 fn test_full_tapret_verification_wrong_commitment() {
259 let commitment = Hash::new([0xAB; 32]);
260 let script = create_tapret_script([0x01; 32], 0x42, commitment);
261 let wrong_commitment = Hash::new([0xFF; 32]);
262 let result = verify_tapret_script(&script, wrong_commitment);
263 assert!(!result.is_valid);
264 assert!(!result.commitment_found);
265 assert!(result.script_valid);
266 }
267
268 #[test]
269 fn test_tap_tweak_hash_deterministic() {
270 let key = [0x01; 32];
271 let root = Some([0x02; 32]);
272
273 let h1 = compute_tap_tweak_hash(key, root);
274 let h2 = compute_tap_tweak_hash(key, root);
275 assert_eq!(h1, h2);
276 }
277
278 #[test]
279 fn test_tap_tweak_hash_different_roots() {
280 let key = [0x01; 32];
281
282 let h1 = compute_tap_tweak_hash(key, Some([0x02; 32]));
283 let h2 = compute_tap_tweak_hash(key, Some([0x03; 32]));
284 assert_ne!(h1, h2);
285 }
286
287 #[test]
288 fn test_tap_tweak_hash_no_root() {
289 let key = [0x01; 32];
290 let h = compute_tap_tweak_hash(key, None);
291 assert_ne!(h, [0u8; 32]);
292 }
293}