zescrow_core/condition/hashlock.rs
1use bincode::{Decode, Encode};
2#[cfg(feature = "json")]
3use hex::serde as hex_serde;
4#[cfg(feature = "json")]
5use serde::{Deserialize, Serialize};
6use sha2::{Digest, Sha256};
7use subtle::ConstantTimeEq;
8
9#[cfg(feature = "json")]
10use crate::serde::utf8_serde;
11
12/// A hashlock condition requiring SHA-256 preimage verification.
13///
14/// The condition is satisfied when `SHA-256(preimage) == hash`.
15///
16/// # Example
17///
18/// ```
19/// use sha2::{Digest, Sha256};
20/// use zescrow_core::Condition;
21///
22/// let preimage = b"my-secret-preimage".to_vec();
23/// let hash: [u8; 32] = Sha256::digest(&preimage).into();
24///
25/// let condition = Condition::hashlock(hash, preimage);
26/// assert!(condition.verify().is_ok());
27/// ```
28#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
29#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
30pub struct Hashlock {
31 /// The expected SHA-256 digest of the preimage.
32 #[cfg_attr(feature = "json", serde(with = "hex_serde"))]
33 pub hash: [u8; 32],
34
35 /// Secret preimage as UTF-8 string.
36 #[cfg_attr(feature = "json", serde(with = "utf8_serde"))]
37 pub preimage: Vec<u8>,
38}
39
40impl Hashlock {
41 /// Verifies that `SHA-256(preimage) == hash` using constant-time comparison.
42 ///
43 /// # Errors
44 ///
45 /// Returns [`Error::Mismatch`] if the computed hash does not match.
46 pub fn verify(&self) -> Result<(), Error> {
47 let computed = Sha256::digest(&self.preimage);
48 computed[..]
49 .ct_eq(&self.hash)
50 .unwrap_u8()
51 .eq(&1)
52 .then_some(())
53 .ok_or(Error::Mismatch)
54 }
55}
56
57/// Errors from hashlock verification.
58#[derive(Debug, thiserror::Error)]
59pub enum Error {
60 /// The provided preimage did not hash to the expected value.
61 #[error("SHA256(preimage) != hash")]
62 Mismatch,
63}