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;
8use codec_multikey::encode_multikey;
9use crypto_core::Algorithm;
10
11fn compress_p256_public_key(input: &[u8]) -> Result<Vec<u8>, AlgorithmError> {
12    #[cfg(all(feature = "native", not(all(feature = "wasm", target_arch = "wasm32"))))]
13    {
14        return crypto_p256::compress_p256(input)
15            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P256));
16    }
17
18    #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
19    {
20        return crypto_p256::compress_p256(input)
21            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P256));
22    }
23
24    #[cfg(not(any(
25        all(feature = "native", not(all(feature = "wasm", target_arch = "wasm32"))),
26        all(feature = "wasm", target_arch = "wasm32")
27    )))]
28    {
29        let _ = input;
30        Err(AlgorithmError::UnsupportedAlgorithm(Algorithm::P256))
31    }
32}
33
34fn compress_p384_public_key(input: &[u8]) -> Result<Vec<u8>, AlgorithmError> {
35    #[cfg(any(feature = "native", feature = "wasm"))]
36    {
37        return crypto_p384::compress_p384(input)
38            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P384));
39    }
40
41    #[cfg(not(any(feature = "native", feature = "wasm")))]
42    {
43        let _ = input;
44        Err(AlgorithmError::UnsupportedAlgorithm(Algorithm::P384))
45    }
46}
47
48fn compress_p521_public_key(input: &[u8]) -> Result<Vec<u8>, AlgorithmError> {
49    #[cfg(any(feature = "native", feature = "wasm"))]
50    {
51        return crypto_p521::compress_p521(input)
52            .map_err(|_| AlgorithmError::InvalidKey(Algorithm::P521));
53    }
54
55    #[cfg(not(any(feature = "native", feature = "wasm")))]
56    {
57        let _ = input;
58        Err(AlgorithmError::UnsupportedAlgorithm(Algorithm::P521))
59    }
60}
61
62fn canonicalize_sec1_public_key(
63    alg: Algorithm,
64    public_key: &[u8],
65    compressed_len: usize,
66    uncompressed_len: usize,
67    raw_len: usize,
68    compress: fn(&[u8]) -> Result<Vec<u8>, AlgorithmError>,
69) -> Result<Vec<u8>, AlgorithmError> {
70    match public_key.len() {
71        len if len == compressed_len => Ok(public_key.to_vec()),
72        len if len == uncompressed_len => compress(public_key),
73        len if len == raw_len => {
74            let capacity = raw_len
75                .checked_add(1)
76                .ok_or(AlgorithmError::InvalidKey(alg))?;
77            let mut sec1 = Vec::with_capacity(capacity);
78            sec1.push(0x04);
79            sec1.extend_from_slice(public_key);
80            compress(&sec1)
81        }
82        _ => Err(AlgorithmError::InvalidKey(alg)),
83    }
84}
85
86/// Multicodec/multikey-encode a public key, canonicalizing P-256 to its
87/// compressed SEC1 form. Returns the `z`-prefixed multikey string.
88pub fn public_key_to_multikey(alg: Algorithm, public_key: &[u8]) -> Result<String, AlgorithmError> {
89    let (codec, key_bytes) = match alg {
90        Algorithm::Ed25519 => ("ed25519-pub", public_key.to_vec()),
91        Algorithm::X25519 => ("x25519-pub", public_key.to_vec()),
92        Algorithm::Secp256k1 => ("secp256k1-pub", public_key.to_vec()),
93        Algorithm::P384 => (
94            "p384-pub",
95            canonicalize_sec1_public_key(
96                alg,
97                public_key,
98                crypto_p384::P384_PUBLIC_KEY_COMPRESSED_LEN,
99                crypto_p384::P384_PUBLIC_KEY_UNCOMPRESSED_LEN,
100                crypto_p384::P384_PUBLIC_KEY_RAW_LEN,
101                compress_p384_public_key,
102            )?,
103        ),
104        Algorithm::P521 => (
105            "p521-pub",
106            canonicalize_sec1_public_key(
107                alg,
108                public_key,
109                crypto_p521::P521_PUBLIC_KEY_COMPRESSED_LEN,
110                crypto_p521::P521_PUBLIC_KEY_UNCOMPRESSED_LEN,
111                crypto_p521::P521_PUBLIC_KEY_RAW_LEN,
112                compress_p521_public_key,
113            )?,
114        ),
115        Algorithm::MlDsa44 => ("mldsa-44-pub", public_key.to_vec()),
116        Algorithm::MlDsa65 => ("mldsa-65-pub", public_key.to_vec()),
117        Algorithm::MlDsa87 => ("mldsa-87-pub", public_key.to_vec()),
118        Algorithm::MlKem512 => ("mlkem-512-pub", public_key.to_vec()),
119        Algorithm::MlKem768 => ("mlkem-768-pub", public_key.to_vec()),
120        Algorithm::MlKem1024 => ("mlkem-1024-pub", public_key.to_vec()),
121        Algorithm::XWing768 | Algorithm::XWing1024 => {
122            return Err(AlgorithmError::UnsupportedAlgorithm(alg));
123        }
124
125        // Canonicalization step: P-256 must be compressed
126        Algorithm::P256 => {
127            let compressed = canonicalize_sec1_public_key(
128                alg,
129                public_key,
130                33,
131                65,
132                64,
133                compress_p256_public_key,
134            )?;
135
136            ("p256-pub", compressed)
137        }
138    };
139
140    encode_multikey(codec, &key_bytes).map_err(|_| AlgorithmError::InvalidKey(alg))
141}