Skip to main content

provable_proof/
envelope.rs

1use crate::types::{KayrosData, KayrosProof, ProofDataFormat};
2use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
3use base64::Engine;
4use provable_sdk::{keccak256, sha256, ProvableError, Result};
5use serde::Serialize;
6
7fn decode_base64(value: &str) -> Result<Vec<u8>> {
8    BASE64_STANDARD.decode(value.trim()).map_err(|error| {
9        ProvableError::new(format!(
10            "Invalid proof data: expected base64 string ({})",
11            error
12        ))
13    })
14}
15
16fn encode_base64(bytes: &[u8]) -> String {
17    BASE64_STANDARD.encode(bytes)
18}
19
20fn normalize_hash(value: Option<&str>) -> Option<String> {
21    value.and_then(|input| {
22        let trimmed = input.trim();
23        if trimmed.is_empty() {
24            return None;
25        }
26        let normalized = trimmed.trim_start_matches("0x").to_lowercase();
27        if normalized.len() % 2 == 0 && normalized.chars().all(|ch| ch.is_ascii_hexdigit()) {
28            return Some(normalized);
29        }
30        BASE64_STANDARD.decode(trimmed).ok().map(hex::encode)
31    })
32}
33
34fn first_string(values: &[Option<String>]) -> Option<String> {
35    values
36        .iter()
37        .flatten()
38        .find(|value| !value.is_empty())
39        .cloned()
40}
41
42#[derive(Debug, Clone, PartialEq)]
43pub struct KayrosEnvelope {
44    pub data: String,
45    pub data_format: ProofDataFormat,
46    pub kayros: KayrosData,
47    data_bytes: Vec<u8>,
48}
49
50impl KayrosEnvelope {
51    pub fn new(
52        data: impl Into<String>,
53        kayros: KayrosData,
54        data_format: impl Into<String>,
55    ) -> Result<Self> {
56        let data = data.into();
57        let data_bytes = decode_base64(&data)?;
58        Ok(Self {
59            data,
60            data_format: data_format.into(),
61            kayros,
62            data_bytes,
63        })
64    }
65
66    pub fn from_bytes(
67        data: &[u8],
68        kayros: KayrosData,
69        data_format: impl Into<String>,
70    ) -> Result<Self> {
71        Self::new(encode_base64(data), kayros, data_format)
72    }
73
74    pub fn from_text(
75        data: &str,
76        kayros: KayrosData,
77        data_format: impl Into<String>,
78    ) -> Result<Self> {
79        Self::from_bytes(data.as_bytes(), kayros, data_format)
80    }
81
82    pub fn from_json_value<T: Serialize>(
83        data: &T,
84        kayros: KayrosData,
85        data_format: impl Into<String>,
86    ) -> Result<Self> {
87        let serialized = serde_json::to_vec(data)?;
88        Self::from_bytes(&serialized, kayros, data_format)
89    }
90
91    pub fn from_json_str(json: &str) -> Result<Self> {
92        let parsed: KayrosProof = serde_json::from_str(json)?;
93        Self::new(
94            parsed.data,
95            parsed.kayros,
96            parsed.data_format.unwrap_or_default(),
97        )
98    }
99
100    pub fn get_data_format(&self) -> String {
101        self.data_format.clone()
102    }
103
104    pub fn get_data_hash(&self) -> Option<String> {
105        normalize_hash(self.kayros.hash.as_deref())
106    }
107
108    pub fn get_data_type(&self) -> Option<String> {
109        self.kayros
110            .timestamp
111            .as_ref()
112            .and_then(|timestamp| timestamp.response.data.as_ref())
113            .map(|record| record.data_type.clone())
114    }
115
116    pub fn get_data_type_label(&self) -> Option<String> {
117        self.get_data_type()
118    }
119
120    pub fn get_data_type_lookup_candidates(&self) -> Vec<Option<String>> {
121        vec![self.get_data_type()]
122    }
123
124    pub fn get_kayros_hash(&self) -> Option<String> {
125        let direct = self
126            .kayros
127            .timestamp
128            .as_ref()
129            .and_then(|timestamp| timestamp.response.response.as_ref())
130            .and_then(|response| response.hash.clone());
131        let fallback = self
132            .kayros
133            .timestamp
134            .as_ref()
135            .and_then(|timestamp| timestamp.response.data.as_ref())
136            .map(|record| record.hash_item.clone());
137        normalize_hash(first_string(&[direct, fallback]).as_deref())
138    }
139
140    pub fn get_timeuuid(&self) -> Option<String> {
141        first_string(&[
142            self.kayros
143                .timestamp
144                .as_ref()
145                .and_then(|timestamp| timestamp.response.response.as_ref())
146                .and_then(|response| response.timeuuid.clone()),
147            self.kayros
148                .timestamp
149                .as_ref()
150                .and_then(|timestamp| timestamp.response.data.as_ref())
151                .map(|record| record.ts.clone()),
152        ])
153    }
154
155    pub fn get_hash_algorithm(&self) -> String {
156        self.kayros
157            .hash_algorithm
158            .clone()
159            .unwrap_or_else(|| "sha256".to_string())
160            .to_lowercase()
161            .replace('_', "")
162            .replace('-', "")
163    }
164
165    pub fn get_data(&self) -> &[u8] {
166        &self.data_bytes
167    }
168
169    pub fn get_data_text(&self) -> Result<String> {
170        String::from_utf8(self.data_bytes.clone())
171            .map_err(|error| ProvableError::new(error.to_string()))
172    }
173
174    pub fn parse_data_json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
175        Ok(serde_json::from_slice(&self.data_bytes)?)
176    }
177
178    pub fn to_proof(&self) -> KayrosProof {
179        KayrosProof {
180            data: self.data.clone(),
181            data_format: Some(self.data_format.clone()),
182            kayros: self.kayros.clone(),
183        }
184    }
185
186    pub fn compute_data_hash(&self) -> Result<String> {
187        if self.data_format == "raw_hash" {
188            return Ok(hex::encode(&self.data_bytes));
189        }
190        Ok(match self.get_hash_algorithm().as_str() {
191            "keccak256" => keccak256(&self.data_bytes),
192            _ => sha256(&self.data_bytes),
193        })
194    }
195}