use codec_multicodec::KeyLength;
use codec_multikey::{parse_multikey as parse_primitive_multikey, MultikeyError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum MultikeyOperationError {
#[error("unknown multikey codec")]
UnknownCodec,
#[error("invalid multikey")]
InvalidMultikey,
#[error("multikey operation invariant violation")]
OperationInvariant,
}
pub struct ParsedMultikey {
codec_name: &'static str,
algorithm_name: &'static str,
public_key: Vec<u8>,
expected_public_key_length: Option<usize>,
}
impl ParsedMultikey {
pub const fn codec_name(&self) -> &'static str {
self.codec_name
}
pub const fn algorithm_name(&self) -> &'static str {
self.algorithm_name
}
#[cfg(test)]
pub fn public_key(&self) -> &[u8] {
self.public_key.as_slice()
}
pub fn into_public_key(self) -> Vec<u8> {
self.public_key
}
pub const fn expected_public_key_length(&self) -> Option<usize> {
self.expected_public_key_length
}
pub const fn variable_public_key_length(&self) -> bool {
self.expected_public_key_length.is_none()
}
}
pub fn parse_multikey(multikey: &str) -> Result<ParsedMultikey, MultikeyOperationError> {
let parsed = parse_primitive_multikey(multikey).map_err(multikey_operation_error)?;
let expected_public_key_length = match parsed.key_length() {
KeyLength::Fixed(length) => Some(length),
KeyLength::Variable => None,
KeyLength::NotApplicable => return Err(MultikeyOperationError::OperationInvariant),
};
Ok(ParsedMultikey {
codec_name: parsed.codec_name(),
algorithm_name: parsed.algorithm_name(),
public_key: parsed.into_public_key(),
expected_public_key_length,
})
}
fn multikey_operation_error(error: MultikeyError) -> MultikeyOperationError {
match error {
MultikeyError::UnknownCodecPrefix | MultikeyError::UnknownCodecName { .. } => {
MultikeyOperationError::UnknownCodec
}
MultikeyError::InvalidMultibase
| MultikeyError::NonPublicKeyMaterial
| MultikeyError::EmptyKey
| MultikeyError::InvalidCompressedPoint
| MultikeyError::DecodedTooShort(_)
| MultikeyError::KeyLengthMismatch { .. }
| MultikeyError::KeyTooLarge { .. }
| MultikeyError::EncodedPayloadTooLarge
| MultikeyError::BindingTypeCodecMismatch { .. }
| MultikeyError::BindingAlgorithmMismatch { .. }
| MultikeyError::BindingAlgorithmMissing { .. } => MultikeyOperationError::InvalidMultikey,
_ => MultikeyOperationError::OperationInvariant,
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use codec_multicodec::KeyLength;
use codec_multikey::encode_multikey;
use super::{parse_multikey, parse_primitive_multikey, MultikeyOperationError};
#[test]
fn parse_multikey_preserves_primitive_parse_semantics() {
let public_key = [7_u8; 32];
let multikey = encode_multikey("ed25519-pub", &public_key).unwrap();
let primitive = parse_primitive_multikey(&multikey).unwrap();
let parsed = parse_multikey(&multikey).unwrap();
assert_eq!(parsed.codec_name(), primitive.codec_name());
assert_eq!(parsed.algorithm_name(), primitive.algorithm_name());
assert_eq!(parsed.public_key(), primitive.public_key());
assert_eq!(
parsed.expected_public_key_length(),
Some(match primitive.key_length() {
KeyLength::Fixed(length) => length,
KeyLength::Variable | KeyLength::NotApplicable => 0,
})
);
assert!(!parsed.variable_public_key_length());
}
#[test]
fn parse_multikey_represents_variable_public_key_length_explicitly() {
let public_key = [9_u8; 80];
let multikey = encode_multikey("rsa-pub", &public_key).unwrap();
let parsed = parse_multikey(&multikey).unwrap();
assert_eq!(parsed.codec_name(), "rsa-pub");
assert_eq!(parsed.algorithm_name(), "RSA");
assert_eq!(parsed.public_key(), public_key);
assert_eq!(parsed.expected_public_key_length(), None);
assert!(parsed.variable_public_key_length());
}
#[test]
fn parse_multikey_maps_noncanonical_multibase_to_invalid_multikey() {
let error = match parse_multikey("not-a-key") {
Ok(_) => MultikeyOperationError::OperationInvariant,
Err(error) => error,
};
assert_eq!(error, MultikeyOperationError::InvalidMultikey);
}
#[test]
fn parse_multikey_maps_unknown_prefix_to_unknown_codec() {
let multikey = codec_multibase::bytes_to_multibase58btc(&[0, 0, 7]).unwrap();
let error = match parse_multikey(&multikey) {
Ok(_) => MultikeyOperationError::OperationInvariant,
Err(error) => error,
};
assert_eq!(error, MultikeyOperationError::UnknownCodec);
}
}