1use crate::{verifier_config_for_log_size, Result, ZyncError};
19use sha2::{Digest, Sha256};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct FrostPublicKey(pub [u8; 32]);
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct FrostSignature {
28 pub r: [u8; 32],
29 pub s: [u8; 32],
30}
31
32impl FrostSignature {
33 pub fn is_present(&self) -> bool {
34 self.r != [0u8; 32] || self.s != [0u8; 32]
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct EpochCheckpoint {
41 pub epoch_index: u64,
42 pub height: u32,
43 pub block_hash: [u8; 32],
44 pub tree_root: [u8; 32],
45 pub nullifier_root: [u8; 32],
46 pub timestamp: u64,
47 pub signature: FrostSignature,
48 pub signer_set_id: [u8; 32],
49}
50
51impl EpochCheckpoint {
52 pub fn message_hash(&self) -> [u8; 32] {
54 let mut hasher = Sha256::new();
55 hasher.update(b"ZIDECAR_CHECKPOINT_V1");
56 hasher.update(self.epoch_index.to_le_bytes());
57 hasher.update(self.height.to_le_bytes());
58 hasher.update(&self.block_hash);
59 hasher.update(&self.tree_root);
60 hasher.update(&self.nullifier_root);
61 hasher.update(self.timestamp.to_le_bytes());
62 hasher.finalize().into()
63 }
64
65 pub fn verify(&self, public_key: &FrostPublicKey) -> Result<()> {
67 if !self.signature.is_present() {
68 return Err(ZyncError::Verification("checkpoint not signed".into()));
69 }
70
71 if self.signer_set_id == [0u8; 32] {
74 return Err(ZyncError::Verification("invalid signer set".into()));
75 }
76
77 let _ = public_key;
79
80 Ok(())
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct StateTransitionProof {
87 pub proof_bytes: Vec<u8>,
89 pub from_height: u32,
91 pub from_tree_root: [u8; 32],
92 pub from_nullifier_root: [u8; 32],
93 pub to_height: u32,
95 pub to_tree_root: [u8; 32],
96 pub to_nullifier_root: [u8; 32],
97 pub proof_log_size: u32,
99}
100
101impl StateTransitionProof {
102 pub fn verify(&self) -> Result<bool> {
104 use ligerito::{verify, FinalizedLigeritoProof};
105 use ligerito_binary_fields::{BinaryElem32, BinaryElem128};
106
107 if self.proof_bytes.is_empty() {
108 return Ok(false);
109 }
110
111 let proof: FinalizedLigeritoProof<BinaryElem32, BinaryElem128> =
113 bincode::deserialize(&self.proof_bytes)
114 .map_err(|e| ZyncError::Verification(format!("invalid proof: {}", e)))?;
115
116 let config = verifier_config_for_log_size(self.proof_log_size);
118
119 verify(&config, &proof)
121 .map_err(|e| ZyncError::Verification(format!("proof verification failed: {:?}", e)))
122 }
123}
124
125#[derive(Debug, Clone)]
127pub struct TrustlessStateProof {
128 pub checkpoint: EpochCheckpoint,
130 pub transition: StateTransitionProof,
132 pub current_height: u32,
134 pub current_hash: [u8; 32],
135}
136
137impl TrustlessStateProof {
138 pub fn verify(&self, signer_key: &FrostPublicKey) -> Result<VerifiedState> {
140 self.checkpoint.verify(signer_key)?;
142
143 if self.transition.from_height != self.checkpoint.height {
145 return Err(ZyncError::Verification(
146 "transition doesn't start from checkpoint".into(),
147 ));
148 }
149 if self.transition.from_tree_root != self.checkpoint.tree_root {
150 return Err(ZyncError::Verification(
151 "transition tree root doesn't match checkpoint".into(),
152 ));
153 }
154 if self.transition.from_nullifier_root != self.checkpoint.nullifier_root {
155 return Err(ZyncError::Verification(
156 "transition nullifier root doesn't match checkpoint".into(),
157 ));
158 }
159
160 if !self.transition.verify()? {
162 return Err(ZyncError::Verification(
163 "state transition proof invalid".into(),
164 ));
165 }
166
167 if self.transition.to_height != self.current_height {
169 return Err(ZyncError::Verification(
170 "transition doesn't reach current height".into(),
171 ));
172 }
173
174 Ok(VerifiedState {
175 height: self.current_height,
176 block_hash: self.current_hash,
177 tree_root: self.transition.to_tree_root,
178 nullifier_root: self.transition.to_nullifier_root,
179 checkpoint_epoch: self.checkpoint.epoch_index,
180 })
181 }
182}
183
184#[derive(Debug, Clone)]
186pub struct VerifiedState {
187 pub height: u32,
188 pub block_hash: [u8; 32],
189 pub tree_root: [u8; 32],
190 pub nullifier_root: [u8; 32],
191 pub checkpoint_epoch: u64,
192}
193
194#[derive(Debug, Clone)]
196pub struct NomtProof {
197 pub key: [u8; 32],
198 pub root: [u8; 32],
199 pub exists: bool,
200 pub path: Vec<[u8; 32]>,
201 pub indices: Vec<bool>,
202}
203
204impl NomtProof {
205 pub fn verify(&self) -> Result<bool> {
207 if self.path.is_empty() {
208 return Ok(self.root == [0u8; 32] && !self.exists);
210 }
211
212 let mut current = self.key;
214 for (sibling, is_right) in self.path.iter().zip(self.indices.iter()) {
215 let mut hasher = Sha256::new();
216 hasher.update(b"NOMT_NODE");
217 if *is_right {
218 hasher.update(sibling);
219 hasher.update(¤t);
220 } else {
221 hasher.update(¤t);
222 hasher.update(sibling);
223 }
224 current = hasher.finalize().into();
225 }
226
227 Ok(current == self.root)
228 }
229}
230
231#[derive(Debug, Clone)]
233pub struct CommitmentProof {
234 pub cmx: [u8; 32],
235 pub position: u64,
236 pub tree_root: [u8; 32],
237 pub proof: NomtProof,
238}
239
240impl CommitmentProof {
241 pub fn verify(&self) -> Result<bool> {
242 if !self.proof.exists {
243 return Ok(false);
244 }
245 self.proof.verify()
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct NullifierProof {
252 pub nullifier: [u8; 32],
253 pub nullifier_root: [u8; 32],
254 pub is_spent: bool,
255 pub proof: NomtProof,
256}
257
258impl NullifierProof {
259 pub fn verify(&self) -> Result<bool> {
260 if self.is_spent != self.proof.exists {
261 return Ok(false);
262 }
263 self.proof.verify()
264 }
265}
266
267pub struct SignerRegistry {
269 mainnet_key: FrostPublicKey,
271 testnet_key: FrostPublicKey,
273}
274
275impl SignerRegistry {
276 pub fn new() -> Self {
277 Self {
279 mainnet_key: FrostPublicKey([0x01; 32]),
280 testnet_key: FrostPublicKey([0x02; 32]),
281 }
282 }
283
284 pub fn mainnet_key(&self) -> &FrostPublicKey {
285 &self.mainnet_key
286 }
287
288 pub fn testnet_key(&self) -> &FrostPublicKey {
289 &self.testnet_key
290 }
291}
292
293impl Default for SignerRegistry {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn test_checkpoint_message_hash() {
305 let cp = EpochCheckpoint {
306 epoch_index: 1,
307 height: 1024,
308 block_hash: [0x11; 32],
309 tree_root: [0x22; 32],
310 nullifier_root: [0x33; 32],
311 timestamp: 1234567890,
312 signature: FrostSignature {
313 r: [0; 32],
314 s: [0; 32],
315 },
316 signer_set_id: [0; 32],
317 };
318
319 let hash1 = cp.message_hash();
320 let hash2 = cp.message_hash();
321 assert_eq!(hash1, hash2);
322
323 let cp2 = EpochCheckpoint {
325 epoch_index: 2,
326 ..cp
327 };
328 assert_ne!(cp.message_hash(), cp2.message_hash());
329 }
330
331 #[test]
332 fn test_frost_signature_present() {
333 let empty = FrostSignature {
334 r: [0; 32],
335 s: [0; 32],
336 };
337 assert!(!empty.is_present());
338
339 let present = FrostSignature {
340 r: [1; 32],
341 s: [0; 32],
342 };
343 assert!(present.is_present());
344 }
345}