1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3use base64::engine::general_purpose::URL_SAFE_NO_PAD as B64;
6use base64::Engine;
7use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
8use hmac::{Hmac, Mac};
9use rand_core::OsRng;
10use sha2::Sha256;
11use thiserror::Error;
12use zeroize::{Zeroize, ZeroizeOnDrop};
13
14#[derive(Debug, Error)]
16pub enum AtomicCryptoError {
17 #[error("base64 decode: {0}")]
19 Base64(#[from] base64::DecodeError),
20 #[error("invalid signature")]
22 InvalidSig,
23 #[error("hmac verify failed")]
25 HmacVerify,
26 #[error("bad did:key material")]
28 BadDidKey,
29}
30
31#[derive(Zeroize, ZeroizeOnDrop, Clone)]
33pub struct SecretKey(pub [u8; 32]);
34
35impl SecretKey {
36 #[must_use]
38 pub fn verifying_key(&self) -> VerifyingKey {
39 SigningKey::from_bytes(&self.0).verifying_key()
40 }
41}
42
43pub struct Keypair {
45 pub sk: SecretKey,
47 pub vk: VerifyingKey,
49}
50impl Keypair {
51 #[must_use]
53 pub fn generate() -> Self {
54 let sk = SigningKey::generate(&mut OsRng);
55 Self {
56 sk: SecretKey(sk.to_bytes()),
57 vk: sk.verifying_key(),
58 }
59 }
60 #[must_use]
62 pub fn signing_key(&self) -> SigningKey {
63 SigningKey::from_bytes(&self.sk.0)
64 }
65}
66
67#[must_use]
69pub fn b64_encode(b: &[u8]) -> String {
70 B64.encode(b)
71}
72pub fn b64_decode(s: &str) -> Result<Vec<u8>, AtomicCryptoError> {
78 Ok(B64.decode(s)?)
79}
80#[must_use]
82pub fn blake3_hex(data: &[u8]) -> String {
83 blake3::hash(data).to_hex().to_string()
84}
85
86#[must_use]
88pub fn key_id_v1(vk: &VerifyingKey) -> String {
89 let h = blake3::hash(vk.as_bytes());
90 format!("kid:v1:{}", B64.encode(h.as_bytes()))
91}
92#[must_use]
94pub fn key_id_v2(vk: &VerifyingKey) -> String {
95 let h = blake3::hash(vk.as_bytes());
96 format!("kid:v2:0001:{}", B64.encode(h.as_bytes()))
97}
98
99#[must_use]
101pub fn sign_cid_hex(sk: &SecretKey, cid_hex: &str) -> [u8; 64] {
102 let sig: Signature = SigningKey::from_bytes(&sk.0).sign(cid_hex.as_bytes());
103 sig.to_bytes()
104}
105#[must_use]
107pub fn verify_cid_hex(vk: &VerifyingKey, cid_hex: &str, sig: &[u8]) -> bool {
108 Signature::from_slice(sig).is_ok_and(|parsed| vk.verify(cid_hex.as_bytes(), &parsed).is_ok())
109}
110
111#[must_use]
117pub fn hmac_sign(key: &[u8], data: &[u8]) -> String {
118 let mut mac: Hmac<Sha256> = Hmac::new_from_slice(key).expect("HMAC key");
119 mac.update(data);
120 let out = mac.finalize().into_bytes();
121 b64_encode(&out)
122}
123pub fn hmac_verify(key: &[u8], data: &[u8], tag_b64: &str) -> Result<(), AtomicCryptoError> {
133 let want = b64_decode(tag_b64)?;
134 let mut mac: Hmac<Sha256> = Hmac::new_from_slice(key).expect("HMAC key");
135 mac.update(data);
136 mac.verify_slice(&want)
137 .map_err(|_| AtomicCryptoError::HmacVerify)
138}
139
140#[must_use]
142pub fn did_key_encode_ed25519(vk: &VerifyingKey) -> String {
143 let mut data = vec![0xED, 0x01];
145 data.extend_from_slice(vk.as_bytes());
146 let mut out = String::from("did:key:z");
147 out.push_str(&bs58::encode(data).into_string());
149 out
150}
151pub fn did_key_decode_ed25519(did: &str) -> Result<VerifyingKey, AtomicCryptoError> {
157 let p = did
158 .strip_prefix("did:key:z")
159 .ok_or(AtomicCryptoError::BadDidKey)?;
160 let bytes = bs58::decode(p)
161 .into_vec()
162 .map_err(|_| AtomicCryptoError::BadDidKey)?;
163 if bytes.len() != 34 || bytes[0] != 0xED || bytes[1] != 0x01 {
164 return Err(AtomicCryptoError::BadDidKey);
165 }
166 let pk: [u8; 32] = bytes[2..]
167 .try_into()
168 .map_err(|_| AtomicCryptoError::BadDidKey)?;
169 VerifyingKey::from_bytes(&pk).map_err(|_| AtomicCryptoError::BadDidKey)
170}
171
172#[must_use]
174pub fn verify_many(vk: &VerifyingKey, items: &[(&str, &str)]) -> usize {
175 items
176 .iter()
177 .filter(|(cid, sig_b64)| b64_decode(sig_b64).is_ok_and(|sig| verify_cid_hex(vk, cid, &sig)))
178 .count()
179}
180
181use ubl_types::{Cid32, PublicKeyBytes, SignatureBytes};
186
187#[must_use]
191#[inline]
192pub fn blake3_cid(data: &[u8]) -> Cid32 {
193 let hash = blake3::hash(data);
194 let mut bytes = [0u8; 32];
195 bytes.copy_from_slice(hash.as_bytes());
196 Cid32(bytes)
197}
198
199#[must_use]
203#[inline]
204pub fn blake3_cid_chunks<'a, I>(chunks: I) -> Cid32
205where
206 I: IntoIterator<Item = &'a [u8]>,
207{
208 let mut hasher = blake3::Hasher::new();
209 for c in chunks {
210 hasher.update(c);
211 }
212 let out = hasher.finalize();
213 let mut bytes = [0u8; 32];
214 bytes.copy_from_slice(out.as_bytes());
215 Cid32(bytes)
216}
217
218#[must_use]
220#[inline]
221pub fn derive_public_bytes(secret_key: &[u8; 32]) -> PublicKeyBytes {
222 let sk = SigningKey::from_bytes(secret_key);
223 PublicKeyBytes(sk.verifying_key().to_bytes())
224}
225
226#[must_use]
228#[inline]
229pub fn sign_bytes(msg: &[u8], secret_key: &[u8; 32]) -> SignatureBytes {
230 let sk = SigningKey::from_bytes(secret_key);
231 let sig: Signature = sk.sign(msg);
232 SignatureBytes(sig.to_bytes())
233}
234
235#[must_use]
239#[inline]
240pub fn verify_bytes(msg: &[u8], pk: &PublicKeyBytes, sig: &SignatureBytes) -> bool {
241 match VerifyingKey::from_bytes(&pk.0) {
242 Ok(vk) => {
243 let s = Signature::from_bytes(&sig.0);
244 vk.verify_strict(msg, &s).is_ok()
245 }
246 Err(_) => false,
247 }
248}
249
250#[cfg(test)]
251mod atomic_types_tests {
252 use super::*;
253
254 #[test]
255 fn blake3_cid_deterministic() {
256 let a = blake3_cid(b"hello");
257 let b = blake3_cid(b"hello");
258 assert_eq!(a.0, b.0);
259
260 let c = blake3_cid_chunks([b"hel".as_slice(), b"lo".as_slice()]);
261 assert_eq!(a.0, c.0);
262 }
263
264 #[test]
265 fn ed25519_atomic_types_roundtrip() {
266 let sk = [7u8; 32]; let pk = derive_public_bytes(&sk);
268 let msg = b"deterministic message";
269 let sig = sign_bytes(msg, &sk);
270
271 assert!(verify_bytes(msg, &pk, &sig));
272 assert!(!verify_bytes(b"wrong", &pk, &sig));
273 }
274
275 #[test]
276 fn cid_serializes_to_hex() {
277 let cid = blake3_cid(b"test");
278 let json = serde_json::to_string(&cid).unwrap();
279 assert_eq!(json.len(), 66);
281 }
282}