1use base64::Engine as _;
11use sha2::{Digest, Sha256};
12
13pub fn thumbprint_ed25519(x_b64: &str, crv: &str) -> String {
24 let crv = if crv.is_empty() { "Ed25519" } else { crv };
25 let canonical = format!(r#"{{"crv":"{}","kty":"OKP","x":"{}"}}"#, crv, x_b64);
26 let hash = Sha256::digest(canonical.as_bytes());
27 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(hash)
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 #[test]
35 fn deterministic() {
36 let x1 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode([1u8; 32]);
37 let x2 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode([2u8; 32]);
38 assert_ne!(
39 thumbprint_ed25519(&x1, "Ed25519"),
40 thumbprint_ed25519(&x2, "Ed25519")
41 );
42 assert_eq!(
43 thumbprint_ed25519(&x1, "Ed25519"),
44 thumbprint_ed25519(&x1, "Ed25519")
45 );
46 }
47
48 #[test]
49 fn empty_crv_defaults_to_ed25519() {
50 let x = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode([1u8; 32]);
51 assert_eq!(
52 thumbprint_ed25519(&x, ""),
53 thumbprint_ed25519(&x, "Ed25519"),
54 );
55 }
56}