anp 0.8.4

Rust SDK for Agent Network Protocol (ANP)
Documentation
use serde_json::Value;
use thiserror::Error;

use crate::keys::{base64url_decode, decode_signature_bytes, encode_signature_bytes};
use crate::PublicKeyMaterial;

#[derive(Debug, Error)]
pub enum VerificationMethodError {
    #[error("Missing verification method type")]
    MissingType,
    #[error("Unsupported verification method type: {0}")]
    UnsupportedType(String),
    #[error("Missing key material")]
    MissingKeyMaterial,
    #[error("Invalid key material")]
    InvalidKeyMaterial,
    #[error("Signature encoding error")]
    SignatureEncoding,
}

#[derive(Debug, Clone)]
pub struct VerificationMethod {
    pub id: String,
    pub method_type: String,
    pub public_key: PublicKeyMaterial,
}

impl VerificationMethod {
    pub fn verify_signature(
        &self,
        content: &[u8],
        signature: &str,
    ) -> Result<(), VerificationMethodError> {
        let signature_bytes = decode_signature_bytes(signature)
            .map_err(|_| VerificationMethodError::SignatureEncoding)?;
        self.public_key
            .verify_message(content, &signature_bytes)
            .map_err(|_| VerificationMethodError::SignatureEncoding)
    }

    pub fn encode_signature(
        &self,
        signature_bytes: &[u8],
    ) -> Result<String, VerificationMethodError> {
        match self.public_key {
            PublicKeyMaterial::X25519(_) => Err(VerificationMethodError::UnsupportedType(
                self.method_type.clone(),
            )),
            _ => Ok(encode_signature_bytes(signature_bytes)),
        }
    }
}

pub fn create_verification_method(
    method_dict: &Value,
) -> Result<VerificationMethod, VerificationMethodError> {
    let object = method_dict
        .as_object()
        .ok_or(VerificationMethodError::InvalidKeyMaterial)?;
    let method_type = object
        .get("type")
        .and_then(Value::as_str)
        .ok_or(VerificationMethodError::MissingType)?;
    let id = object
        .get("id")
        .and_then(Value::as_str)
        .unwrap_or_default()
        .to_string();
    let public_key = extract_public_key(method_dict)?;
    Ok(VerificationMethod {
        id,
        method_type: method_type.to_string(),
        public_key,
    })
}

pub fn extract_public_key(
    method_dict: &Value,
) -> Result<PublicKeyMaterial, VerificationMethodError> {
    let object = method_dict
        .as_object()
        .ok_or(VerificationMethodError::InvalidKeyMaterial)?;
    let method_type = object
        .get("type")
        .and_then(Value::as_str)
        .ok_or(VerificationMethodError::MissingType)?;

    match method_type {
        "EcdsaSecp256k1VerificationKey2019" => extract_secp256k1_key(object),
        "EcdsaSecp256r1VerificationKey2019" => extract_secp256r1_key(object),
        "Ed25519VerificationKey2018" | "Ed25519VerificationKey2020" | "Multikey" => {
            extract_ed25519_key(object)
        }
        "X25519KeyAgreementKey2019" => extract_x25519_key(object),
        "JsonWebKey2020" => extract_ec_jwk_key(object),
        other => Err(VerificationMethodError::UnsupportedType(other.to_string())),
    }
}

fn extract_secp256k1_key(
    object: &serde_json::Map<String, Value>,
) -> Result<PublicKeyMaterial, VerificationMethodError> {
    if let Some(jwk) = object.get("publicKeyJwk") {
        return extract_ec_key_from_jwk(jwk, "secp256k1").map(PublicKeyMaterial::Secp256k1);
    }
    if let Some(multibase) = object.get("publicKeyMultibase").and_then(Value::as_str) {
        let bytes = bs58::decode(multibase.trim_start_matches('z'))
            .into_vec()
            .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        let key = k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes)
            .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        return Ok(PublicKeyMaterial::Secp256k1(key));
    }
    Err(VerificationMethodError::MissingKeyMaterial)
}

fn extract_secp256r1_key(
    object: &serde_json::Map<String, Value>,
) -> Result<PublicKeyMaterial, VerificationMethodError> {
    if let Some(jwk) = object.get("publicKeyJwk") {
        return extract_p256_key_from_jwk(jwk, "P-256").map(PublicKeyMaterial::Secp256r1);
    }
    if let Some(multibase) = object.get("publicKeyMultibase").and_then(Value::as_str) {
        let bytes = bs58::decode(multibase.trim_start_matches('z'))
            .into_vec()
            .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        let key = p256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes)
            .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        return Ok(PublicKeyMaterial::Secp256r1(key));
    }
    Err(VerificationMethodError::MissingKeyMaterial)
}

fn extract_ec_jwk_key(
    object: &serde_json::Map<String, Value>,
) -> Result<PublicKeyMaterial, VerificationMethodError> {
    let jwk = object
        .get("publicKeyJwk")
        .ok_or(VerificationMethodError::MissingKeyMaterial)?;
    let crv = jwk
        .get("crv")
        .and_then(Value::as_str)
        .ok_or(VerificationMethodError::InvalidKeyMaterial)?;
    match crv {
        "secp256k1" => extract_ec_key_from_jwk(jwk, crv).map(PublicKeyMaterial::Secp256k1),
        "P-256" => extract_p256_key_from_jwk(jwk, crv).map(PublicKeyMaterial::Secp256r1),
        _ => Err(VerificationMethodError::UnsupportedType(crv.to_string())),
    }
}

