dcrypt_sign/eddsa/ed25519/
mod.rs1#![forbid(unsafe_code)]
11
12use dcrypt_algorithms::hash::sha2::Sha512;
13use dcrypt_algorithms::hash::HashFunction;
14use dcrypt_api::{
15 error::Error as ApiError, Result as ApiResult, Signature as SignatureTrait, ZeroizingBytes,
16};
17use dcrypt_internal::{
18 try_fill_bytes_zeroing_on_error, zeroizing_bytes_from_slice, ConstantTimeEq, CryptoRng,
19 RngCore, Zeroize, ZeroizeOnDrop, Zeroizing,
20};
21
22use super::constants::{ED25519_PUBLIC_KEY_SIZE, ED25519_SECRET_KEY_SIZE, ED25519_SIGNATURE_SIZE};
23use super::point::EdwardsPoint;
24use super::scalar::Scalar;
25
26pub struct Ed25519;
28
29#[derive(Clone)]
31pub struct Ed25519PublicKey(pub [u8; ED25519_PUBLIC_KEY_SIZE]);
32
33impl core::fmt::Debug for Ed25519PublicKey {
34 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
35 f.debug_struct("Ed25519PublicKey")
36 .field("algorithm", &"Ed25519")
37 .finish()
38 }
39}
40
41#[derive(Clone)]
43pub struct Ed25519SecretKey {
44 seed: [u8; ED25519_SECRET_KEY_SIZE],
45}
46
47impl Zeroize for Ed25519SecretKey {
48 fn zeroize(&mut self) {
49 self.seed.zeroize();
50 }
51}
52
53impl Drop for Ed25519SecretKey {
54 fn drop(&mut self) {
55 self.zeroize();
56 }
57}
58
59impl ZeroizeOnDrop for Ed25519SecretKey {}
60
61impl core::fmt::Debug for Ed25519SecretKey {
62 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63 f.debug_struct("Ed25519SecretKey")
64 .field("algorithm", &"Ed25519")
65 .finish()
66 }
67}
68
69#[derive(Clone)]
71pub struct Ed25519Signature(pub [u8; ED25519_SIGNATURE_SIZE]);
72
73impl core::fmt::Debug for Ed25519Signature {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 f.debug_struct("Ed25519Signature")
76 .field("length", &self.0.len())
77 .finish()
78 }
79}
80
81fn invalid_key(context: &'static str, _message: &'static str) -> ApiError {
82 ApiError::InvalidKey {
83 context,
84 #[cfg(feature = "std")]
85 message: _message.to_string(),
86 }
87}
88
89fn invalid_signature(context: &'static str, _message: &'static str) -> ApiError {
90 ApiError::InvalidSignature {
91 context,
92 #[cfg(feature = "std")]
93 message: _message.to_string(),
94 }
95}
96
97fn decode_prime_order_point(bytes: &[u8; 32], context: &'static str) -> ApiResult<EdwardsPoint> {
98 let point = EdwardsPoint::decompress(bytes)
99 .ok_or_else(|| invalid_key(context, "point encoding is invalid or non-canonical"))?;
100 if !point.is_strict_prime_order() {
101 return Err(invalid_key(
102 context,
103 "point is identity, small-order, or not torsion-free",
104 ));
105 }
106 Ok(point)
107}
108
109fn hash_parts(parts: &[&[u8]]) -> ApiResult<Zeroizing<[u8; 64]>> {
110 let mut hasher = Sha512::new();
111 for part in parts {
112 hasher.update(part).map_err(ApiError::from)?;
113 }
114 let mut digest = hasher.finalize().map_err(ApiError::from)?;
115 let mut output = Zeroizing::new([0u8; 64]);
116 output.copy_from_slice(digest.as_ref());
117 digest.zeroize();
118 Ok(output)
119}
120
121impl Ed25519PublicKey {
122 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
124 let key_bytes: [u8; ED25519_PUBLIC_KEY_SIZE] =
125 bytes.try_into().map_err(|_| ApiError::InvalidLength {
126 context: "Ed25519PublicKey::from_bytes",
127 expected: ED25519_PUBLIC_KEY_SIZE,
128 actual: bytes.len(),
129 })?;
130 decode_prime_order_point(&key_bytes, "Ed25519PublicKey::from_bytes")?;
131 Ok(Self(key_bytes))
132 }
133
134 pub fn to_bytes(&self) -> [u8; ED25519_PUBLIC_KEY_SIZE] {
135 self.0
136 }
137
138 fn point(&self) -> ApiResult<EdwardsPoint> {
139 decode_prime_order_point(&self.0, "Ed25519 verify")
140 }
141}
142
143impl Ed25519SecretKey {
144 pub fn from_seed(seed: &[u8; ED25519_SECRET_KEY_SIZE]) -> ApiResult<Self> {
146 Ok(Self { seed: *seed })
147 }
148
149 pub fn seed(&self) -> &[u8; ED25519_SECRET_KEY_SIZE] {
150 &self.seed
151 }
152
153 pub fn export_seed(&self) -> ZeroizingBytes {
154 zeroizing_bytes_from_slice(&self.seed)
155 }
156
157 pub fn public_key(&self) -> ApiResult<Ed25519PublicKey> {
158 Ed25519::derive_public_from_secret(self)
159 }
160
161 fn expanded(&self) -> ApiResult<Zeroizing<[u8; 64]>> {
162 let mut expanded = hash_parts(&[&self.seed])?;
163 expanded[0] &= 248;
164 expanded[31] &= 127;
165 expanded[31] |= 64;
166 Ok(expanded)
167 }
168}
169
170impl Ed25519Signature {
171 pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
173 let signature: [u8; ED25519_SIGNATURE_SIZE] =
174 bytes.try_into().map_err(|_| ApiError::InvalidLength {
175 context: "Ed25519Signature::from_bytes",
176 expected: ED25519_SIGNATURE_SIZE,
177 actual: bytes.len(),
178 })?;
179 let r_bytes: [u8; 32] = signature[..32]
180 .try_into()
181 .map_err(|_| invalid_signature("Ed25519Signature::from_bytes", "invalid R length"))?;
182 decode_prime_order_point(&r_bytes, "Ed25519Signature::from_bytes")
183 .map_err(|_| invalid_signature("Ed25519Signature::from_bytes", "invalid R point"))?;
184 let s_bytes: [u8; 32] = signature[32..]
185 .try_into()
186 .map_err(|_| invalid_signature("Ed25519Signature::from_bytes", "invalid S length"))?;
187 Scalar::from_canonical_bytes(&s_bytes).ok_or_else(|| {
188 invalid_signature("Ed25519Signature::from_bytes", "S is not below group order")
189 })?;
190 Ok(Self(signature))
191 }
192
193 pub fn to_bytes(&self) -> [u8; ED25519_SIGNATURE_SIZE] {
194 self.0
195 }
196}
197
198impl SignatureTrait for Ed25519 {
199 type PublicKey = Ed25519PublicKey;
200 type SecretKey = Ed25519SecretKey;
201 type SignatureData = Ed25519Signature;
202 type KeyPair = (Self::PublicKey, Self::SecretKey);
203
204 fn name() -> &'static str {
205 "Ed25519"
206 }
207
208 fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
210 let mut seed = Zeroizing::new([0u8; ED25519_SECRET_KEY_SIZE]);
211 try_fill_bytes_zeroing_on_error(rng, &mut *seed).map_err(|_| {
212 ApiError::RandomGenerationError {
213 context: "Ed25519::keypair",
214 #[cfg(feature = "std")]
215 message: "caller-provided randomness source failed".into(),
216 }
217 })?;
218 let secret = Ed25519SecretKey::from_seed(&seed)?;
219 let public = secret.public_key()?;
220 Ok((public, secret))
221 }
222
223 fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
224 keypair.0.clone()
225 }
226
227 fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
228 keypair.1.clone()
229 }
230
231 fn sign(message: &[u8], secret_key: &Self::SecretKey) -> ApiResult<Self::SignatureData> {
232 let expanded = secret_key.expanded()?;
233 let mut secret_scalar_bytes = Zeroizing::new([0u8; 32]);
234 secret_scalar_bytes.copy_from_slice(&expanded[..32]);
235 let secret_scalar = Scalar::reduce_32(&secret_scalar_bytes);
236
237 let nonce_digest = hash_parts(&[&expanded[32..], message])?;
238 let nonce = Scalar::reduce_64(&nonce_digest);
239 let commitment = EdwardsPoint::basepoint().scalar_mult(&nonce);
240 if bool::from(commitment.is_identity()) {
241 return Err(invalid_signature(
242 "Ed25519 sign",
243 "derived commitment is the identity",
244 ));
245 }
246 let r_bytes = commitment.compress();
247 let public_bytes = EdwardsPoint::basepoint()
248 .scalar_mult(&secret_scalar)
249 .compress();
250
251 let challenge_digest = hash_parts(&[&r_bytes, &public_bytes, message])?;
252 let challenge = Scalar::reduce_64(&challenge_digest);
253 let response = nonce.add(&challenge.mul(&secret_scalar));
254
255 let mut signature = [0u8; ED25519_SIGNATURE_SIZE];
256 signature[..32].copy_from_slice(&r_bytes);
257 let response_bytes = response.to_bytes();
258 signature[32..].copy_from_slice(&response_bytes[..]);
260 Ok(Ed25519Signature(signature))
261 }
262
263 fn verify(
264 message: &[u8],
265 signature: &Self::SignatureData,
266 public_key: &Self::PublicKey,
267 ) -> ApiResult<()> {
268 let public_point = public_key.point()?;
271 let r_bytes: [u8; 32] = signature.0[..32]
272 .try_into()
273 .map_err(|_| invalid_signature("Ed25519 verify", "invalid R length"))?;
274 let r_point = decode_prime_order_point(&r_bytes, "Ed25519 signature R")
275 .map_err(|_| invalid_signature("Ed25519 verify", "invalid R point"))?;
276 let s_bytes: [u8; 32] = signature.0[32..]
277 .try_into()
278 .map_err(|_| invalid_signature("Ed25519 verify", "invalid S length"))?;
279 let response = Scalar::from_canonical_bytes(&s_bytes)
280 .ok_or_else(|| invalid_signature("Ed25519 verify", "S is not below group order"))?;
281
282 let challenge_digest = hash_parts(&[&r_bytes, &public_key.0, message])?;
283 let challenge = Scalar::reduce_64(&challenge_digest);
284 let left = EdwardsPoint::basepoint().scalar_mult(&response);
285 let right = r_point.add(&public_point.scalar_mult(&challenge));
286
287 if bool::from(left.ct_eq(&right)) {
288 Ok(())
289 } else {
290 Err(invalid_signature(
291 "Ed25519 verify",
292 "strict signature equation failed",
293 ))
294 }
295 }
296}
297
298impl Ed25519 {
299 pub fn derive_public_from_secret(secret_key: &Ed25519SecretKey) -> ApiResult<Ed25519PublicKey> {
300 let expanded = secret_key.expanded()?;
301 let mut scalar_bytes = Zeroizing::new([0u8; 32]);
302 scalar_bytes.copy_from_slice(&expanded[..32]);
303 let scalar = Scalar::reduce_32(&scalar_bytes);
304 let bytes = EdwardsPoint::basepoint().scalar_mult(&scalar).compress();
305 Ed25519PublicKey::from_bytes(&bytes)
306 }
307}
308
309#[cfg(test)]
310mod tests;