atproto-record 0.9.1

AT Protocol record signature operations - cryptographic signing and verification for AT Protocol records
Documentation
//! AT Protocol record signature operations.
//!
//! Provides cryptographic signing and verification capabilities for AT Protocol records using
//! elliptic curve digital signatures. Implements the AT Protocol signature format with proper
//! `$sig` object handling and IPLD DAG-CBOR serialization for secure record attestation.

use atproto_identity::key::{KeyData, sign, validate};
use serde_json::json;

use crate::errors::VerificationError;

/// Signs an AT Protocol record.
///
/// This function generates a signature for the provided record using the specified
/// key data and context information. The signature is embedded into the record
/// following AT Protocol signature conventions.
///
/// # Parameters
///
/// * `key_data` - The cryptographic key information wrapped in KeyData
/// * `record` - The JSON record to be signed
/// * `repository` - The repository DID context for the signature
/// * `collection` - The collection name context for the signature  
/// * `signature_object` - Optional additional fields for the signature object
///
/// # Returns
///
/// Returns the original record with a `signatures` field containing the new signature.
///
/// # Errors
///
/// Returns an error if:
/// - IPLD serialization fails
/// - Cryptographic signing fails
/// - JSON manipulation fails
pub async fn create(
    key_data: &KeyData,
    record: &serde_json::Value,
    repository: &str,
    collection: &str,
    signature_object: serde_json::Value,
) -> Result<serde_json::Value, VerificationError> {
    if let Some(record_map) = signature_object.as_object() {
        if !record_map.contains_key("issuer") {
            return Err(VerificationError::SignatureObjectMissingField {
                field: "issuer".to_string(),
            });
        }
        if !record_map.contains_key("issuedAt") {
            return Err(VerificationError::SignatureObjectMissingField {
                field: "issuedAt".to_string(),
            });
        }
    } else {
        return Err(VerificationError::InvalidSignatureObjectType);
    };

    // Prepare the $sig object.
    let mut sig = signature_object.clone();
    if let Some(record_map) = sig.as_object_mut() {
        record_map.insert("repository".to_string(), json!(repository));
        record_map.insert("collection".to_string(), json!(collection));
    }

    // Create a copy of the record with the $sig object for signing.
    let mut signing_record = record.clone();
    if let Some(record_map) = signing_record.as_object_mut() {
        record_map.remove("signatures");
        record_map.remove("$sig");
        record_map.insert("$sig".to_string(), sig);
    }

    // Create a signature.
    let serialized_signing_record = serde_ipld_dagcbor::to_vec(&signing_record)?;

    let signature = sign(key_data, &serialized_signing_record)?;
    let encoded_signature = multibase::encode(multibase::Base::Base64Url, &signature);

    // Compose the proof object
    let mut proof = signature_object.clone();
    if let Some(record_map) = proof.as_object_mut() {
        record_map.remove("repository");
        record_map.remove("collection");
        record_map.insert("signature".to_string(), json!(encoded_signature));
    }

    // Add the signature to the original record
    let mut signed_record = record.clone();

    if let Some(record_map) = signed_record.as_object_mut() {
        let mut signatures: Vec<serde_json::Value> = record
            .get("signatures")
            .and_then(|v| v.as_array().cloned())
            .unwrap_or_default();

        signatures.push(proof);

        record_map.remove("$sig");
        record_map.remove("signatures");

        // Add the $sig field
        record_map.insert("signatures".to_string(), json!(signatures));
    }

    Ok(signed_record)
}

/// Verifies a cryptographic signature on an AT Protocol record.
///
/// This function validates that the provided record contains a valid signature
/// from the specified issuer using the provided public key. It reconstructs
/// the signed content and verifies the cryptographic signature.
///
/// # Parameters
///
/// * `issuer` - The DID of the expected signature issuer
/// * `key_data` - The public key information for verification
/// * `record` - The signed JSON record to verify
/// * `repository` - The repository DID context for verification
/// * `collection` - The collection name context for verification
///
/// # Returns
///
/// Returns `Ok(())` if a valid signature from the issuer is found and verified.
///
/// # Errors
///
/// Returns a [`VerificationError`] if:
/// - No signatures field is found in the record
/// - No signature from the specified issuer is found
/// - Signature decoding or parsing fails
/// - Cryptographic verification fails
/// - Record serialization fails
pub async fn verify(
    issuer: &str,
    key_data: &KeyData,
    record: serde_json::Value,
    repository: &str,
    collection: &str,
) -> Result<(), VerificationError> {
    let signatures = record
        .get("sigs")
        .or_else(|| record.get("signatures"))
        .and_then(|v| v.as_array())
        .ok_or(VerificationError::NoSignaturesField)?;

    for sig_obj in signatures {
        // Extract the issuer from the signature object
        let signature_issuer = sig_obj
            .get("issuer")
            .and_then(|v| v.as_str())
            .ok_or(VerificationError::MissingIssuerField)?;

        let signature_value = sig_obj
            .get("signature")
            .and_then(|v| v.as_str())
            .ok_or(VerificationError::MissingSignatureField)?;

        if issuer != signature_issuer {
            continue;
        }

        let mut sig_variable = sig_obj.clone();

        if let Some(sig_map) = sig_variable.as_object_mut() {
            sig_map.remove("signature");
            sig_map.insert("repository".to_string(), json!(repository));
            sig_map.insert("collection".to_string(), json!(collection));
        }

        let mut signed_record = record.clone();
        if let Some(record_map) = signed_record.as_object_mut() {
            record_map.remove("signatures");
            record_map.insert("$sig".to_string(), sig_variable);
        }

        let serialized_record = serde_ipld_dagcbor::to_vec(&signed_record)
            .map_err(|error| VerificationError::RecordSerializationFailed { error })?;

        let (_, signature_bytes) = multibase::decode(signature_value)
            .map_err(|error| VerificationError::SignatureDecodingFailed { error })?;

        validate(key_data, &signature_bytes, &serialized_record)
            .map_err(|error| VerificationError::CryptographicValidationFailed { error })?;

        return Ok(());
    }

    Err(VerificationError::NoValidSignatureForIssuer {
        issuer: issuer.to_string(),
    })
}