1use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
12use ed25519_dalek::{Signature, Signer as _, SigningKey, Verifier as _, VerifyingKey};
13use sha2::{Digest, Sha256};
14
15#[derive(Debug, thiserror::Error, PartialEq, Eq)]
16pub enum CryptoError {
17 #[error("invalid ed25519 private key: expected 32 raw bytes, got {0}")]
18 PrivateKeyLength(usize),
19 #[error("invalid ed25519 public key: expected 32 raw bytes, got {0}")]
20 PublicKeyLength(usize),
21 #[error("invalid ed25519 signature: expected 64 raw bytes, got {0}")]
22 SignatureLength(usize),
23 #[error("signature verification failed")]
24 BadSignature,
25 #[error("invalid public key encoding")]
26 BadPublicKey,
27 #[error("invalid base64: {0}")]
28 BadBase64(String),
29 #[error("unknown algorithm: {0}")]
30 UnknownAlgorithm(String),
31 #[error("{0}")]
32 Generic(String),
33}
34
35pub struct Ed25519Signer {
37 inner: SigningKey,
38}
39
40impl Ed25519Signer {
41 pub fn from_bytes(seed: &[u8; 32]) -> Self {
42 Ed25519Signer {
43 inner: SigningKey::from_bytes(seed),
44 }
45 }
46
47 pub fn generate<R: rand::RngCore + rand::CryptoRng>(rng: &mut R) -> Self {
48 Ed25519Signer {
49 inner: SigningKey::generate(rng),
50 }
51 }
52
53 pub fn public_key_bytes(&self) -> [u8; 32] {
54 self.inner.verifying_key().to_bytes()
55 }
56
57 pub fn sign(&self, msg: &[u8]) -> [u8; 64] {
58 self.inner.sign(msg).to_bytes()
59 }
60}
61
62pub fn ed25519_verify(public_key: &[u8], msg: &[u8], signature: &[u8]) -> Result<(), CryptoError> {
64 let pk_bytes: &[u8; 32] = public_key
65 .try_into()
66 .map_err(|_| CryptoError::PublicKeyLength(public_key.len()))?;
67 let sig_bytes: &[u8; 64] = signature
68 .try_into()
69 .map_err(|_| CryptoError::SignatureLength(signature.len()))?;
70 let vk = VerifyingKey::from_bytes(pk_bytes).map_err(|_| CryptoError::BadPublicKey)?;
71 let sig = Signature::from_bytes(sig_bytes);
72 vk.verify(msg, &sig).map_err(|_| CryptoError::BadSignature)
73}
74
75pub fn sha256_hashref(bytes: &[u8]) -> String {
77 let digest = Sha256::digest(bytes);
78 format!("sha256:{}", hex(&digest))
79}
80
81pub fn blake3_hashref(bytes: &[u8]) -> String {
83 let digest = blake3::hash(bytes);
84 format!("blake3:{}", hex(digest.as_bytes()))
85}
86
87pub fn hex(bytes: &[u8]) -> String {
88 let mut s = String::with_capacity(bytes.len() * 2);
89 for b in bytes {
90 s.push_str(&format!("{:02x}", b));
91 }
92 s
93}
94
95pub fn parse_hashref(s: &str) -> Result<(String, Vec<u8>), CryptoError> {
97 let (algo, hex_part) = s
98 .split_once(':')
99 .ok_or_else(|| CryptoError::UnknownAlgorithm(s.to_owned()))?;
100 let mut out = Vec::with_capacity(hex_part.len() / 2);
101 let bytes = hex_part.as_bytes();
102 let mut i = 0;
103 while i < bytes.len() {
104 if i + 1 >= bytes.len() {
105 return Err(CryptoError::BadBase64("odd hex length".into()));
106 }
107 let hi = from_hex(bytes[i]).ok_or_else(|| CryptoError::BadBase64("non-hex char".into()))?;
108 let lo =
109 from_hex(bytes[i + 1]).ok_or_else(|| CryptoError::BadBase64("non-hex char".into()))?;
110 out.push((hi << 4) | lo);
111 i += 2;
112 }
113 Ok((algo.to_owned(), out))
114}
115
116fn from_hex(b: u8) -> Option<u8> {
117 match b {
118 b'0'..=b'9' => Some(b - b'0'),
119 b'a'..=b'f' => Some(b - b'a' + 10),
120 b'A'..=b'F' => Some(b - b'A' + 10),
121 _ => None,
122 }
123}
124
125pub fn b64encode(bytes: &[u8]) -> String {
127 B64.encode(bytes)
128}
129
130pub fn b64decode(s: &str) -> Result<Vec<u8>, CryptoError> {
131 B64.decode(s.as_bytes())
132 .map_err(|e| CryptoError::BadBase64(e.to_string()))
133}
134
135pub struct X25519KeyPair {
138 pub private: [u8; 32],
139 pub public: [u8; 32],
140}
141
142pub fn x25519_generate<R: rand::RngCore + rand::CryptoRng>(rng: &mut R) -> X25519KeyPair {
143 let secret = x25519_dalek::StaticSecret::random_from_rng(rng);
144 let public = x25519_dalek::PublicKey::from(&secret);
145 X25519KeyPair {
146 private: secret.to_bytes(),
147 public: public.to_bytes(),
148 }
149}
150
151pub fn x25519_from_bytes(seed: &[u8; 32]) -> X25519KeyPair {
152 let secret = x25519_dalek::StaticSecret::from(*seed);
153 let public = x25519_dalek::PublicKey::from(&secret);
154 X25519KeyPair {
155 private: secret.to_bytes(),
156 public: public.to_bytes(),
157 }
158}
159
160pub fn x25519_diffie_hellman(private: &[u8; 32], peer_public: &[u8; 32]) -> [u8; 32] {
161 let secret = x25519_dalek::StaticSecret::from(*private);
162 let peer = x25519_dalek::PublicKey::from(*peer_public);
163 secret.diffie_hellman(&peer).to_bytes()
164}
165
166pub fn hkdf_sha256(input_key: &[u8], salt: &[u8], info: &[u8], output_len: usize) -> Vec<u8> {
169 let hk = hkdf::Hkdf::<Sha256>::new(Some(salt), input_key);
170 let mut out = vec![0u8; output_len];
171 hk.expand(info, &mut out).expect("output_len <= 255*32");
172 out
173}
174
175#[derive(Debug, thiserror::Error, PartialEq, Eq)]
178pub enum AeadError {
179 #[error("aead key must be 32 bytes")]
180 BadKey,
181 #[error("aead nonce must be 12 bytes")]
182 BadNonce,
183 #[error("aead authentication failed")]
184 AuthFailed,
185}
186
187pub fn chacha20poly1305_encrypt(
188 key: &[u8; 32],
189 nonce: &[u8; 12],
190 aad: &[u8],
191 plaintext: &[u8],
192) -> Vec<u8> {
193 use chacha20poly1305::aead::{Aead, KeyInit, Payload};
194 use chacha20poly1305::ChaCha20Poly1305;
195 let cipher = ChaCha20Poly1305::new_from_slice(key).expect("32-byte key");
196 cipher
197 .encrypt(
198 chacha20poly1305::Nonce::from_slice(nonce),
199 Payload {
200 msg: plaintext,
201 aad,
202 },
203 )
204 .expect("encrypt")
205}
206
207pub fn chacha20poly1305_decrypt(
208 key: &[u8; 32],
209 nonce: &[u8; 12],
210 aad: &[u8],
211 ciphertext: &[u8],
212) -> Result<Vec<u8>, AeadError> {
213 use chacha20poly1305::aead::{Aead, KeyInit, Payload};
214 use chacha20poly1305::ChaCha20Poly1305;
215 let cipher = ChaCha20Poly1305::new_from_slice(key).map_err(|_| AeadError::BadKey)?;
216 cipher
217 .decrypt(
218 chacha20poly1305::Nonce::from_slice(nonce),
219 Payload {
220 msg: ciphertext,
221 aad,
222 },
223 )
224 .map_err(|_| AeadError::AuthFailed)
225}