Skip to main content

kya_validator/
verifier.rs

1use crate::resolver::{decode_multibase, resolve_key_for_method};
2use crate::types::{CryptoReport, KeyType, Manifest, ResolvedKey, ValidationConfig};
3use ed25519_dalek::{Signature as Ed25519Signature, VerifyingKey as Ed25519VerifyingKey};
4use k256::ecdsa::signature::Verifier;
5use k256::ecdsa::{Signature as SecpSignature, VerifyingKey as SecpVerifyingKey};
6
7#[cfg(not(target_arch = "wasm32"))]
8use serde_jcs::to_vec as to_jcs_vec;
9
10#[cfg(not(target_arch = "wasm32"))]
11fn canonicalize_manifest(manifest: &serde_json::Value) -> Result<Vec<u8>, String> {
12    let mut clone = manifest.clone();
13    if let serde_json::Value::Object(map) = &mut clone {
14        map.remove("proof");
15    }
16    to_jcs_vec(&clone).map_err(|err| format!("Canonicalization failed: {}", err))
17}
18
19#[cfg(target_arch = "wasm32")]
20fn canonicalize_manifest(manifest: &serde_json::Value) -> Result<Vec<u8>, String> {
21    // WASM fallback: use basic JSON serialization without full JCS
22    let mut clone = manifest.clone();
23    if let serde_json::Value::Object(map) = &mut clone {
24        map.remove("proof");
25    }
26    // Sort keys for deterministic output (simplified JCS-like behavior)
27    serde_json::to_string(&clone)
28        .map(|s| s.into_bytes())
29        .map_err(|err| format!("Canonicalization failed: {}", err))
30}
31
32fn verify_ed25519(public_key: &[u8], signature: &[u8], message: &[u8]) -> Result<(), String> {
33    let key_bytes: [u8; 32] = public_key
34        .try_into()
35        .map_err(|_| "Ed25519 public key must be 32 bytes".to_string())?;
36    let key = Ed25519VerifyingKey::from_bytes(&key_bytes)
37        .map_err(|err| format!("Invalid Ed25519 key: {}", err))?;
38    let signature = Ed25519Signature::from_slice(signature)
39        .map_err(|err| format!("Invalid Ed25519 signature: {}", err))?;
40    key.verify_strict(message, &signature)
41        .map_err(|err| format!("Ed25519 verification failed: {}", err))
42}
43
44fn verify_secp256k1(public_key: &[u8], signature: &[u8], message: &[u8]) -> Result<(), String> {
45    let key = SecpVerifyingKey::from_sec1_bytes(public_key)
46        .map_err(|err| format!("Invalid Secp256k1 key: {}", err))?;
47    let signature = SecpSignature::from_slice(signature)
48        .or_else(|_| SecpSignature::from_der(signature))
49        .map_err(|err| format!("Invalid Secp256k1 signature: {}", err))?;
50    key.verify(message, &signature)
51        .map_err(|err| format!("Secp256k1 verification failed: {}", err))
52}
53
54fn verify_signature(
55    resolved: &ResolvedKey,
56    signature_bytes: &[u8],
57    message: &[u8],
58) -> Result<(), String> {
59    match resolved.key_type {
60        KeyType::Ed25519 => verify_ed25519(&resolved.public_key, signature_bytes, message),
61        KeyType::Secp256k1 => verify_secp256k1(&resolved.public_key, signature_bytes, message),
62    }
63}
64
65pub fn verify_manifest_proofs(
66    manifest: &Manifest,
67    raw_manifest: &serde_json::Value,
68    config: &ValidationConfig,
69) -> (bool, Vec<String>, CryptoReport) {
70    let mut errors = Vec::new();
71    let mut report = CryptoReport::ok();
72    let message = match canonicalize_manifest(raw_manifest) {
73        Ok(message) => message,
74        Err(err) => {
75            errors.push(err);
76            return (false, errors, report);
77        }
78    };
79
80    for proof in &manifest.proof {
81        let resolved = match resolve_key_for_method(&proof.verification_method, manifest) {
82            Ok(resolved) => resolved,
83            Err(err) => {
84                report
85                    .missing_verification_methods
86                    .push(proof.verification_method.clone());
87                errors.push(err);
88                continue;
89            }
90        };
91
92        if config.enforce_controller_match && resolved.controller != manifest.agent_id {
93            errors.push(format!(
94                "Verification method controller {} does not match agentId {}",
95                resolved.controller, manifest.agent_id
96            ));
97        }
98
99        report.resolved_keys.push(resolved.id.clone());
100
101        let signature_bytes = match decode_multibase(&proof.proof_value) {
102            Ok(bytes) => bytes,
103            Err(err) => {
104                report
105                    .invalid_signatures
106                    .push(proof.verification_method.clone());
107                errors.push(err);
108                continue;
109            }
110        };
111
112        if let Err(err) = verify_signature(&resolved, &signature_bytes, &message) {
113            report
114                .invalid_signatures
115                .push(proof.verification_method.clone());
116            errors.push(err);
117        }
118    }
119
120    (errors.is_empty(), errors, report)
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use ed25519_dalek::{Signer, SigningKey};
127    use rand::rngs::OsRng;
128    use rand::RngCore;
129
130    #[test]
131    fn verify_ed25519_signature_success() {
132        let mut secret_bytes = [0u8; 32];
133        OsRng.fill_bytes(&mut secret_bytes);
134        let signing_key = SigningKey::from_bytes(&secret_bytes);
135        let verifying_key = signing_key.verifying_key();
136        let public_key = verifying_key.to_bytes();
137        let signature = signing_key.sign(b"hello");
138        let signature_bytes = signature.to_bytes();
139        let result = verify_ed25519(&public_key, &signature_bytes, b"hello");
140        assert!(result.is_ok());
141    }
142}