provable-sdk 0.1.2

Provable SDK for Rust
Documentation
use crate::error::{ProvableError, Result};
use base64::engine::general_purpose::{STANDARD as BASE64_STANDARD, URL_SAFE as BASE64_URL_SAFE};
use base64::Engine;
use chrono::{TimeZone, Utc};

pub fn decode_flexible_bytes(value: &str) -> Option<Vec<u8>> {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        return None;
    }

    let normalized = trimmed.strip_prefix("0x").unwrap_or(trimmed);
    if normalized.len() % 2 == 0 && normalized.chars().all(|ch| ch.is_ascii_hexdigit()) {
        return hex::decode(normalized).ok();
    }

    BASE64_STANDARD
        .decode(trimmed)
        .ok()
        .or_else(|| BASE64_URL_SAFE.decode(trimmed).ok())
}

pub fn normalize_hex_string(value: &str) -> Option<String> {
    decode_flexible_bytes(value).map(hex::encode)
}

pub fn uuid_string_to_hex(value: &str) -> String {
    let normalized = value.trim().replace('-', "").to_lowercase();
    if normalized.len() == 32 && normalized.chars().all(|ch| ch.is_ascii_hexdigit()) {
        normalized
    } else {
        String::new()
    }
}

pub fn timeuuid_hex_to_timestamp(uuid_hex: &str) -> String {
    let bytes = match hex::decode(uuid_hex) {
        Ok(bytes) if bytes.len() == 16 => bytes,
        _ => return String::new(),
    };

    let time_low = ((bytes[0] as u64) << 24)
        | ((bytes[1] as u64) << 16)
        | ((bytes[2] as u64) << 8)
        | bytes[3] as u64;
    let time_mid = ((bytes[4] as u64) << 8) | bytes[5] as u64;
    let time_hi = ((((bytes[6] as u64) << 8) | bytes[7] as u64) & 0x0fff) as u64;
    let timestamp = time_low | (time_mid << 32) | (time_hi << 48);
    const UUID_GREGORIAN_EPOCH: i128 = 122_192_928_000_000_000;
    let unix_nanos = (timestamp as i128 - UUID_GREGORIAN_EPOCH) * 100;
    let unix_millis = unix_nanos / 1_000_000;
    Utc.timestamp_millis_opt(unix_millis as i64)
        .single()
        .map(|dt| dt.to_rfc3339())
        .unwrap_or_default()
}

pub fn is_zero_hash(value: &str) -> bool {
    value.chars().all(|ch| ch == '0')
}

pub fn normalize_level_counts(
    counts: &[usize],
    levels: usize,
    proof_len: usize,
) -> Result<Vec<usize>> {
    if proof_len == 0 {
        return Err(ProvableError::new("empty proof path"));
    }
    if !counts.is_empty() {
        if counts.iter().any(|count| *count == 0) {
            return Err(ProvableError::new("invalid level count"));
        }
        if counts.iter().sum::<usize>() != proof_len {
            return Err(ProvableError::new("proof length mismatch"));
        }
        return Ok(counts.to_vec());
    }
    if levels <= 1 {
        return Ok(vec![proof_len]);
    }
    let remaining = proof_len.saturating_sub(256 * (levels - 1));
    if remaining == 0 {
        return Err(ProvableError::new("proof length mismatch"));
    }
    let mut result = vec![256; levels - 1];
    result.push(remaining);
    Ok(result)
}