Skip to main content

cpop_protocol/war/
types.rs

1// SPDX-License-Identifier: Apache-2.0
2
3// NOTE: cpop_engine extends Block with evidence: Option<Box<Packet>>
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7/// WAR block format version.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9pub enum Version {
10    /// Legacy parallel computation (WAR/1.0)
11    V1_0,
12    /// Entangled computation with jitter binding (WAR/1.1)
13    V1_1,
14    /// EAR appraisal with attestation results (WAR/2.0)
15    V2_0,
16}
17
18impl Version {
19    pub fn as_str(&self) -> &'static str {
20        match self {
21            Version::V1_0 => "WAR/1.0",
22            Version::V1_1 => "WAR/1.1",
23            Version::V2_0 => "WAR/2.0",
24        }
25    }
26
27    pub fn parse(s: &str) -> Option<Self> {
28        match s {
29            "WAR/1.0" => Some(Version::V1_0),
30            "WAR/1.1" => Some(Version::V1_1),
31            "WAR/2.0" => Some(Version::V2_0),
32            _ => None,
33        }
34    }
35}
36
37/// A WAR evidence block.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Block {
40    pub version: Version,
41    /// From declaration or public key fingerprint.
42    pub author: String,
43    /// SHA-256 of final content.
44    pub document_id: [u8; 32],
45    pub timestamp: DateTime<Utc>,
46    pub statement: String,
47    pub seal: Seal,
48    /// H3 signature is valid.
49    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
50    pub signed: bool,
51    /// Freshness nonce for replay attack prevention.
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub verifier_nonce: Option<[u8; 32]>,
54    /// EAR appraisal token (WAR/2.0+)
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub ear: Option<super::ear::EarToken>,
57}
58
59/// The cryptographic seal binding all evidence together.
60#[derive(Debug, Clone)]
61pub struct Seal {
62    /// H1: SHA-256(doc || checkpoint_root || declaration)
63    pub h1: [u8; 32],
64    /// H2: SHA-256(H1 || jitter || pubkey)
65    pub h2: [u8; 32],
66    /// H3: SHA-256(H2 || vdf_output || doc)
67    pub h3: [u8; 32],
68    /// H4: Ed25519 signature of H3
69    pub signature: [u8; 64],
70    pub public_key: [u8; 32],
71}
72
73impl Serialize for Seal {
74    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75    where
76        S: serde::Serializer,
77    {
78        use serde::ser::SerializeStruct;
79        let mut state = serializer.serialize_struct("Seal", 5)?;
80        state.serialize_field("h1", &hex::encode(self.h1))?;
81        state.serialize_field("h2", &hex::encode(self.h2))?;
82        state.serialize_field("h3", &hex::encode(self.h3))?;
83        state.serialize_field("signature", &hex::encode(self.signature))?;
84        state.serialize_field("public_key", &hex::encode(self.public_key))?;
85        state.end()
86    }
87}
88
89impl<'de> Deserialize<'de> for Seal {
90    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91    where
92        D: serde::Deserializer<'de>,
93    {
94        #[derive(Deserialize)]
95        struct SealHelper {
96            h1: String,
97            h2: String,
98            h3: String,
99            signature: String,
100            public_key: String,
101        }
102
103        let helper = SealHelper::deserialize(deserializer)?;
104
105        let h1 = hex::decode(&helper.h1).map_err(serde::de::Error::custom)?;
106        let h2 = hex::decode(&helper.h2).map_err(serde::de::Error::custom)?;
107        let h3 = hex::decode(&helper.h3).map_err(serde::de::Error::custom)?;
108        let signature = hex::decode(&helper.signature).map_err(serde::de::Error::custom)?;
109        let public_key = hex::decode(&helper.public_key).map_err(serde::de::Error::custom)?;
110
111        if h1.len() != 32 || h2.len() != 32 || h3.len() != 32 {
112            return Err(serde::de::Error::custom("hash must be 32 bytes"));
113        }
114        if signature.len() != 64 {
115            return Err(serde::de::Error::custom("signature must be 64 bytes"));
116        }
117        if public_key.len() != 32 {
118            return Err(serde::de::Error::custom("public key must be 32 bytes"));
119        }
120
121        let mut seal = Seal {
122            h1: [0u8; 32],
123            h2: [0u8; 32],
124            h3: [0u8; 32],
125            signature: [0u8; 64],
126            public_key: [0u8; 32],
127        };
128        seal.h1.copy_from_slice(&h1);
129        seal.h2.copy_from_slice(&h2);
130        seal.h3.copy_from_slice(&h3);
131        seal.signature.copy_from_slice(&signature);
132        seal.public_key.copy_from_slice(&public_key);
133        Ok(seal)
134    }
135}
136
137/// Result of WAR block verification.
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct VerificationReport {
140    pub valid: bool,
141    pub checks: Vec<CheckResult>,
142    pub summary: String,
143    pub details: ForensicDetails,
144}
145
146/// Individual verification check result.
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct CheckResult {
149    /// e.g. "seal_signature", "hash_chain"
150    pub name: String,
151    pub passed: bool,
152    pub message: String,
153}
154
155/// Detailed forensic information from verification.
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct ForensicDetails {
158    pub version: String,
159    pub author: String,
160    pub document_id: String,
161    pub timestamp: DateTime<Utc>,
162    pub components: Vec<String>,
163    /// Total elapsed time from VDF proofs.
164    pub elapsed_time_secs: Option<f64>,
165    pub checkpoint_count: Option<usize>,
166    pub keystroke_count: Option<u64>,
167    pub has_jitter_seal: bool,
168    pub has_hardware_attestation: bool,
169    pub has_verifier_nonce: bool,
170    /// Hex-encoded, if present.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub verifier_nonce: Option<String>,
173}