kya-validator 0.2.3

Rust core KYA (Know Your Agent) validator with Python bindings, TEE support, and blockchain integration
Documentation
use crate::resolver::{decode_multibase, resolve_key_for_method};
use crate::types::{CryptoReport, KeyType, Manifest, ResolvedKey, ValidationConfig};
use ed25519_dalek::{Signature as Ed25519Signature, VerifyingKey as Ed25519VerifyingKey};
use k256::ecdsa::signature::Verifier;
use k256::ecdsa::{Signature as SecpSignature, VerifyingKey as SecpVerifyingKey};

#[cfg(not(target_arch = "wasm32"))]
use serde_jcs::to_vec as to_jcs_vec;

#[cfg(not(target_arch = "wasm32"))]
fn canonicalize_manifest(manifest: &serde_json::Value) -> Result<Vec<u8>, String> {
    let mut clone = manifest.clone();
    if let serde_json::Value::Object(map) = &mut clone {
        map.remove("proof");
    }
    to_jcs_vec(&clone).map_err(|err| format!("Canonicalization failed: {}", err))
}

#[cfg(target_arch = "wasm32")]
fn canonicalize_manifest(manifest: &serde_json::Value) -> Result<Vec<u8>, String> {
    // WASM fallback: use basic JSON serialization without full JCS
    let mut clone = manifest.clone();
    if let serde_json::Value::Object(map) = &mut clone {
        map.remove("proof");
    }
    // Sort keys for deterministic output (simplified JCS-like behavior)
    serde_json::to_string(&clone)
        .map(|s| s.into_bytes())
        .map_err(|err| format!("Canonicalization failed: {}", err))
}

fn verify_ed25519(public_key: &[u8], signature: &[u8], message: &[u8]) -> Result<(), String> {
    let key_bytes: [u8; 32] = public_key
        .try_into()
        .map_err(|_| "Ed25519 public key must be 32 bytes".to_string())?;
    let key = Ed25519VerifyingKey::from_bytes(&key_bytes)
        .map_err(|err| format!("Invalid Ed25519 key: {}", err))?;
    let signature = Ed25519Signature::from_slice(signature)
        .map_err(|err| format!("Invalid Ed25519 signature: {}", err))?;
    key.verify_strict(message, &signature)
        .map_err(|err| format!("Ed25519 verification failed: {}", err))
}

fn verify_secp256k1(public_key: &[u8], signature: &[u8], message: &[u8]) -> Result<(), String> {
    let key = SecpVerifyingKey::from_sec1_bytes(public_key)
        .map_err(|err| format!("Invalid Secp256k1 key: {}", err))?;
    let signature = SecpSignature::from_slice(signature)
        .or_else(|_| SecpSignature::from_der(signature))
        .map_err(|err| format!("Invalid Secp256k1 signature: {}", err))?;
    key.verify(message, &signature)
        .map_err(|err| format!("Secp256k1 verification failed: {}", err))
}

fn verify_signature(
    resolved: &ResolvedKey,
    signature_bytes: &[u8],
    message: &[u8],
) -> Result<(), String> {
    match resolved.key_type {
        KeyType::Ed25519 => verify_ed25519(&resolved.public_key, signature_bytes, message),
        KeyType::Secp256k1 => verify_secp256k1(&resolved.public_key, signature_bytes, message),
    }
}

pub fn verify_manifest_proofs(
    manifest: &Manifest,
    raw_manifest: &serde_json::Value,
    config: &ValidationConfig,
) -> (bool, Vec<String>, CryptoReport) {
    let mut errors = Vec::new();
    let mut report = CryptoReport::ok();
    let message = match canonicalize_manifest(raw_manifest) {
        Ok(message) => message,
        Err(err) => {
            errors.push(err);
            return (false, errors, report);
        }
    };

    for proof in &manifest.proof {
        let resolved = match resolve_key_for_method(&proof.verification_method, manifest) {
            Ok(resolved) => resolved,
            Err(err) => {
                report
                    .missing_verification_methods
                    .push(proof.verification_method.clone());
                errors.push(err);
                continue;
            }
        };

        if config.enforce_controller_match && resolved.controller != manifest.agent_id {
            errors.push(format!(
                "Verification method controller {} does not match agentId {}",
                resolved.controller, manifest.agent_id
            ));
        }

        report.resolved_keys.push(resolved.id.clone());

        let signature_bytes = match decode_multibase(&proof.proof_value) {
            Ok(bytes) => bytes,
            Err(err) => {
                report
                    .invalid_signatures
                    .push(proof.verification_method.clone());
                errors.push(err);
                continue;
            }
        };

        if let Err(err) = verify_signature(&resolved, &signature_bytes, &message) {
            report
                .invalid_signatures
                .push(proof.verification_method.clone());
            errors.push(err);
        }
    }

    (errors.is_empty(), errors, report)
}

#[cfg(test)]
mod tests {
    use super::*;
    use ed25519_dalek::{Signer, SigningKey};
    use rand::rngs::OsRng;
    use rand::RngCore;

    #[test]
    fn verify_ed25519_signature_success() {
        let mut secret_bytes = [0u8; 32];
        OsRng.fill_bytes(&mut secret_bytes);
        let signing_key = SigningKey::from_bytes(&secret_bytes);
        let verifying_key = signing_key.verifying_key();
        let public_key = verifying_key.to_bytes();
        let signature = signing_key.sign(b"hello");
        let signature_bytes = signature.to_bytes();
        let result = verify_ed25519(&public_key, &signature_bytes, b"hello");
        assert!(result.is_ok());
    }
}