1use codec_multibase::bytes_to_multibase58btc;
6use codec_multicodec::{KeyLength, KeyMaterialKind, MULTICODEC_TABLE};
7
8use crate::error::{classify_multikey_codec, CodecNameReason, MultikeyError};
9
10pub const MAX_RSA_PUBLIC_KEY_DER_LEN: usize = 4 * 1024;
16
17pub fn encode_multikey(codec_name: &str, public_key: &[u8]) -> Result<String, MultikeyError> {
22 let (canonical_codec_name, spec) = MULTICODEC_TABLE
23 .iter()
24 .find(|(name, _)| *name == codec_name)
25 .ok_or(MultikeyError::UnknownCodecName {
26 reason: CodecNameReason::Unsupported,
27 })?;
28
29 if spec.key_material != KeyMaterialKind::PublicKey {
30 return Err(MultikeyError::UnknownCodecName {
31 reason: CodecNameReason::Unsupported,
32 });
33 }
34
35 match spec.key_length {
36 KeyLength::Fixed(expected) if public_key.len() != expected => {
37 return Err(MultikeyError::KeyLengthMismatch {
38 codec: classify_multikey_codec(canonical_codec_name),
39 expected,
40 actual: public_key.len(),
41 });
42 }
43 KeyLength::Variable if public_key.is_empty() => return Err(MultikeyError::EmptyKey),
44 KeyLength::NotApplicable => {
45 return Err(MultikeyError::UnknownCodecName {
46 reason: CodecNameReason::Unsupported,
47 });
48 }
49 KeyLength::Fixed(_) | KeyLength::Variable => {}
50 }
51
52 if *canonical_codec_name == "rsa-pub" && public_key.len() > MAX_RSA_PUBLIC_KEY_DER_LEN {
53 return Err(MultikeyError::KeyTooLarge {
54 codec: classify_multikey_codec(canonical_codec_name),
55 max: MAX_RSA_PUBLIC_KEY_DER_LEN,
56 actual: public_key.len(),
57 });
58 }
59
60 if matches!(
61 *canonical_codec_name,
62 "p256-pub" | "p384-pub" | "p521-pub" | "secp256k1-pub"
63 ) && !matches!(public_key.first(), Some(0x02 | 0x03))
64 {
65 return Err(MultikeyError::InvalidCompressedPoint);
66 }
67
68 let capacity = spec
69 .codec
70 .len()
71 .checked_add(public_key.len())
72 .ok_or(MultikeyError::EncodedPayloadTooLarge)?;
73 let mut payload = Vec::with_capacity(capacity);
74 payload.extend_from_slice(spec.codec);
75 payload.extend_from_slice(public_key);
76
77 bytes_to_multibase58btc(&payload).map_err(|_| MultikeyError::EncodedPayloadTooLarge)
78}