Skip to main content

crypto_dispatch/
validation.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use crate::AlgorithmError;
6use codec_multikey::{parse_multikey, validate_key_binding, KeyBindingInput};
7use crypto_core::Algorithm;
8
9const SLH_DSA_SHA2_128S_PUBLIC_KEY_LEN: usize = 32;
10
11fn expected_public_key_len(algorithm: Algorithm) -> Result<usize, AlgorithmError> {
12    match algorithm {
13        Algorithm::Ed25519 => {
14            #[cfg(feature = "ed25519")]
15            {
16                Ok(32)
17            }
18            #[cfg(not(feature = "ed25519"))]
19            {
20                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
21            }
22        }
23        Algorithm::X25519 => {
24            #[cfg(feature = "x25519")]
25            {
26                Ok(32)
27            }
28            #[cfg(not(feature = "x25519"))]
29            {
30                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
31            }
32        }
33        Algorithm::P256 => {
34            #[cfg(feature = "p256")]
35            {
36                Ok(33)
37            }
38            #[cfg(not(feature = "p256"))]
39            {
40                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
41            }
42        }
43        Algorithm::P384 => {
44            #[cfg(feature = "p384")]
45            {
46                Ok(crypto_p384::P384_PUBLIC_KEY_COMPRESSED_LEN)
47            }
48            #[cfg(not(feature = "p384"))]
49            {
50                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
51            }
52        }
53        Algorithm::P521 => {
54            #[cfg(feature = "p521")]
55            {
56                Ok(crypto_p521::P521_PUBLIC_KEY_COMPRESSED_LEN)
57            }
58            #[cfg(not(feature = "p521"))]
59            {
60                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
61            }
62        }
63        Algorithm::Secp256k1 => {
64            #[cfg(feature = "secp256k1")]
65            {
66                Ok(33)
67            }
68            #[cfg(not(feature = "secp256k1"))]
69            {
70                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
71            }
72        }
73        Algorithm::MlDsa44 => {
74            #[cfg(feature = "ml-dsa-44")]
75            {
76                Ok(1312)
77            }
78            #[cfg(not(feature = "ml-dsa-44"))]
79            {
80                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
81            }
82        }
83        Algorithm::MlDsa65 => {
84            #[cfg(feature = "ml-dsa-65")]
85            {
86                Ok(1952)
87            }
88            #[cfg(not(feature = "ml-dsa-65"))]
89            {
90                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
91            }
92        }
93        Algorithm::MlDsa87 => {
94            #[cfg(feature = "ml-dsa-87")]
95            {
96                Ok(2592)
97            }
98            #[cfg(not(feature = "ml-dsa-87"))]
99            {
100                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
101            }
102        }
103        Algorithm::SlhDsaSha2_128s => Ok(SLH_DSA_SHA2_128S_PUBLIC_KEY_LEN),
104        Algorithm::MlKem512 => {
105            #[cfg(feature = "ml-kem-512")]
106            {
107                Ok(800)
108            }
109            #[cfg(not(feature = "ml-kem-512"))]
110            {
111                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
112            }
113        }
114        Algorithm::MlKem768 => {
115            #[cfg(feature = "ml-kem-768")]
116            {
117                Ok(1184)
118            }
119            #[cfg(not(feature = "ml-kem-768"))]
120            {
121                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
122            }
123        }
124        Algorithm::MlKem1024 => {
125            #[cfg(feature = "ml-kem-1024")]
126            {
127                Ok(1568)
128            }
129            #[cfg(not(feature = "ml-kem-1024"))]
130            {
131                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
132            }
133        }
134        Algorithm::XWing768 => {
135            #[cfg(feature = "x-wing")]
136            {
137                Ok(crypto_x_wing::X_WING_768_PUBLIC_KEY_LEN)
138            }
139            #[cfg(not(feature = "x-wing"))]
140            {
141                Err(AlgorithmError::UnsupportedAlgorithm(algorithm))
142            }
143        }
144    }
145}
146
147/// Structural validation of a typed public-key binding.
148///
149/// This performs NO cryptography.
150/// It checks:
151/// - multikey encoding
152/// - codec ↔ algorithm match
153/// - key length
154pub fn validate_verification_method_multikey(
155    algorithm: Algorithm,
156    binding_type: &str,
157    public_key_multibase: &str,
158) -> Result<(), AlgorithmError> {
159    // Check the compiled policy first so disabled algorithms return a stable
160    // unsupported error instead of leaking parser-specific validation details.
161    let expected_len = expected_public_key_len(algorithm)?;
162
163    let parsed =
164        parse_multikey(public_key_multibase).map_err(|_| AlgorithmError::InvalidKey(algorithm))?;
165
166    // Validate that the declared binding label is compatible with the key algorithm.
167    validate_key_binding(
168        KeyBindingInput {
169            binding_type,
170            algorithm: Some(algorithm.as_str()),
171        },
172        &parsed,
173    )
174    .map_err(|_| AlgorithmError::InvalidKey(algorithm))?;
175
176    if parsed.public_key.len() != expected_len {
177        return Err(AlgorithmError::InvalidKey(algorithm));
178    }
179
180    Ok(())
181}