use codec_multibase::multibase_to_bytes;
use codec_multicodec::{lookup_codec_prefix, KeyLength, KeyMaterialKind};
use crate::encode::MAX_RSA_PUBLIC_KEY_DER_LEN;
use crate::error::{classify_multikey_codec, MultikeyError};
use zeroize::Zeroizing;
pub struct ParsedMultikey {
codec_name: &'static str,
alg: &'static str,
public_key: Vec<u8>,
key_length: KeyLength,
}
impl ParsedMultikey {
#[must_use]
pub const fn codec_name(&self) -> &'static str {
self.codec_name
}
#[must_use]
pub const fn algorithm_name(&self) -> &'static str {
self.alg
}
#[must_use]
pub fn public_key(&self) -> &[u8] {
&self.public_key
}
#[must_use]
pub const fn key_length(&self) -> KeyLength {
self.key_length
}
#[must_use]
pub fn into_public_key(self) -> Vec<u8> {
self.public_key
}
}
pub fn parse_multikey(multibase_key: &str) -> Result<ParsedMultikey, MultikeyError> {
if multibase_key.len() < 2 {
return Err(MultikeyError::InvalidMultibase);
}
if !multibase_key.starts_with('z') {
return Err(MultikeyError::InvalidMultibase);
}
let raw = Zeroizing::new(
multibase_to_bytes(multibase_key).map_err(|_| MultikeyError::InvalidMultibase)?,
);
if raw.len() < 2 {
return Err(MultikeyError::DecodedTooShort(raw.len()));
}
let found = lookup_codec_prefix(&raw).ok_or(MultikeyError::UnknownCodecPrefix)?;
if found.key_material != KeyMaterialKind::PublicKey {
return Err(MultikeyError::NonPublicKeyMaterial);
}
let public_key = raw[found.codec.len()..].to_vec();
match found.key_length {
KeyLength::Fixed(expected) if public_key.len() != expected => {
return Err(MultikeyError::KeyLengthMismatch {
codec: classify_multikey_codec(found.name),
expected,
actual: public_key.len(),
});
}
KeyLength::Variable if public_key.is_empty() => return Err(MultikeyError::EmptyKey),
KeyLength::NotApplicable => return Err(MultikeyError::NonPublicKeyMaterial),
KeyLength::Fixed(_) | KeyLength::Variable => {}
}
if found.name == "rsa-pub" && public_key.len() > MAX_RSA_PUBLIC_KEY_DER_LEN {
return Err(MultikeyError::KeyTooLarge {
codec: classify_multikey_codec(found.name),
max: MAX_RSA_PUBLIC_KEY_DER_LEN,
actual: public_key.len(),
});
}
if matches!(
found.name,
"p256-pub" | "p384-pub" | "p521-pub" | "secp256k1-pub"
) && !matches!(public_key.first(), Some(0x02 | 0x03))
{
return Err(MultikeyError::InvalidCompressedPoint);
}
Ok(ParsedMultikey {
codec_name: found.name,
alg: found.alg,
public_key,
key_length: found.key_length,
})
}