Skip to main content

codec_multikey/
binding.rs

1// SPDX-FileCopyrightText: 2026 ReallyMe LLC
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use crate::error::{
6    classify_binding_algorithm, classify_binding_type, classify_multikey_codec, MultikeyError,
7};
8use crate::parse::{parse_multikey, ParsedMultikey};
9
10/// Validate binding metadata against the encoded multikey itself.
11///
12/// Parsing inside this boundary prevents a caller from forging the public
13/// fields of [`ParsedMultikey`] to claim another codec or algorithm.
14///
15/// ```
16/// use codec_multikey::{encode_multikey, validate_multikey_binding, KeyBindingInput};
17/// let encoded = encode_multikey("ed25519-pub", &[7_u8; 32])?;
18/// validate_multikey_binding(
19///     KeyBindingInput { binding_type: "Multikey", algorithm: None },
20///     &encoded,
21/// )?;
22/// # Ok::<(), codec_multikey::MultikeyError>(())
23/// ```
24///
25/// ```compile_fail
26/// use codec_multikey::{validate_multikey_binding, KeyBindingInput, ParsedMultikey};
27/// fn validate(binding: KeyBindingInput<'_>, forged: &ParsedMultikey) {
28///     let _ = validate_multikey_binding(binding, forged);
29/// }
30/// ```
31///
32/// # Errors
33///
34/// Returns a typed parsing or binding mismatch error.
35pub fn validate_multikey_binding(
36    binding: KeyBindingInput<'_>,
37    encoded_multikey: &str,
38) -> Result<(), MultikeyError> {
39    let parsed = parse_multikey(encoded_multikey)?;
40    validate_binding_fields(binding, &parsed)
41}
42
43/// Generic binding compatibility rules.
44/// Binding labels are protocol-facing metadata and are validated here
45/// as algorithm constraints over multikey-encoded public keys.
46/// Returns whether a binding-type label is compatible with a codec name.
47///
48/// Generic `Multikey` matches any supported codec; profile-specific labels
49/// match only their one codec. Unknown labels return `false`.
50pub fn binding_type_matches_codec(binding_type: &str, codec_name: &str) -> bool {
51    match binding_type {
52        // Generic Multikey (ALL supported algorithms)
53        "Multikey" => matches!(
54            codec_name,
55            "ed25519-pub"
56                | "ed448-pub"
57                | "x25519-pub"
58                | "p256-pub"
59                | "p384-pub"
60                | "p521-pub"
61                | "rsa-pub"
62                | "secp256k1-pub"
63                | "mldsa-44-pub"
64                | "mldsa-65-pub"
65                | "mldsa-87-pub"
66                | "mlkem-512-pub"
67                | "mlkem-768-pub"
68                | "mlkem-1024-pub"
69        ),
70
71        // Profile-specific / constrained bindings
72        "P256Key2024" => codec_name == "p256-pub",
73        "P384Key2024" => codec_name == "p384-pub",
74        "P521Key2024" => codec_name == "p521-pub",
75        "RsaVerificationKey2024" => codec_name == "rsa-pub",
76        "ML_DSA_44Key2024" => codec_name == "mldsa-44-pub",
77        "ML_DSA_65Key2024" => codec_name == "mldsa-65-pub",
78        "ML_DSA_87Key2024" => codec_name == "mldsa-87-pub",
79        "MLKEM512Key2024" => codec_name == "mlkem-512-pub",
80        "MLKEM768Key2024" => codec_name == "mlkem-768-pub",
81        "MLKEM1024Key2024" => codec_name == "mlkem-1024-pub",
82
83        _ => false,
84    }
85}
86
87/// Binding metadata to validate against a parsed multikey.
88pub struct KeyBindingInput<'a> {
89    /// The binding-type label (e.g. `Multikey`, `P256Key2024`).
90    pub binding_type: &'a str,
91    /// Optional explicit algorithm label; required for non-`Multikey` types.
92    pub algorithm: Option<&'a str>,
93}
94
95/// Validates that a binding's type and algorithm agree with a parsed key.
96///
97/// Fails closed: returns an error on a type/codec mismatch, an algorithm
98/// mismatch, or a missing required algorithm.
99pub fn validate_key_binding(
100    binding: KeyBindingInput<'_>,
101    parsed: &ParsedMultikey,
102) -> Result<(), MultikeyError> {
103    validate_binding_fields(binding, parsed)
104}
105
106fn validate_binding_fields(
107    binding: KeyBindingInput<'_>,
108    parsed: &ParsedMultikey,
109) -> Result<(), MultikeyError> {
110    if !binding_type_matches_codec(binding.binding_type, parsed.codec_name()) {
111        return Err(MultikeyError::BindingTypeCodecMismatch {
112            binding_type: classify_binding_type(binding.binding_type),
113            codec: classify_multikey_codec(parsed.codec_name()),
114            algorithm: classify_binding_algorithm(parsed.algorithm_name()),
115        });
116    }
117
118    if let Some(binding_alg) = binding.algorithm {
119        if binding_alg != parsed.algorithm_name() {
120            return Err(MultikeyError::BindingAlgorithmMismatch {
121                binding_alg: classify_binding_algorithm(binding_alg),
122                codec_algorithm: classify_binding_algorithm(parsed.algorithm_name()),
123            });
124        }
125    } else if binding.binding_type != "Multikey" {
126        return Err(MultikeyError::BindingAlgorithmMissing {
127            binding_type: classify_binding_type(binding.binding_type),
128        });
129    }
130
131    Ok(())
132}