1use serde::{Deserialize, Serialize};
29use std::collections::HashMap;
30
31use super::fixed_point::{Centibits, Decibits, Millibits, RhoMillibits, SlopeDecibits};
32use super::serde_helpers::{hex_bytes_vec, hex_bytes_vec_opt};
33
34pub const CBOR_TAG_EVIDENCE_PACKET: u64 = crate::codec::CBOR_TAG_CPOP;
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct PacketRfc {
45 #[serde(rename = "1")]
48 pub version: u32,
49
50 #[serde(rename = "2")]
53 pub vdf: VdfStructure,
54
55 #[serde(rename = "3")]
58 pub jitter_seal: JitterSealStructure,
59
60 #[serde(rename = "4")]
63 pub content_hash_tree: ContentHashTree,
64
65 #[serde(rename = "5")]
68 pub correlation_proof: CorrelationProof,
69
70 #[serde(rename = "6", skip_serializing_if = "Option::is_none")]
73 pub error_topology: Option<ErrorTopology>,
74
75 #[serde(rename = "7", skip_serializing_if = "Option::is_none")]
78 pub enclave_vise: Option<EnclaveVise>,
79
80 #[serde(rename = "8", skip_serializing_if = "Option::is_none")]
83 pub zk_verdict: Option<ZkProcessVerdict>,
84
85 #[serde(rename = "9", skip_serializing_if = "Option::is_none")]
88 pub profile: Option<ProfileDeclaration>,
89
90 #[serde(rename = "18", skip_serializing_if = "Option::is_none")]
93 pub privacy_budget: Option<PrivacyBudgetCertificate>,
94
95 #[serde(rename = "19", skip_serializing_if = "Option::is_none")]
98 pub key_rotation: Option<KeyRotationMetadata>,
99
100 #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
102 pub extensions: HashMap<String, serde_json::Value>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct VdfStructure {
118 #[serde(rename = "1", with = "hex_bytes_vec")]
120 pub input: Vec<u8>,
121
122 #[serde(rename = "2", with = "hex_bytes_vec")]
124 pub output: Vec<u8>,
125
126 #[serde(rename = "3")]
128 pub iterations: u64,
129
130 #[serde(rename = "4")]
132 pub rdtsc_checkpoints: Vec<u64>,
133
134 #[serde(rename = "5", with = "hex_bytes_vec")]
136 pub entropic_pulse: Vec<u8>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct JitterSealStructure {
152 #[serde(rename = "1")]
154 pub lang: String,
155
156 #[serde(rename = "2", with = "hex_bytes_vec")]
158 pub bucket_commitment: Vec<u8>,
159
160 #[serde(rename = "3")]
162 pub entropy_millibits: u32,
163
164 #[serde(rename = "4")]
166 pub dp_epsilon_centibits: Centibits,
167
168 #[serde(rename = "5")]
170 pub pink_noise_slope_decibits: SlopeDecibits,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct ContentHashTree {
183 #[serde(rename = "1", with = "hex_bytes_vec")]
185 pub root: Vec<u8>,
186
187 #[serde(rename = "2")]
189 pub segment_count: u16,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct CorrelationProof {
202 #[serde(rename = "1")]
204 pub rho: RhoMillibits,
205
206 #[serde(rename = "2")]
208 pub threshold: i16,
209}
210
211impl Default for CorrelationProof {
212 fn default() -> Self {
213 Self {
214 rho: RhoMillibits::new(0),
215 threshold: 700,
216 }
217 }
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct ErrorTopology {
225 #[serde(rename = "1")]
227 pub fractal_dimension_decibits: Decibits,
228
229 #[serde(rename = "2")]
231 pub clustering_millibits: Millibits,
232
233 #[serde(rename = "3", with = "hex_bytes_vec")]
235 pub temporal_signature: Vec<u8>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct EnclaveVise {
241 #[serde(rename = "1")]
243 pub enclave_type: u8,
244
245 #[serde(rename = "2", with = "hex_bytes_vec")]
247 pub attestation: Vec<u8>,
248
249 #[serde(rename = "3")]
251 pub timestamp: u64,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct ZkProcessVerdict {
257 #[serde(rename = "1")]
259 pub verdict: u8,
260
261 #[serde(rename = "2")]
263 pub confidence_millibits: Millibits,
264
265 #[serde(
267 rename = "3",
268 skip_serializing_if = "Option::is_none",
269 with = "hex_bytes_vec_opt"
270 )]
271 pub proof: Option<Vec<u8>>,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct ProfileDeclaration {
286 #[serde(rename = "1")]
288 pub tier: u8,
289
290 #[serde(rename = "2")]
292 pub uri: String,
293
294 #[serde(rename = "3", skip_serializing_if = "Option::is_none")]
296 pub enabled_features: Option<Vec<u8>>,
297
298 #[serde(rename = "4", skip_serializing_if = "Option::is_none")]
300 pub implementation_id: Option<String>,
301}
302
303impl ProfileDeclaration {
304 pub fn core() -> Self {
306 Self {
307 tier: 1,
308 uri: "urn:ietf:params:pop:profile:1.0".to_string(),
309 enabled_features: None,
310 implementation_id: None,
311 }
312 }
313
314 pub fn enhanced() -> Self {
316 Self {
317 tier: 2,
318 uri: "urn:ietf:params:pop:profile:1.0".to_string(),
319 enabled_features: None,
320 implementation_id: None,
321 }
322 }
323
324 pub fn maximum() -> Self {
326 Self {
327 tier: 3,
328 uri: "urn:ietf:params:pop:profile:1.0".to_string(),
329 enabled_features: None,
330 implementation_id: None,
331 }
332 }
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct PrivacyBudgetCertificate {
340 #[serde(rename = "1")]
342 pub key_generation_method: String,
343
344 #[serde(rename = "2")]
346 pub key_valid_from: u64,
347
348 #[serde(rename = "3")]
350 pub key_valid_until: u64,
351
352 #[serde(rename = "4")]
354 pub session_epsilon_centibits: Centibits,
355
356 #[serde(rename = "5")]
358 pub cumulative_epsilon_micros_before: u64,
359
360 #[serde(rename = "6")]
362 pub cumulative_epsilon_micros_after: u64,
363
364 #[serde(rename = "7")]
366 pub sessions_used_this_key: u8,
367
368 #[serde(rename = "8")]
370 pub max_sessions_recommended: u8,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct KeyRotationMetadata {
376 #[serde(rename = "1")]
378 pub rotation_method: String,
379
380 #[serde(rename = "2")]
382 pub next_rotation_date: u64,
383
384 #[serde(rename = "3")]
386 pub sessions_remaining: u8,
387
388 #[serde(rename = "4")]
390 pub cumulative_epsilon_micros: u64,
391}
392
393impl PacketRfc {
394 pub fn new_core(
396 vdf: VdfStructure,
397 jitter_seal: JitterSealStructure,
398 content_hash_tree: ContentHashTree,
399 correlation_proof: CorrelationProof,
400 ) -> Self {
401 Self {
402 version: 1,
403 vdf,
404 jitter_seal,
405 content_hash_tree,
406 correlation_proof,
407 error_topology: None,
408 enclave_vise: None,
409 zk_verdict: None,
410 profile: Some(ProfileDeclaration::core()),
411 privacy_budget: None,
412 key_rotation: None,
413 extensions: HashMap::new(),
414 }
415 }
416
417 pub fn validate(&self) -> Vec<String> {
419 let mut errors = Vec::new();
420
421 if self.version != 1 {
422 errors.push(format!("unsupported version: {}", self.version));
423 }
424
425 if self.vdf.input.is_empty() {
426 errors.push("VDF input is empty".into());
427 }
428
429 if self.vdf.output.is_empty() {
430 errors.push("VDF output is empty".into());
431 }
432
433 if self.content_hash_tree.root.is_empty() {
434 errors.push("content hash tree root is empty".into());
435 }
436
437 if self.content_hash_tree.segment_count < 20 {
438 errors.push(format!(
439 "segment_count {} is below minimum 20",
440 self.content_hash_tree.segment_count
441 ));
442 }
443
444 if self.correlation_proof.threshold != 700 {
445 errors.push(format!(
446 "non-standard correlation threshold: {} (expected 700)",
447 self.correlation_proof.threshold
448 ));
449 }
450
451 errors
452 }
453
454 pub fn is_valid(&self) -> bool {
455 self.validate().is_empty()
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462
463 fn create_test_packet() -> PacketRfc {
464 PacketRfc::new_core(
465 VdfStructure {
466 input: vec![1u8; 32],
467 output: vec![2u8; 64],
468 iterations: 1_000_000,
469 rdtsc_checkpoints: vec![1000, 2000, 3000],
470 entropic_pulse: vec![3u8; 32],
471 },
472 JitterSealStructure {
473 lang: "en-US".to_string(),
474 bucket_commitment: vec![4u8; 32],
475 entropy_millibits: 8500,
476 dp_epsilon_centibits: Centibits::from_float(0.5),
477 pink_noise_slope_decibits: SlopeDecibits::from_float(-1.0),
478 },
479 ContentHashTree {
480 root: vec![5u8; 32],
481 segment_count: 25,
482 },
483 CorrelationProof {
484 rho: RhoMillibits::from_float(0.75),
485 threshold: 700,
486 },
487 )
488 }
489
490 #[test]
491 fn test_packet_creation() {
492 let packet = create_test_packet();
493 assert_eq!(packet.version, 1);
494 assert!(packet.profile.is_some());
495 assert_eq!(packet.profile.as_ref().unwrap().tier, 1);
496 }
497
498 #[test]
499 fn test_packet_validation() {
500 let packet = create_test_packet();
501 let errors = packet.validate();
502 assert!(errors.is_empty(), "errors: {:?}", errors);
503 }
504
505 #[test]
506 fn test_packet_validation_empty_vdf() {
507 let mut packet = create_test_packet();
508 packet.vdf.input = vec![];
509 let errors = packet.validate();
510 assert!(errors.iter().any(|e| e.contains("VDF input is empty")));
511 }
512
513 #[test]
514 fn test_packet_serialization() {
515 let packet = create_test_packet();
516 let json = serde_json::to_string(&packet).unwrap();
517
518 assert!(json.contains("\"1\":1"));
519 assert!(json.contains("\"2\":{"));
520 assert!(json.contains("\"3\":{"));
521
522 let decoded: PacketRfc = serde_json::from_str(&json).unwrap();
523 assert_eq!(decoded.version, packet.version);
524 }
525
526 #[test]
527 fn test_profile_tiers() {
528 assert_eq!(ProfileDeclaration::core().tier, 1);
529 assert_eq!(ProfileDeclaration::enhanced().tier, 2);
530 assert_eq!(ProfileDeclaration::maximum().tier, 3);
531 }
532}