provable-proof 0.1.2

Provable proof envelope format for Rust
Documentation
use crate::envelope::KayrosEnvelope;
use crate::types::{
    EnvelopeVerifyDetails, EnvelopeVerifyInput, EnvelopeVerifyOverrides, EnvelopeVerifyResult,
    EnvelopeVerifyWithInclusionOverrides,
};
use provable_sdk::{
    verify, verify_with_inclusion, VerifyRequest, VerifyResult, VerifyResultDetails,
    VerifyWithInclusionRequest,
};

fn normalize_hex(value: Option<&str>) -> Option<String> {
    value.and_then(|input| {
        let trimmed = input.trim();
        if trimmed.is_empty() {
            return None;
        }
        let normalized = trimmed.trim_start_matches("0x").to_lowercase();
        if normalized.len() % 2 == 0 && normalized.chars().all(|ch| ch.is_ascii_hexdigit()) {
            Some(normalized)
        } else {
            None
        }
    })
}

fn invalid_envelope_result(details: EnvelopeVerifyDetails, error: String) -> EnvelopeVerifyResult {
    EnvelopeVerifyResult {
        valid: false,
        error: Some(error),
        details: Some(details),
    }
}

fn merge_details(
    base: Option<VerifyResultDetails>,
    extra: EnvelopeVerifyDetails,
) -> EnvelopeVerifyDetails {
    EnvelopeVerifyDetails {
        verify: base.unwrap_or_else(|| {
            let mut details = VerifyResultDetails::default();
            details.lookup_mode = if extra.envelope_kayros_hash.is_some() {
                "kayros_hash".to_string()
            } else {
                "data_item".to_string()
            };
            details.record_found = false;
            details
        }),
        computed_data_item: extra.computed_data_item,
        envelope_data_item: extra.envelope_data_item,
        envelope_data_item_match: extra.envelope_data_item_match,
        envelope_data_type: extra.envelope_data_type,
        envelope_kayros_hash: extra.envelope_kayros_hash,
    }
}

pub fn build_envelope_verify_request(
    envelope: &KayrosEnvelope,
    overrides: &EnvelopeVerifyOverrides,
) -> provable_sdk::Result<EnvelopeVerifyInput> {
    let computed_data_item = normalize_hex(Some(&envelope.compute_data_hash()?));
    let envelope_data_item = envelope.get_data_hash();
    let envelope_data_type = overrides
        .data_type
        .clone()
        .or_else(|| envelope.get_data_type())
        .or_else(|| envelope.get_data_type_label());
    let envelope_kayros_hash = envelope.get_kayros_hash();

    Ok(EnvelopeVerifyInput {
        request: VerifyRequest {
            data_type: envelope_data_type.clone(),
            data_item: overrides
                .data_item
                .clone()
                .or_else(|| computed_data_item.clone())
                .and_then(|value| normalize_hex(Some(&value))),
            kayros_hash: overrides
                .kayros_hash
                .clone()
                .or_else(|| envelope_kayros_hash.clone())
                .and_then(|value| normalize_hex(Some(&value))),
            api_key: overrides.api_key.clone(),
        },
        details: EnvelopeVerifyDetails {
            verify: VerifyResultDetails::default(),
            computed_data_item: computed_data_item.clone(),
            envelope_data_item: envelope_data_item.clone(),
            envelope_data_item_match: match (
                envelope_data_item.as_ref(),
                computed_data_item.as_ref(),
            ) {
                (Some(expected), Some(computed)) => Some(expected == computed),
                _ => None,
            },
            envelope_data_type,
            envelope_kayros_hash,
        },
    })
}

pub fn verify_envelope(
    envelope: &KayrosEnvelope,
    overrides: &EnvelopeVerifyOverrides,
) -> EnvelopeVerifyResult {
    let input = match build_envelope_verify_request(envelope, overrides) {
        Ok(input) => input,
        Err(error) => {
            return EnvelopeVerifyResult {
                valid: false,
                error: Some(error.0),
                details: None,
            }
        }
    };
    if input.details.envelope_data_item_match == Some(false) {
        let expected = input.details.envelope_data_item.clone().unwrap_or_default();
        let computed = input.details.computed_data_item.clone().unwrap_or_default();
        return invalid_envelope_result(
            input.details,
            format!(
                "Envelope data_item mismatch: expected={} computed={}",
                expected, computed
            ),
        );
    }

    let result: VerifyResult = verify(input.request.clone());
    EnvelopeVerifyResult {
        valid: result.valid,
        error: result.error.clone(),
        details: Some(merge_details(result.details, input.details)),
    }
}

pub fn verify_envelope_with_inclusion(
    envelope: &KayrosEnvelope,
    overrides: &EnvelopeVerifyWithInclusionOverrides,
) -> EnvelopeVerifyResult {
    let input = match build_envelope_verify_request(envelope, &overrides.overrides) {
        Ok(input) => input,
        Err(error) => {
            return EnvelopeVerifyResult {
                valid: false,
                error: Some(error.0),
                details: None,
            }
        }
    };
    if input.details.envelope_data_item_match == Some(false) {
        let expected = input.details.envelope_data_item.clone().unwrap_or_default();
        let computed = input.details.computed_data_item.clone().unwrap_or_default();
        return invalid_envelope_result(
            input.details,
            format!(
                "Envelope data_item mismatch: expected={} computed={}",
                expected, computed
            ),
        );
    }
    let request = VerifyWithInclusionRequest {
        verify_request: input.request.clone(),
        trusted_root_hash: overrides.trusted_root_hash.clone(),
        trusted_level: overrides.trusted_level,
        trusted_position: overrides.trusted_position,
        levels_hash_type: overrides.levels_hash_type.clone(),
        verify_batch_existence: overrides.verify_batch_existence,
        level_checks: overrides.level_checks.clone(),
    };
    let result = verify_with_inclusion(request);
    EnvelopeVerifyResult {
        valid: result.valid,
        error: result.error.clone(),
        details: Some(merge_details(result.details, input.details)),
    }
}