use cesride::{Diger, Matter, Salter, Verfer, matter};
use crate::keys::KeriDecodeError;
pub fn encode_salt_128(raw: &[u8; 16]) -> Result<String, KeriDecodeError> {
Salter::new_with_raw(raw, Some(matter::Codex::Salt_128), None)
.and_then(|s| s.qb64())
.map_err(|e| KeriDecodeError::DecodeError(e.to_string()))
}
pub(crate) fn encode_verkey(raw: &[u8], code: &str) -> Result<String, KeriDecodeError> {
Verfer::new(Some(code), Some(raw), None, None, None)
.and_then(|v| v.qb64())
.map_err(|e| KeriDecodeError::DecodeError(e.to_string()))
}
pub(crate) fn decode_verkey(qb64: &str) -> Result<(Vec<u8>, String), KeriDecodeError> {
Verfer::new(None, None, None, Some(qb64), None)
.map(|v| (v.raw(), v.code()))
.map_err(|e| KeriDecodeError::DecodeError(e.to_string()))
}
fn matter_hard_len(first: u8) -> Option<usize> {
Some(match first {
b'A'..=b'Z' | b'a'..=b'z' => 1,
b'0' | b'4' | b'5' | b'6' => 2,
b'1' | b'2' | b'3' | b'7' | b'8' | b'9' => 4,
_ => return None,
})
}
fn matter_full_len(code: &str) -> Option<usize> {
Some(match code {
"D" | "B" | "E" => 44,
"0B" | "0I" => 88,
"1AAI" | "1AAJ" => 48,
_ => return None,
})
}
pub(crate) fn take_matter_qb64(s: &str) -> Result<&str, KeriDecodeError> {
let bytes = s.as_bytes();
let first = *bytes.first().ok_or(KeriDecodeError::EmptyInput)?;
let hs = matter_hard_len(first).ok_or_else(|| {
KeriDecodeError::DecodeError(format!("not a CESR primitive: {:?}", first as char))
})?;
if bytes.len() < hs || !bytes[..hs].is_ascii() {
return Err(KeriDecodeError::DecodeError(
"truncated or non-ASCII CESR hard code".into(),
));
}
let code = &s[..hs];
let fs =
matter_full_len(code).ok_or_else(|| KeriDecodeError::UnsupportedKeyType(code.into()))?;
if bytes.len() < fs || !bytes[..fs].is_ascii() {
return Err(KeriDecodeError::DecodeError(format!(
"{code} primitive needs {fs} ASCII chars, have {}",
bytes.len()
)));
}
Ok(&s[..fs])
}
pub(crate) fn encode_blake3_digest(digest: &[u8]) -> Result<String, KeriDecodeError> {
Diger::new(
None,
Some(matter::Codex::Blake3_256),
Some(digest),
None,
None,
None,
)
.and_then(|d| d.qb64())
.map_err(|e| KeriDecodeError::DecodeError(e.to_string()))
}
pub(crate) fn verkey_code(curve: auths_crypto::CurveType, transferable: bool) -> &'static str {
match (curve, transferable) {
(auths_crypto::CurveType::Ed25519, true) => matter::Codex::Ed25519,
(auths_crypto::CurveType::Ed25519, false) => matter::Codex::Ed25519N,
(auths_crypto::CurveType::P256, true) => matter::Codex::ECDSA_256r1,
(auths_crypto::CurveType::P256, false) => matter::Codex::ECDSA_256r1N,
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn encode_decode_matches_keripy() {
let raw: Vec<u8> = (0u8..32).collect();
let qb64 = encode_verkey(&raw, matter::Codex::Ed25519).unwrap();
assert_eq!(qb64, "DAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4f");
let (decoded, code) = decode_verkey(&qb64).unwrap();
assert_eq!(decoded, raw, "round-trip must recover the raw bytes");
assert_eq!(code, matter::Codex::Ed25519);
let commitment = encode_blake3_digest(blake3::hash(qb64.as_bytes()).as_bytes()).unwrap();
assert_eq!(commitment, "EF_M_u7ASVHXfI8QzdWLq3V9ocSKqxkbujXGbi9QMtP9");
}
#[test]
fn matter_full_len_matches_cesride_encoding() {
use cesride::{Cigar, Matter};
let cases: [(&str, String); 7] = [
(
"D",
encode_verkey(&[0u8; 32], matter::Codex::Ed25519).unwrap(),
),
(
"B",
encode_verkey(&[0u8; 32], matter::Codex::Ed25519N).unwrap(),
),
(
"1AAJ",
encode_verkey(&[0u8; 33], matter::Codex::ECDSA_256r1).unwrap(),
),
(
"1AAI",
encode_verkey(&[0u8; 33], matter::Codex::ECDSA_256r1N).unwrap(),
),
("E", encode_blake3_digest(&[0u8; 32]).unwrap()),
(
"0B",
Cigar::new_with_raw(&[0u8; 64], None, Some(matter::Codex::Ed25519_Sig))
.and_then(|c| c.qb64())
.unwrap(),
),
(
"0I",
Cigar::new_with_raw(&[0u8; 64], None, Some(matter::Codex::ECDSA_256r1_Sig))
.and_then(|c| c.qb64())
.unwrap(),
),
];
for (code, encoded) in cases {
assert!(
encoded.starts_with(code),
"{code} primitive must encode under its own code, got {encoded:?}"
);
assert_eq!(
matter_full_len(code),
Some(encoded.len()),
"matter_full_len({code:?}) drifted from cesride's {} chars",
encoded.len()
);
}
}
}