cpop_protocol/rfc/
checkpoint.rs1use serde::{Deserialize, Serialize};
22use uuid::Uuid;
23
24use super::fixed_point::{Millibits, RhoMillibits};
25use super::jitter_binding::JitterBinding;
26use super::serde_helpers::{hex_bytes, hex_bytes_32_opt, hex_bytes_vec};
27use super::vdf::VdfProofRfc;
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct CheckpointRfc {
35 #[serde(rename = "1")]
36 pub sequence: u64,
37
38 #[serde(rename = "2")]
39 pub checkpoint_id: Uuid,
40
41 #[serde(rename = "3")]
42 pub timestamp: u64,
43
44 #[serde(rename = "4", with = "hex_bytes")]
45 pub content_hash: [u8; 32],
46
47 #[serde(rename = "5", with = "hex_bytes")]
49 pub prev_hash: [u8; 32],
50
51 #[serde(rename = "6", with = "hex_bytes")]
52 pub checkpoint_hash: [u8; 32],
53
54 #[serde(rename = "7", skip_serializing_if = "Option::is_none")]
55 pub vdf_proof: Option<VdfProofRfc>,
56
57 #[serde(rename = "8", skip_serializing_if = "Option::is_none")]
58 pub jitter_binding: Option<JitterBinding>,
59
60 #[serde(
61 rename = "9",
62 skip_serializing_if = "Option::is_none",
63 with = "hex_bytes_32_opt"
64 )]
65 pub chain_mac: Option<[u8; 32]>,
66}
67
68impl CheckpointRfc {
69 pub fn new(sequence: u64, timestamp: u64, content_hash: [u8; 32], prev_hash: [u8; 32]) -> Self {
71 Self {
72 sequence,
73 checkpoint_id: Uuid::new_v4(),
74 timestamp,
75 content_hash,
76 prev_hash,
77 checkpoint_hash: [0u8; 32],
78 vdf_proof: None,
79 jitter_binding: None,
80 chain_mac: None,
81 }
82 }
83
84 pub fn with_vdf(mut self, proof: VdfProofRfc) -> Self {
86 self.vdf_proof = Some(proof);
87 self
88 }
89
90 pub fn with_jitter(mut self, binding: JitterBinding) -> Self {
92 self.jitter_binding = Some(binding);
93 self
94 }
95
96 pub fn with_chain_mac(mut self, mac: [u8; 32]) -> Self {
98 self.chain_mac = Some(mac);
99 self
100 }
101
102 pub fn compute_hash(&mut self) {
104 use sha2::{Digest, Sha256};
105
106 let mut hasher = Sha256::new();
107
108 hasher.update(b"witnessd-checkpoint-v3");
109
110 hasher.update(self.sequence.to_be_bytes());
111 hasher.update(self.checkpoint_id.as_bytes());
112 hasher.update(self.timestamp.to_be_bytes());
113 hasher.update(self.content_hash);
114 hasher.update(self.prev_hash);
115
116 if let Some(vdf) = &self.vdf_proof {
117 hasher.update(b"\x01");
118 hasher.update(vdf.challenge);
119 hasher.update(vdf.output);
120 hasher.update(vdf.iterations.to_be_bytes());
121 hasher.update(vdf.duration_ms.to_be_bytes());
122 } else {
123 hasher.update(b"\x00");
124 }
125
126 if let Some(jitter) = &self.jitter_binding {
127 hasher.update(b"\x01");
128 hasher.update(jitter.entropy_commitment.hash);
129 } else {
130 hasher.update(b"\x00");
131 }
132
133 if let Some(mac) = &self.chain_mac {
134 hasher.update(b"\x01");
135 hasher.update(mac);
136 } else {
137 hasher.update(b"\x00");
138 }
139
140 self.checkpoint_hash = hasher.finalize().into();
141 }
142
143 pub fn validate(&self) -> Vec<String> {
145 let mut errors = Vec::new();
146
147 if self.content_hash == [0u8; 32] {
148 errors.push("content_hash is zero".into());
149 }
150
151 if self.checkpoint_hash == [0u8; 32] {
152 errors.push("checkpoint_hash is zero (call compute_hash first)".into());
153 }
154
155 if let Some(vdf) = &self.vdf_proof {
156 errors.extend(vdf.validate());
157 }
158
159 if let Some(jitter) = &self.jitter_binding {
160 errors.extend(jitter.validate());
161 }
162
163 errors
164 }
165
166 pub fn is_valid(&self) -> bool {
168 self.validate().is_empty()
169 }
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct SaVdfProof {
184 #[serde(rename = "1")]
186 pub algorithm: u32,
187
188 #[serde(rename = "2")]
189 pub iterations: u64,
190
191 #[serde(rename = "3")]
192 pub cycle_count: u64,
193
194 #[serde(rename = "4", with = "hex_bytes_vec")]
195 pub output: Vec<u8>,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct BioBinding {
209 #[serde(rename = "1")]
210 pub rho_millibits: RhoMillibits,
211
212 #[serde(rename = "2")]
213 pub hurst_millibits: Millibits,
214
215 #[serde(rename = "3")]
216 pub recognition_gap_ms: u32,
217}
218
219impl BioBinding {
220 pub fn new(rho: f64, hurst: f64, gap_ms: u32) -> Self {
222 Self {
223 rho_millibits: RhoMillibits::from_float(rho),
224 hurst_millibits: Millibits::from_float(hurst),
225 recognition_gap_ms: gap_ms,
226 }
227 }
228
229 pub fn is_hurst_human_like(&self) -> bool {
231 let h = self.hurst_millibits.raw();
232 h > 550 && h < 850
233 }
234
235 pub fn is_correlation_valid(&self) -> bool {
237 let rho = self.rho_millibits.raw();
238 (500..=950).contains(&rho)
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_checkpoint_creation() {
248 let cp = CheckpointRfc::new(0, 1700000000, [1u8; 32], [0u8; 32]);
249
250 assert_eq!(cp.sequence, 0);
251 assert_eq!(cp.content_hash, [1u8; 32]);
252 assert_eq!(cp.prev_hash, [0u8; 32]);
253 }
254
255 #[test]
256 fn test_checkpoint_hash_computation() {
257 let mut cp = CheckpointRfc::new(1, 1700000000, [1u8; 32], [2u8; 32]);
258
259 assert_eq!(cp.checkpoint_hash, [0u8; 32]);
260 cp.compute_hash();
261 assert_ne!(cp.checkpoint_hash, [0u8; 32]);
262 }
263
264 #[test]
265 fn test_bio_binding_hurst() {
266 let binding = BioBinding::new(0.75, 0.72, 250);
267 assert!(binding.is_hurst_human_like());
268 assert!(binding.is_correlation_valid());
269
270 let white_noise = BioBinding::new(0.75, 0.5, 250);
272 assert!(!white_noise.is_hurst_human_like());
273 }
274
275 #[test]
276 fn test_checkpoint_serialization() {
277 let cp = CheckpointRfc::new(0, 1700000000, [1u8; 32], [0u8; 32]);
278
279 let json = serde_json::to_string(&cp).unwrap();
280 assert!(json.contains("\"1\":0")); assert!(json.contains("\"3\":1700000000")); }
283}