fn extract_ed25519_key(
    object: &serde_json::Map<String, Value>,
) -> Result<PublicKeyMaterial, VerificationMethodError> {
    if let Some(jwk) = object.get("publicKeyJwk") {
        let x = jwk
            .get("x")
            .and_then(Value::as_str)
            .ok_or(VerificationMethodError::InvalidKeyMaterial)?;
        let bytes = base64url_decode(x).map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        let key = ed25519_dalek::VerifyingKey::from_bytes(
            &bytes
                .try_into()
                .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?,
        )
        .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        return Ok(PublicKeyMaterial::Ed25519(key));
    }
    if let Some(multibase) = object.get("publicKeyMultibase").and_then(Value::as_str) {
        let mut bytes = bs58::decode(multibase.trim_start_matches('z'))
            .into_vec()
            .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        if bytes.len() == 34 && bytes.starts_with(&[0xed, 0x01]) {
            bytes = bytes[2..].to_vec();
        }
        let key = ed25519_dalek::VerifyingKey::from_bytes(
            &bytes
                .try_into()
                .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?,
        )
        .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        return Ok(PublicKeyMaterial::Ed25519(key));
    }
    if let Some(base58_key) = object.get("publicKeyBase58").and_then(Value::as_str) {
        let bytes = bs58::decode(base58_key)
            .into_vec()
            .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        let key = ed25519_dalek::VerifyingKey::from_bytes(
            &bytes
                .try_into()
                .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?,
        )
        .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
        return Ok(PublicKeyMaterial::Ed25519(key));
    }
    Err(VerificationMethodError::MissingKeyMaterial)
}

fn extract_x25519_key(
    object: &serde_json::Map<String, Value>,
) -> Result<PublicKeyMaterial, VerificationMethodError> {
    let multibase = object
        .get("publicKeyMultibase")
        .and_then(Value::as_str)
        .ok_or(VerificationMethodError::MissingKeyMaterial)?;
    let mut bytes = bs58::decode(multibase.trim_start_matches('z'))
        .into_vec()
        .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
    if bytes.len() == 34 && bytes.starts_with(&[0xec, 0x01]) {
        bytes = bytes[2..].to_vec();
    }
    let bytes: [u8; 32] = bytes
        .try_into()
        .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
    Ok(PublicKeyMaterial::X25519(bytes))
}

fn extract_ec_key_from_jwk(
    jwk: &Value,
    expected_curve: &str,
) -> Result<k256::ecdsa::VerifyingKey, VerificationMethodError> {
    let object = jwk
        .as_object()
        .ok_or(VerificationMethodError::InvalidKeyMaterial)?;
    if object.get("kty").and_then(Value::as_str) != Some("EC") {
        return Err(VerificationMethodError::InvalidKeyMaterial);
    }
    if object.get("crv").and_then(Value::as_str) != Some(expected_curve) {
        return Err(VerificationMethodError::InvalidKeyMaterial);
    }
    let x = decode_coordinate(object.get("x").and_then(Value::as_str))?;
    let y = decode_coordinate(object.get("y").and_then(Value::as_str))?;
    let encoded = k256::EncodedPoint::from_affine_coordinates((&x).into(), (&y).into(), false);
    k256::ecdsa::VerifyingKey::from_encoded_point(&encoded)
        .map_err(|_| VerificationMethodError::InvalidKeyMaterial)
}

fn extract_p256_key_from_jwk(
    jwk: &Value,
    expected_curve: &str,
) -> Result<p256::ecdsa::VerifyingKey, VerificationMethodError> {
    let object = jwk
        .as_object()
        .ok_or(VerificationMethodError::InvalidKeyMaterial)?;
    if object.get("kty").and_then(Value::as_str) != Some("EC") {
        return Err(VerificationMethodError::InvalidKeyMaterial);
    }
    if object.get("crv").and_then(Value::as_str) != Some(expected_curve) {
        return Err(VerificationMethodError::InvalidKeyMaterial);
    }
    let x = decode_coordinate(object.get("x").and_then(Value::as_str))?;
    let y = decode_coordinate(object.get("y").and_then(Value::as_str))?;
    let encoded = p256::EncodedPoint::from_affine_coordinates((&x).into(), (&y).into(), false);
    p256::ecdsa::VerifyingKey::from_encoded_point(&encoded)
        .map_err(|_| VerificationMethodError::InvalidKeyMaterial)
}

fn decode_coordinate(value: Option<&str>) -> Result<[u8; 32], VerificationMethodError> {
    let bytes = base64url_decode(value.ok_or(VerificationMethodError::InvalidKeyMaterial)?)
        .map_err(|_| VerificationMethodError::InvalidKeyMaterial)?;
    bytes
        .try_into()
        .map_err(|_| VerificationMethodError::InvalidKeyMaterial)
}