1use codec_multibase::multibase_to_bytes;
6use codec_multicodec::{lookup_codec_prefix, KeyLength, KeyMaterialKind};
7
8use crate::encode::MAX_RSA_PUBLIC_KEY_DER_LEN;
9use crate::error::{classify_multikey_codec, MultikeyError};
10use zeroize::Zeroizing;
11
12pub struct ParsedMultikey {
25 codec_name: &'static str,
27 alg: &'static str,
29 public_key: Vec<u8>,
31 key_length: KeyLength,
33}
34
35impl ParsedMultikey {
36 #[must_use]
38 pub const fn codec_name(&self) -> &'static str {
39 self.codec_name
40 }
41
42 #[must_use]
44 pub const fn algorithm_name(&self) -> &'static str {
45 self.alg
46 }
47
48 #[must_use]
50 pub fn public_key(&self) -> &[u8] {
51 &self.public_key
52 }
53
54 #[must_use]
56 pub const fn key_length(&self) -> KeyLength {
57 self.key_length
58 }
59
60 #[must_use]
62 pub fn into_public_key(self) -> Vec<u8> {
63 self.public_key
64 }
65}
66
67pub fn parse_multikey(multibase_key: &str) -> Result<ParsedMultikey, MultikeyError> {
72 if multibase_key.len() < 2 {
73 return Err(MultikeyError::InvalidMultibase);
74 }
75 if !multibase_key.starts_with('z') {
79 return Err(MultikeyError::InvalidMultibase);
80 }
81
82 let raw = Zeroizing::new(
84 multibase_to_bytes(multibase_key).map_err(|_| MultikeyError::InvalidMultibase)?,
85 );
86
87 if raw.len() < 2 {
88 return Err(MultikeyError::DecodedTooShort(raw.len()));
89 }
90
91 let found = lookup_codec_prefix(&raw).ok_or(MultikeyError::UnknownCodecPrefix)?;
93
94 if found.key_material != KeyMaterialKind::PublicKey {
95 return Err(MultikeyError::NonPublicKeyMaterial);
96 }
97
98 let public_key = raw[found.codec.len()..].to_vec();
99
100 match found.key_length {
102 KeyLength::Fixed(expected) if public_key.len() != expected => {
103 return Err(MultikeyError::KeyLengthMismatch {
104 codec: classify_multikey_codec(found.name),
105 expected,
106 actual: public_key.len(),
107 });
108 }
109 KeyLength::Variable if public_key.is_empty() => return Err(MultikeyError::EmptyKey),
110 KeyLength::NotApplicable => return Err(MultikeyError::NonPublicKeyMaterial),
111 KeyLength::Fixed(_) | KeyLength::Variable => {}
112 }
113
114 if found.name == "rsa-pub" && public_key.len() > MAX_RSA_PUBLIC_KEY_DER_LEN {
115 return Err(MultikeyError::KeyTooLarge {
116 codec: classify_multikey_codec(found.name),
117 max: MAX_RSA_PUBLIC_KEY_DER_LEN,
118 actual: public_key.len(),
119 });
120 }
121
122 if matches!(
123 found.name,
124 "p256-pub" | "p384-pub" | "p521-pub" | "secp256k1-pub"
125 ) && !matches!(public_key.first(), Some(0x02 | 0x03))
126 {
127 return Err(MultikeyError::InvalidCompressedPoint);
128 }
129
130 Ok(ParsedMultikey {
131 codec_name: found.name,
132 alg: found.alg,
133 public_key,
134 key_length: found.key_length,
135 })
136}