Skip to main content

crypto_dispatch/
multikey.rs

1// SPDX-FileCopyrightText: Copyright © 2026 ReallyMe LLC. All rights reserved
2//
3// SPDX-License-Identifier: Apache-2.0
4
5#![allow(clippy::needless_return)]
6
7use crate::AlgorithmError;
8#[cfg(any(
9    feature = "ed25519",
10    feature = "x25519",
11    feature = "secp256k1",
12    feature = "p256",
13    feature = "p384",
14    feature = "p521",
15    feature = "ml-dsa-44",
16    feature = "ml-dsa-65",
17    feature = "ml-dsa-87",
18    feature = "ml-kem-512",
19    feature = "ml-kem-768",
20    feature = "ml-kem-1024"
21))]
22use codec_multikey::encode_multikey;
23use crypto_core::Algorithm;
24
25#[cfg(feature = "p256")]
26fn compress_p256_public_key(input: &[u8]) -> Result<Vec<u8>, AlgorithmError> {
27    #[cfg(all(
28        feature = "p256",
29        feature = "native",
30        not(all(feature = "wasm", target_arch = "wasm32"))
31    ))]
32    {
33        return crypto_p256::compress_p256(input)
34            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P256));
35    }
36
37    #[cfg(all(feature = "p256", feature = "wasm", target_arch = "wasm32"))]
38    {
39        return crypto_p256::compress_p256(input)
40            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P256));
41    }
42
43    #[cfg(not(any(
44        all(
45            feature = "p256",
46            feature = "native",
47            not(all(feature = "wasm", target_arch = "wasm32"))
48        ),
49        all(feature = "p256", feature = "wasm", target_arch = "wasm32")
50    )))]
51    {
52        let _ = input;
53        Err(AlgorithmError::UnsupportedAlgorithm(Algorithm::P256))
54    }
55}
56
57#[cfg(feature = "p384")]
58fn compress_p384_public_key(input: &[u8]) -> Result<Vec<u8>, AlgorithmError> {
59    #[cfg(all(
60        feature = "p384",
61        feature = "native",
62        not(all(feature = "wasm", target_arch = "wasm32"))
63    ))]
64    {
65        return crypto_p384::compress_p384(input)
66            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P384));
67    }
68
69    #[cfg(all(feature = "p384", feature = "wasm", target_arch = "wasm32"))]
70    {
71        return crypto_p384::compress_p384(input)
72            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P384));
73    }
74
75    #[cfg(not(any(
76        all(
77            feature = "p384",
78            feature = "native",
79            not(all(feature = "wasm", target_arch = "wasm32"))
80        ),
81        all(feature = "p384", feature = "wasm", target_arch = "wasm32")
82    )))]
83    {
84        let _ = input;
85        Err(AlgorithmError::UnsupportedAlgorithm(Algorithm::P384))
86    }
87}
88
89#[cfg(feature = "p521")]
90fn compress_p521_public_key(input: &[u8]) -> Result<Vec<u8>, AlgorithmError> {
91    #[cfg(all(
92        feature = "p521",
93        feature = "native",
94        not(all(feature = "wasm", target_arch = "wasm32"))
95    ))]
96    {
97        return crypto_p521::compress_p521(input)
98            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P521));
99    }
100
101    #[cfg(all(feature = "p521", feature = "wasm", target_arch = "wasm32"))]
102    {
103        return crypto_p521::compress_p521(input)
104            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P521));
105    }
106
107    #[cfg(not(any(
108        all(
109            feature = "p521",
110            feature = "native",
111            not(all(feature = "wasm", target_arch = "wasm32"))
112        ),
113        all(feature = "p521", feature = "wasm", target_arch = "wasm32")
114    )))]
115    {
116        let _ = input;
117        Err(AlgorithmError::UnsupportedAlgorithm(Algorithm::P521))
118    }
119}
120
121#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
122fn canonicalize_sec1_public_key(
123    alg: Algorithm,
124    public_key: &[u8],
125    compressed_len: usize,
126    uncompressed_len: usize,
127    raw_len: usize,
128    compress: fn(&[u8]) -> Result<Vec<u8>, AlgorithmError>,
129) -> Result<Vec<u8>, AlgorithmError> {
130    match public_key.len() {
131        len if len == compressed_len => Ok(public_key.to_vec()),
132        len if len == uncompressed_len => compress(public_key),
133        len if len == raw_len => {
134            let capacity = raw_len
135                .checked_add(1)
136                .ok_or(AlgorithmError::InvalidKey(alg))?;
137            let mut sec1 = Vec::with_capacity(capacity);
138            sec1.push(0x04);
139            sec1.extend_from_slice(public_key);
140            compress(&sec1)
141        }
142        _ => Err(AlgorithmError::InvalidKey(alg)),
143    }
144}
145
146/// Multicodec/multikey-encode a public key, canonicalizing P-256 to its
147/// compressed SEC1 form. Returns the `z`-prefixed multikey string.
148#[cfg(not(any(
149    feature = "ed25519",
150    feature = "x25519",
151    feature = "secp256k1",
152    feature = "p256",
153    feature = "p384",
154    feature = "p521",
155    feature = "ml-dsa-44",
156    feature = "ml-dsa-65",
157    feature = "ml-dsa-87",
158    feature = "ml-kem-512",
159    feature = "ml-kem-768",
160    feature = "ml-kem-1024"
161)))]
162pub fn public_key_to_multikey(alg: Algorithm, public_key: &[u8]) -> Result<String, AlgorithmError> {
163    let _ = public_key;
164    Err(AlgorithmError::UnsupportedAlgorithm(alg))
165}
166
167/// Multicodec/multikey-encode a public key, canonicalizing P-256 to its
168/// compressed SEC1 form. Returns the `z`-prefixed multikey string.
169#[cfg(any(
170    feature = "ed25519",
171    feature = "x25519",
172    feature = "secp256k1",
173    feature = "p256",
174    feature = "p384",
175    feature = "p521",
176    feature = "ml-dsa-44",
177    feature = "ml-dsa-65",
178    feature = "ml-dsa-87",
179    feature = "ml-kem-512",
180    feature = "ml-kem-768",
181    feature = "ml-kem-1024"
182))]
183pub fn public_key_to_multikey(alg: Algorithm, public_key: &[u8]) -> Result<String, AlgorithmError> {
184    let (codec, key_bytes) = match alg {
185        Algorithm::Ed25519 => {
186            #[cfg(feature = "ed25519")]
187            {
188                ("ed25519-pub", public_key.to_vec())
189            }
190            #[cfg(not(feature = "ed25519"))]
191            {
192                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
193            }
194        }
195        Algorithm::X25519 => {
196            #[cfg(feature = "x25519")]
197            {
198                ("x25519-pub", public_key.to_vec())
199            }
200            #[cfg(not(feature = "x25519"))]
201            {
202                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
203            }
204        }
205        Algorithm::Secp256k1 => {
206            #[cfg(feature = "secp256k1")]
207            {
208                ("secp256k1-pub", public_key.to_vec())
209            }
210            #[cfg(not(feature = "secp256k1"))]
211            {
212                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
213            }
214        }
215        Algorithm::P384 => {
216            #[cfg(feature = "p384")]
217            {
218                (
219                    "p384-pub",
220                    canonicalize_sec1_public_key(
221                        alg,
222                        public_key,
223                        crypto_p384::P384_PUBLIC_KEY_COMPRESSED_LEN,
224                        crypto_p384::P384_PUBLIC_KEY_UNCOMPRESSED_LEN,
225                        crypto_p384::P384_PUBLIC_KEY_RAW_LEN,
226                        compress_p384_public_key,
227                    )?,
228                )
229            }
230            #[cfg(not(feature = "p384"))]
231            {
232                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
233            }
234        }
235        Algorithm::P521 => {
236            #[cfg(feature = "p521")]
237            {
238                (
239                    "p521-pub",
240                    canonicalize_sec1_public_key(
241                        alg,
242                        public_key,
243                        crypto_p521::P521_PUBLIC_KEY_COMPRESSED_LEN,
244                        crypto_p521::P521_PUBLIC_KEY_UNCOMPRESSED_LEN,
245                        crypto_p521::P521_PUBLIC_KEY_RAW_LEN,
246                        compress_p521_public_key,
247                    )?,
248                )
249            }
250            #[cfg(not(feature = "p521"))]
251            {
252                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
253            }
254        }
255        Algorithm::MlDsa44 => {
256            #[cfg(feature = "ml-dsa-44")]
257            {
258                ("mldsa-44-pub", public_key.to_vec())
259            }
260            #[cfg(not(feature = "ml-dsa-44"))]
261            {
262                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
263            }
264        }
265        Algorithm::MlDsa65 => {
266            #[cfg(feature = "ml-dsa-65")]
267            {
268                ("mldsa-65-pub", public_key.to_vec())
269            }
270            #[cfg(not(feature = "ml-dsa-65"))]
271            {
272                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
273            }
274        }
275        Algorithm::MlDsa87 => {
276            #[cfg(feature = "ml-dsa-87")]
277            {
278                ("mldsa-87-pub", public_key.to_vec())
279            }
280            #[cfg(not(feature = "ml-dsa-87"))]
281            {
282                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
283            }
284        }
285        Algorithm::MlKem512 => {
286            #[cfg(feature = "ml-kem-512")]
287            {
288                ("mlkem-512-pub", public_key.to_vec())
289            }
290            #[cfg(not(feature = "ml-kem-512"))]
291            {
292                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
293            }
294        }
295        Algorithm::MlKem768 => {
296            #[cfg(feature = "ml-kem-768")]
297            {
298                ("mlkem-768-pub", public_key.to_vec())
299            }
300            #[cfg(not(feature = "ml-kem-768"))]
301            {
302                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
303            }
304        }
305        Algorithm::MlKem1024 => {
306            #[cfg(feature = "ml-kem-1024")]
307            {
308                ("mlkem-1024-pub", public_key.to_vec())
309            }
310            #[cfg(not(feature = "ml-kem-1024"))]
311            {
312                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
313            }
314        }
315        Algorithm::XWing768 | Algorithm::XWing1024 => {
316            return Err(AlgorithmError::UnsupportedAlgorithm(alg));
317        }
318
319        // Canonicalization step: P-256 must be compressed
320        Algorithm::P256 => {
321            #[cfg(not(feature = "p256"))]
322            {
323                return Err(AlgorithmError::UnsupportedAlgorithm(alg));
324            }
325
326            #[cfg(feature = "p256")]
327            {
328                let compressed = canonicalize_sec1_public_key(
329                    alg,
330                    public_key,
331                    33,
332                    65,
333                    64,
334                    compress_p256_public_key,
335                )?;
336
337                ("p256-pub", compressed)
338            }
339        }
340    };
341
342    encode_multikey(codec, &key_bytes).map_err(|_| AlgorithmError::InvalidKey(alg))
343}