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