provable_proof/
envelope.rs1use 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}