zescrow_core/
condition.rs1use bincode::{Decode, Encode};
12#[cfg(feature = "json")]
13use serde::{Deserialize, Serialize};
14use sha2::{Digest, Sha256};
15
16use crate::Result;
17use crate::error::ConditionError;
18
19pub mod ed25519;
21pub mod hashlock;
23pub mod secp256k1;
25pub mod threshold;
27
28use ed25519::Ed25519;
29use hashlock::Hashlock;
30use secp256k1::Secp256k1;
31use threshold::Threshold;
32
33#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
35#[cfg_attr(
36 feature = "json",
37 serde(tag = "condition", content = "fulfillment", rename_all = "lowercase")
38)]
39#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
40pub enum Condition {
41 Hashlock(Hashlock),
43 Ed25519(Ed25519),
45 Secp256k1(Secp256k1),
47 Threshold(Threshold),
49}
50
51impl Condition {
52 #[inline]
62 pub fn verify(&self) -> Result<()> {
63 match self {
64 Self::Hashlock(hashlock) => hashlock.verify().map_err(ConditionError::Hashlock)?,
65 Self::Ed25519(ed25519) => ed25519.verify().map_err(ConditionError::Ed25519)?,
66 Self::Secp256k1(secp256k1) => secp256k1.verify().map_err(ConditionError::Secp256k1)?,
67 Self::Threshold(threshold) => threshold.verify().map_err(ConditionError::Threshold)?,
68 }
69 Ok(())
70 }
71
72 pub fn hashlock(hash: [u8; 32], preimage: Vec<u8>) -> Self {
74 Self::Hashlock(Hashlock { hash, preimage })
75 }
76
77 pub fn ed25519(public_key: [u8; 32], message: Vec<u8>, signature: Vec<u8>) -> Self {
79 Self::Ed25519(Ed25519 {
80 public_key,
81 signature,
82 message,
83 })
84 }
85
86 pub fn secp256k1(public_key: Vec<u8>, message: Vec<u8>, signature: Vec<u8>) -> Self {
88 Self::Secp256k1(Secp256k1 {
89 public_key,
90 signature,
91 message,
92 })
93 }
94
95 pub fn threshold(threshold: usize, subconditions: Vec<Self>) -> Self {
97 Self::Threshold(Threshold {
98 threshold,
99 subconditions,
100 })
101 }
102
103 pub fn commitment(&self) -> [u8; 32] {
106 let mut hasher = Sha256::new();
107 match self {
108 Self::Hashlock(hashlock) => {
109 hasher.update(b"ZESCROW_COND_HASHLOCK_V1");
110 hasher.update(hashlock.hash);
111 }
112 Self::Ed25519(ed25519) => {
113 hasher.update(b"ZESCROW_COND_ED25519_V1");
114 hasher.update(ed25519.public_key);
115 hasher.update(&ed25519.message);
116 }
117 Self::Secp256k1(secp256k1) => {
118 hasher.update(b"ZESCROW_COND_SECP256K1_V1");
119 hasher.update(&secp256k1.public_key);
120 hasher.update(&secp256k1.message);
121 }
122 Self::Threshold(threshold) => {
123 hasher.update(b"ZESCROW_COND_THRESHOLD_V1");
124 hasher.update(saturating_u32(threshold.threshold).to_be_bytes());
125 hasher.update(saturating_u32(threshold.subconditions.len()).to_be_bytes());
126 threshold
127 .subconditions
128 .iter()
129 .for_each(|sub| hasher.update(sub.commitment()));
130 }
131 }
132 hasher.finalize().into()
133 }
134}
135
136fn saturating_u32(value: usize) -> u32 {
140 u32::try_from(value).unwrap_or(u32::MAX)
141}
142
143#[cfg(feature = "json")]
144impl std::fmt::Display for Condition {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 let json = serde_json::to_string(self).map_err(|_| std::fmt::Error)?;
148 write!(f, "{json}")
149 }
150}
151
152#[cfg(test)]
153mod tests {
154
155 use sha2::{Digest, Sha256};
156
157 use super::*;
158
159 #[test]
160 fn preimage() {
161 let preimage = b"secret".to_vec();
162 let hash = Sha256::digest(&preimage).into();
163 let cond = Condition::hashlock(hash, preimage);
164 assert!(cond.verify().is_ok());
165
166 let cond = Condition::hashlock(hash, b"wrong-secret".to_vec());
168 assert!(cond.verify().is_err());
169 }
170
171 #[test]
172 fn ed25519() {
173 use ed25519_dalek::ed25519::signature::rand_core::OsRng;
174 use ed25519_dalek::{Signer, SigningKey};
175
176 let mut csprng = OsRng;
177 let sk: SigningKey = SigningKey::generate(&mut csprng);
178
179 let message = b"zkEscrow".to_vec();
180 let signature = sk.sign(&message).to_bytes().to_vec();
181 let public_key = sk.verifying_key().to_bytes();
182 let cond = Condition::ed25519(public_key, message.clone(), signature.clone());
183 assert!(cond.verify().is_ok());
184
185 let mut signature = signature;
187 signature[0] ^= 0xFF;
188 let cond = Condition::ed25519(public_key, message, signature);
189 assert!(cond.verify().is_err());
190 }
191
192 #[test]
193 fn secp256k1() {
194 use k256::ecdsa::signature::Signer;
195 use k256::ecdsa::{Signature, SigningKey};
196 use k256::elliptic_curve::rand_core::OsRng;
197
198 let sk = SigningKey::random(&mut OsRng);
199 let vk = sk.verifying_key();
200 let message = b"zkEscrow".to_vec();
201 let signature: Signature = sk.sign(&message);
202
203 let sig_bytes = signature.to_der().as_bytes().to_vec();
204 let pk_bytes = vk.to_encoded_point(false).as_bytes().to_vec();
205
206 let cond = Condition::secp256k1(pk_bytes.clone(), message, sig_bytes.clone());
207 assert!(cond.verify().is_ok());
208
209 let cond = Condition::secp256k1(pk_bytes, b"tampered".to_vec(), sig_bytes);
211 assert!(cond.verify().is_err());
212 }
213
214 #[test]
215 fn nonzero_threshold() {
216 let hash = Sha256::digest(b"zkEscrow").into();
218 let correct = Condition::hashlock(hash, b"zkEscrow".to_vec());
219 let wrong = Condition::hashlock(hash, b"wrong-preimage".to_vec());
220
221 let cond = Condition::threshold(1, vec![correct.clone(), wrong.clone()]);
223 assert!(cond.verify().is_ok());
224
225 let cond = Condition::threshold(2, vec![correct, wrong]);
227 assert!(cond.verify().is_err());
228
229 let cond = Condition::threshold(1, vec![]);
231 assert!(cond.verify().is_err());
232 }
233
234 #[test]
235 fn zero_threshold_rejected() {
236 let cond = Condition::threshold(0, vec![]);
238 assert!(cond.verify().is_err());
239
240 let preimage = b"zkEscrow".to_vec();
241 let hash = Sha256::digest(&preimage).into();
242 let subcond = Condition::hashlock(hash, preimage);
243 let cond = Condition::threshold(0, vec![subcond]);
244 assert!(cond.verify().is_err());
245 }
246
247 #[test]
248 fn threshold_exceeding_subconditions_rejected() {
249 let preimage = b"zkEscrow".to_vec();
250 let hash = Sha256::digest(&preimage).into();
251 let subcond = Condition::hashlock(hash, preimage);
252 let cond = Condition::threshold(2, vec![subcond]);
254 assert!(cond.verify().is_err());
255 }
256
257 #[test]
258 fn nested_thresholds() {
259 let preimage = b"zkEscrow".to_vec();
260 let hash = Sha256::digest(&preimage).into();
261 let leaf = Condition::hashlock(hash, preimage);
262
263 let inner = Condition::threshold(1, vec![leaf.clone()]);
265 let outer = Condition::threshold(1, vec![inner]);
267 assert!(outer.verify().is_ok());
268
269 let wrong_leaf = Condition::hashlock(hash, b"wrong-preimage".to_vec());
271 let inner2 = Condition::threshold(1, vec![wrong_leaf]);
272 let outer2 = Condition::threshold(1, vec![inner2]);
273 assert!(outer2.verify().is_err());
274 }
275
276 #[cfg(feature = "json")]
277 #[test]
278 fn json_roundtrip_hashlock() {
279 let preimage = b"secret".to_vec();
280 let hash = Sha256::digest(&preimage).into();
281 let cond = Condition::hashlock(hash, preimage);
282 let json = serde_json::to_string(&cond).unwrap();
283 let decoded: Condition = serde_json::from_str(&json).unwrap();
284 assert_eq!(decoded, cond);
285 }
286
287 #[cfg(feature = "json")]
288 #[test]
289 fn json_roundtrip_threshold() {
290 let preimage = b"nested".to_vec();
291 let hash = Sha256::digest(&preimage).into();
292 let inner = Condition::hashlock(hash, preimage);
293 let cond = Condition::threshold(1, vec![inner]);
294 let json = serde_json::to_string(&cond).unwrap();
295 let decoded: Condition = serde_json::from_str(&json).unwrap();
296 assert_eq!(decoded, cond);
297 }
298}