reallyme-codec-multikey 0.3.1

Multikey encoding and validation support for ReallyMe Codec.
Documentation
// SPDX-FileCopyrightText: 2026 ReallyMe LLC
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::error::{
    classify_binding_algorithm, classify_binding_type, classify_multikey_codec, MultikeyError,
};
use crate::parse::{parse_multikey, ParsedMultikey};

/// Validate binding metadata against the encoded multikey itself.
///
/// Parsing inside this boundary prevents a caller from forging the public
/// fields of [`ParsedMultikey`] to claim another codec or algorithm.
///
/// ```
/// use codec_multikey::{encode_multikey, validate_multikey_binding, KeyBindingInput};
/// let encoded = encode_multikey("ed25519-pub", &[7_u8; 32])?;
/// validate_multikey_binding(
///     KeyBindingInput { binding_type: "Multikey", algorithm: None },
///     &encoded,
/// )?;
/// # Ok::<(), codec_multikey::MultikeyError>(())
/// ```
///
/// ```compile_fail
/// use codec_multikey::{validate_multikey_binding, KeyBindingInput, ParsedMultikey};
/// fn validate(binding: KeyBindingInput<'_>, forged: &ParsedMultikey) {
///     let _ = validate_multikey_binding(binding, forged);
/// }
/// ```
///
/// # Errors
///
/// Returns a typed parsing or binding mismatch error.
pub fn validate_multikey_binding(
    binding: KeyBindingInput<'_>,
    encoded_multikey: &str,
) -> Result<(), MultikeyError> {
    let parsed = parse_multikey(encoded_multikey)?;
    validate_binding_fields(binding, &parsed)
}

/// Generic binding compatibility rules.
/// Binding labels are protocol-facing metadata and are validated here
/// as algorithm constraints over multikey-encoded public keys.
/// Returns whether a binding-type label is compatible with a codec name.
///
/// Generic `Multikey` matches any supported codec; profile-specific labels
/// match only their one codec. Unknown labels return `false`.
pub fn binding_type_matches_codec(binding_type: &str, codec_name: &str) -> bool {
    match binding_type {
        // Generic Multikey (ALL supported algorithms)
        "Multikey" => matches!(
            codec_name,
            "ed25519-pub"
                | "ed448-pub"
                | "x25519-pub"
                | "p256-pub"
                | "p384-pub"
                | "p521-pub"
                | "rsa-pub"
                | "secp256k1-pub"
                | "mldsa-44-pub"
                | "mldsa-65-pub"
                | "mldsa-87-pub"
                | "mlkem-512-pub"
                | "mlkem-768-pub"
                | "mlkem-1024-pub"
        ),

        // Profile-specific / constrained bindings
        "P256Key2024" => codec_name == "p256-pub",
        "P384Key2024" => codec_name == "p384-pub",
        "P521Key2024" => codec_name == "p521-pub",
        "RsaVerificationKey2024" => codec_name == "rsa-pub",
        "ML_DSA_44Key2024" => codec_name == "mldsa-44-pub",
        "ML_DSA_65Key2024" => codec_name == "mldsa-65-pub",
        "ML_DSA_87Key2024" => codec_name == "mldsa-87-pub",
        "MLKEM512Key2024" => codec_name == "mlkem-512-pub",
        "MLKEM768Key2024" => codec_name == "mlkem-768-pub",
        "MLKEM1024Key2024" => codec_name == "mlkem-1024-pub",

        _ => false,
    }
}

/// Binding metadata to validate against a parsed multikey.
pub struct KeyBindingInput<'a> {
    /// The binding-type label (e.g. `Multikey`, `P256Key2024`).
    pub binding_type: &'a str,
    /// Optional explicit algorithm label; required for non-`Multikey` types.
    pub algorithm: Option<&'a str>,
}

/// Validates that a binding's type and algorithm agree with a parsed key.
///
/// Fails closed: returns an error on a type/codec mismatch, an algorithm
/// mismatch, or a missing required algorithm.
pub fn validate_key_binding(
    binding: KeyBindingInput<'_>,
    parsed: &ParsedMultikey,
) -> Result<(), MultikeyError> {
    validate_binding_fields(binding, parsed)
}

fn validate_binding_fields(
    binding: KeyBindingInput<'_>,
    parsed: &ParsedMultikey,
) -> Result<(), MultikeyError> {
    if !binding_type_matches_codec(binding.binding_type, parsed.codec_name()) {
        return Err(MultikeyError::BindingTypeCodecMismatch {
            binding_type: classify_binding_type(binding.binding_type),
            codec: classify_multikey_codec(parsed.codec_name()),
            algorithm: classify_binding_algorithm(parsed.algorithm_name()),
        });
    }

    if let Some(binding_alg) = binding.algorithm {
        if binding_alg != parsed.algorithm_name() {
            return Err(MultikeyError::BindingAlgorithmMismatch {
                binding_alg: classify_binding_algorithm(binding_alg),
                codec_algorithm: classify_binding_algorithm(parsed.algorithm_name()),
            });
        }
    } else if binding.binding_type != "Multikey" {
        return Err(MultikeyError::BindingAlgorithmMissing {
            binding_type: classify_binding_type(binding.binding_type),
        });
    }

    Ok(())
}