1use crate::envelope::KayrosEnvelope;
2use crate::types::{
3 EnvelopeVerifyDetails, EnvelopeVerifyInput, EnvelopeVerifyOverrides, EnvelopeVerifyResult,
4 EnvelopeVerifyWithInclusionOverrides,
5};
6use provable_sdk::{
7 verify, verify_with_inclusion, VerifyRequest, VerifyResult, VerifyResultDetails,
8 VerifyWithInclusionRequest,
9};
10
11fn normalize_hex(value: Option<&str>) -> Option<String> {
12 value.and_then(|input| {
13 let trimmed = input.trim();
14 if trimmed.is_empty() {
15 return None;
16 }
17 let normalized = trimmed.trim_start_matches("0x").to_lowercase();
18 if normalized.len() % 2 == 0 && normalized.chars().all(|ch| ch.is_ascii_hexdigit()) {
19 Some(normalized)
20 } else {
21 None
22 }
23 })
24}
25
26fn invalid_envelope_result(details: EnvelopeVerifyDetails, error: String) -> EnvelopeVerifyResult {
27 EnvelopeVerifyResult {
28 valid: false,
29 error: Some(error),
30 details: Some(details),
31 }
32}
33
34fn merge_details(
35 base: Option<VerifyResultDetails>,
36 extra: EnvelopeVerifyDetails,
37) -> EnvelopeVerifyDetails {
38 EnvelopeVerifyDetails {
39 verify: base.unwrap_or_else(|| {
40 let mut details = VerifyResultDetails::default();
41 details.lookup_mode = if extra.envelope_kayros_hash.is_some() {
42 "kayros_hash".to_string()
43 } else {
44 "data_item".to_string()
45 };
46 details.record_found = false;
47 details
48 }),
49 computed_data_item: extra.computed_data_item,
50 envelope_data_item: extra.envelope_data_item,
51 envelope_data_item_match: extra.envelope_data_item_match,
52 envelope_data_type: extra.envelope_data_type,
53 envelope_kayros_hash: extra.envelope_kayros_hash,
54 }
55}
56
57pub fn build_envelope_verify_request(
58 envelope: &KayrosEnvelope,
59 overrides: &EnvelopeVerifyOverrides,
60) -> provable_sdk::Result<EnvelopeVerifyInput> {
61 let computed_data_item = normalize_hex(Some(&envelope.compute_data_hash()?));
62 let envelope_data_item = envelope.get_data_hash();
63 let envelope_data_type = overrides
64 .data_type
65 .clone()
66 .or_else(|| envelope.get_data_type())
67 .or_else(|| envelope.get_data_type_label());
68 let envelope_kayros_hash = envelope.get_kayros_hash();
69
70 Ok(EnvelopeVerifyInput {
71 request: VerifyRequest {
72 data_type: envelope_data_type.clone(),
73 data_item: overrides
74 .data_item
75 .clone()
76 .or_else(|| computed_data_item.clone())
77 .and_then(|value| normalize_hex(Some(&value))),
78 kayros_hash: overrides
79 .kayros_hash
80 .clone()
81 .or_else(|| envelope_kayros_hash.clone())
82 .and_then(|value| normalize_hex(Some(&value))),
83 api_key: overrides.api_key.clone(),
84 },
85 details: EnvelopeVerifyDetails {
86 verify: VerifyResultDetails::default(),
87 computed_data_item: computed_data_item.clone(),
88 envelope_data_item: envelope_data_item.clone(),
89 envelope_data_item_match: match (
90 envelope_data_item.as_ref(),
91 computed_data_item.as_ref(),
92 ) {
93 (Some(expected), Some(computed)) => Some(expected == computed),
94 _ => None,
95 },
96 envelope_data_type,
97 envelope_kayros_hash,
98 },
99 })
100}
101
102pub fn verify_envelope(
103 envelope: &KayrosEnvelope,
104 overrides: &EnvelopeVerifyOverrides,
105) -> EnvelopeVerifyResult {
106 let input = match build_envelope_verify_request(envelope, overrides) {
107 Ok(input) => input,
108 Err(error) => {
109 return EnvelopeVerifyResult {
110 valid: false,
111 error: Some(error.0),
112 details: None,
113 }
114 }
115 };
116 if input.details.envelope_data_item_match == Some(false) {
117 let expected = input.details.envelope_data_item.clone().unwrap_or_default();
118 let computed = input.details.computed_data_item.clone().unwrap_or_default();
119 return invalid_envelope_result(
120 input.details,
121 format!(
122 "Envelope data_item mismatch: expected={} computed={}",
123 expected, computed
124 ),
125 );
126 }
127
128 let result: VerifyResult = verify(input.request.clone());
129 EnvelopeVerifyResult {
130 valid: result.valid,
131 error: result.error.clone(),
132 details: Some(merge_details(result.details, input.details)),
133 }
134}
135
136pub fn verify_envelope_with_inclusion(
137 envelope: &KayrosEnvelope,
138 overrides: &EnvelopeVerifyWithInclusionOverrides,
139) -> EnvelopeVerifyResult {
140 let input = match build_envelope_verify_request(envelope, &overrides.overrides) {
141 Ok(input) => input,
142 Err(error) => {
143 return EnvelopeVerifyResult {
144 valid: false,
145 error: Some(error.0),
146 details: None,
147 }
148 }
149 };
150 if input.details.envelope_data_item_match == Some(false) {
151 let expected = input.details.envelope_data_item.clone().unwrap_or_default();
152 let computed = input.details.computed_data_item.clone().unwrap_or_default();
153 return invalid_envelope_result(
154 input.details,
155 format!(
156 "Envelope data_item mismatch: expected={} computed={}",
157 expected, computed
158 ),
159 );
160 }
161 let request = VerifyWithInclusionRequest {
162 verify_request: input.request.clone(),
163 trusted_root_hash: overrides.trusted_root_hash.clone(),
164 trusted_level: overrides.trusted_level,
165 trusted_position: overrides.trusted_position,
166 levels_hash_type: overrides.levels_hash_type.clone(),
167 verify_batch_existence: overrides.verify_batch_existence,
168 level_checks: overrides.level_checks.clone(),
169 };
170 let result = verify_with_inclusion(request);
171 EnvelopeVerifyResult {
172 valid: result.valid,
173 error: result.error.clone(),
174 details: Some(merge_details(result.details, input.details)),
175 }
176}