dryoc/classic/
crypto_sign_ed25519.rs

1//! # Ed25519 to Curve25519 conversion
2//!
3//! This module implements libsodium's Ed25519 to Curve25519 conversion
4//! functions. You can use these functions when you want to sign messages with
5//! the same keys used to encrypt messages (i.e., using a public-key box).
6//!
7//! Generally speaking, you should avoid signing and encrypting with the same
8//! keypair. Additionally, an encrypted box doesn't need to be separately signed
9//! as it already includes a message authentication code.
10
11use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
12use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint};
13use curve25519_dalek::scalar::Scalar;
14use zeroize::Zeroize;
15
16use crate::constants::{
17    CRYPTO_HASH_SHA512_BYTES, CRYPTO_SCALARMULT_CURVE25519_BYTES,
18    CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES, CRYPTO_SIGN_ED25519_BYTES,
19    CRYPTO_SIGN_ED25519_PUBLICKEYBYTES, CRYPTO_SIGN_ED25519_SECRETKEYBYTES,
20    CRYPTO_SIGN_ED25519_SEEDBYTES,
21};
22use crate::error::Error;
23use crate::sha512::Sha512;
24
25/// Type alias for an Ed25519 public key.
26pub type PublicKey = [u8; CRYPTO_SIGN_ED25519_PUBLICKEYBYTES];
27/// Type alias for an Ed25519 secret key with seed bytes.
28pub type SecretKey = [u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
29/// Type alias for an Ed25519 signature.
30pub type Signature = [u8; CRYPTO_SIGN_ED25519_BYTES];
31
32const DOM2PREFIX: &[u8] = b"SigEd25519 no Ed25519 collisions\x01\x00";
33
34/// In-place variant of [`crypto_sign_ed25519_seed_keypair`].
35#[inline]
36pub(crate) fn crypto_sign_ed25519_seed_keypair_inplace(
37    public_key: &mut PublicKey,
38    secret_key: &mut SecretKey,
39    seed: &[u8; CRYPTO_SIGN_ED25519_SEEDBYTES],
40) {
41    let hash: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(seed);
42
43    let mut sk = Scalar::from_bytes_mod_order(clamp_hash(hash));
44
45    let pk = (ED25519_BASEPOINT_TABLE * &sk).compress();
46    secret_key[..CRYPTO_SIGN_ED25519_SEEDBYTES].copy_from_slice(seed);
47    secret_key[CRYPTO_SIGN_ED25519_SEEDBYTES..].copy_from_slice(pk.as_bytes());
48
49    public_key.copy_from_slice(pk.as_bytes());
50
51    sk.zeroize();
52}
53
54/// Generates an Ed25519 keypair from `seed` which can be used for signing
55/// messages.
56pub(crate) fn crypto_sign_ed25519_seed_keypair(
57    seed: &[u8; CRYPTO_SIGN_ED25519_SEEDBYTES],
58) -> (PublicKey, SecretKey) {
59    let mut public_key = PublicKey::default();
60    let mut secret_key = [0u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
61
62    crypto_sign_ed25519_seed_keypair_inplace(&mut public_key, &mut secret_key, seed);
63
64    (public_key, secret_key)
65}
66
67/// In-place variant of [`crypto_sign_ed25519_keypair`].
68#[inline]
69pub(crate) fn crypto_sign_ed25519_keypair_inplace(
70    public_key: &mut PublicKey,
71    secret_key: &mut SecretKey,
72) {
73    use crate::rng::copy_randombytes;
74    let mut seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
75    copy_randombytes(&mut seed);
76    crypto_sign_ed25519_seed_keypair_inplace(public_key, secret_key, &seed);
77}
78
79/// Generates a random Ed25519 keypair which can be used for signing
80/// messages.
81pub(crate) fn crypto_sign_ed25519_keypair() -> (PublicKey, SecretKey) {
82    let mut public_key = PublicKey::default();
83    let mut secret_key = [0u8; CRYPTO_SIGN_ED25519_SECRETKEYBYTES];
84    crypto_sign_ed25519_keypair_inplace(&mut public_key, &mut secret_key);
85
86    (public_key, secret_key)
87}
88
89fn clamp_hash(
90    mut hash: [u8; CRYPTO_HASH_SHA512_BYTES],
91) -> [u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES] {
92    let mut scalar = [0u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES];
93    scalar.copy_from_slice(&hash[..CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES]);
94    hash.zeroize();
95    scalar[0] &= 248;
96    scalar[31] &= 127;
97    scalar[31] |= 64;
98    scalar
99}
100
101/// Converts an Ed25519 public key `ed25519_public_key` into an X25519 public
102/// key, placing the result into `x25519_public_key` upon success.
103///
104/// Compatible with libsodium's `crypto_sign_ed25519_pk_to_curve25519`
105pub fn crypto_sign_ed25519_pk_to_curve25519(
106    x25519_public_key: &mut [u8; CRYPTO_SCALARMULT_CURVE25519_BYTES],
107    ed25519_public_key: &PublicKey,
108) -> Result<(), Error> {
109    let ep = CompressedEdwardsY(*ed25519_public_key)
110        .decompress()
111        .ok_or_else(|| dryoc_error!("failed to convert to Edwards point"))?;
112    x25519_public_key.copy_from_slice(ep.to_montgomery().as_bytes());
113
114    Ok(())
115}
116
117/// Converts an Ed25519 secret key `ed25519_secret_key` into an X25519 secret
118/// key key, placing the result into `x25519_secret_key`.
119///
120/// Compatible with libsodium's `crypto_sign_ed25519_sk_to_curve25519`
121pub fn crypto_sign_ed25519_sk_to_curve25519(
122    x25519_secret_key: &mut [u8; CRYPTO_SCALARMULT_CURVE25519_BYTES],
123    ed25519_secret_key: &SecretKey,
124) {
125    let hash: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(&ed25519_secret_key[..32]);
126    let mut scalar = clamp_hash(hash);
127    x25519_secret_key.copy_from_slice(&scalar);
128    scalar.zeroize()
129}
130
131pub(crate) fn crypto_sign_ed25519(
132    signed_message: &mut [u8],
133    message: &[u8],
134    secret_key: &SecretKey,
135) -> Result<(), Error> {
136    if signed_message.len() != message.len() + CRYPTO_SIGN_ED25519_BYTES {
137        Err(dryoc_error!(format!(
138            "signed_message length incorrect (expect {}, got {})",
139            message.len() + CRYPTO_SIGN_ED25519_BYTES,
140            signed_message.len()
141        )))
142    } else {
143        let (sig, sm) = signed_message.split_at_mut(CRYPTO_SIGN_ED25519_BYTES);
144        let sig: &mut [u8; CRYPTO_SIGN_ED25519_BYTES] =
145            <&mut [u8; CRYPTO_SIGN_ED25519_BYTES]>::try_from(sig).unwrap();
146        sm.copy_from_slice(message);
147        crypto_sign_ed25519_detached(sig, message, secret_key)
148    }
149}
150
151pub(crate) fn crypto_sign_ed25519_detached(
152    signature: &mut Signature,
153    message: &[u8],
154    secret_key: &SecretKey,
155) -> Result<(), Error> {
156    crypto_sign_ed25519_detached_impl(signature, message, secret_key, false)
157}
158
159#[inline]
160fn crypto_sign_ed25519_detached_impl(
161    signature: &mut Signature,
162    message: &[u8],
163    secret_key: &SecretKey,
164    prehashed: bool,
165) -> Result<(), Error> {
166    if signature.len() != CRYPTO_SIGN_ED25519_BYTES {
167        Err(dryoc_error!(format!(
168            "signature length incorrect (expect {}, got {})",
169            CRYPTO_SIGN_ED25519_BYTES,
170            signature.len()
171        )))
172    } else {
173        let mut az: [u8; CRYPTO_HASH_SHA512_BYTES] = Sha512::compute(&secret_key[..32]);
174
175        let mut hasher = Sha512::new();
176        if prehashed {
177            hasher.update(DOM2PREFIX);
178        }
179        hasher.update(&az[32..]);
180        hasher.update(message);
181        let mut nonce: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
182
183        signature[32..].copy_from_slice(&secret_key[32..]);
184
185        let r = Scalar::from_bytes_mod_order_wide(&nonce);
186        let big_r = (ED25519_BASEPOINT_TABLE * &r).compress();
187
188        signature[..32].copy_from_slice(big_r.as_bytes());
189
190        let mut hasher = Sha512::new();
191        if prehashed {
192            hasher.update(DOM2PREFIX);
193        }
194        hasher.update(signature);
195        hasher.update(message);
196        let hram: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
197
198        let k = Scalar::from_bytes_mod_order_wide(&hram);
199        let clamped = clamp_hash(az);
200        let sig = (k * Scalar::from_bytes_mod_order(clamped)) + r;
201
202        signature[32..].copy_from_slice(sig.as_bytes());
203
204        az.zeroize();
205        nonce.zeroize();
206
207        Ok(())
208    }
209}
210
211pub(crate) fn crypto_sign_ed25519_verify_detached(
212    signature: &Signature,
213    message: &[u8],
214    public_key: &PublicKey,
215) -> Result<(), Error> {
216    crypto_sign_ed25519_verify_detached_impl(signature, message, public_key, false)
217}
218
219fn crypto_sign_ed25519_verify_detached_impl(
220    signature: &Signature,
221    message: &[u8],
222    public_key: &PublicKey,
223    prehashed: bool,
224) -> Result<(), Error> {
225    let s = Scalar::from_bytes_mod_order(
226        *<&[u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES]>::try_from(&signature[32..])
227            .map_err(|_| dryoc_error!("bad signature"))?,
228    );
229    let big_r = CompressedEdwardsY::from_slice(&signature[..32])?
230        .decompress()
231        .ok_or_else(|| dryoc_error!("bad signature"))?;
232    if big_r.is_small_order() {
233        return Err(dryoc_error!("bad signature"));
234    }
235    let pk = CompressedEdwardsY::from_slice(public_key)?
236        .decompress()
237        .ok_or_else(|| dryoc_error!("bad public key"))?;
238    if pk.is_small_order() {
239        return Err(dryoc_error!("bad public key"));
240    }
241
242    let mut hasher = Sha512::new();
243    if prehashed {
244        hasher.update(DOM2PREFIX);
245    }
246    hasher.update(&signature[..32]);
247    hasher.update(public_key);
248    hasher.update(message);
249    let h: [u8; CRYPTO_HASH_SHA512_BYTES] = hasher.finalize();
250
251    let k = Scalar::from_bytes_mod_order_wide(&h);
252
253    let sig_r = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(-pk), &s);
254
255    if sig_r == big_r {
256        Ok(())
257    } else {
258        Err(dryoc_error!("bad signature"))
259    }
260}
261
262pub(crate) fn crypto_sign_ed25519_open(
263    message: &mut [u8],
264    signed_message: &[u8],
265    public_key: &PublicKey,
266) -> Result<(), Error> {
267    if signed_message.len() < CRYPTO_SIGN_ED25519_BYTES {
268        Err(dryoc_error!(format!(
269            "signed_message length invalid ({} < {})",
270            signed_message.len(),
271            CRYPTO_SIGN_ED25519_BYTES,
272        )))
273    } else if message.len() != signed_message.len() - CRYPTO_SIGN_ED25519_BYTES {
274        Err(dryoc_error!(format!(
275            "message length incorrect (expect {}, got {})",
276            signed_message.len() - CRYPTO_SIGN_ED25519_BYTES,
277            message.len()
278        )))
279    } else {
280        let (sig, sm) = signed_message.split_at(CRYPTO_SIGN_ED25519_BYTES);
281        let sig: &[u8; CRYPTO_SIGN_ED25519_BYTES] =
282            <&[u8; CRYPTO_SIGN_ED25519_BYTES]>::try_from(sig).unwrap();
283        crypto_sign_ed25519_verify_detached(sig, sm, public_key)?;
284        message.copy_from_slice(sm);
285        Ok(())
286    }
287}
288
289pub(crate) struct Ed25519SignerState {
290    hasher: Sha512,
291}
292
293pub(crate) fn crypto_sign_ed25519ph_init() -> Ed25519SignerState {
294    Ed25519SignerState {
295        hasher: Sha512::new(),
296    }
297}
298
299pub(crate) fn crypto_sign_ed25519ph_update(state: &mut Ed25519SignerState, message: &[u8]) {
300    state.hasher.update(message)
301}
302
303pub(crate) fn crypto_sign_ed25519ph_final_create(
304    state: Ed25519SignerState,
305    signature: &mut Signature,
306    secret_key: &SecretKey,
307) -> Result<(), Error> {
308    let mut hash: [u8; CRYPTO_HASH_SHA512_BYTES] = state.hasher.finalize();
309    let res = crypto_sign_ed25519_detached_impl(signature, &hash, secret_key, true);
310    hash.zeroize();
311    res
312}
313
314pub(crate) fn crypto_sign_ed25519ph_final_verify(
315    state: Ed25519SignerState,
316    signature: &Signature,
317    public_key: &PublicKey,
318) -> Result<(), Error> {
319    let mut hash: [u8; CRYPTO_HASH_SHA512_BYTES] = state.hasher.finalize();
320    let res = crypto_sign_ed25519_verify_detached_impl(signature, &hash, public_key, true);
321    hash.zeroize();
322    res
323}
324
325#[cfg(test)]
326mod tests {
327    use base64::Engine as _;
328    use base64::engine::general_purpose;
329
330    use super::*;
331    use crate::rng::copy_randombytes;
332
333    #[test]
334    fn test_keypair_seed() {
335        use sodiumoxide::crypto::sign;
336
337        for _ in 0..10 {
338            let mut seed = [0u8; CRYPTO_SIGN_ED25519_SEEDBYTES];
339            copy_randombytes(&mut seed);
340
341            let (pk, sk) = crypto_sign_ed25519_seed_keypair(&seed);
342
343            let (so_pk, so_sk) =
344                sign::keypair_from_seed(&sign::Seed::from_slice(&seed).expect("seed failed"));
345
346            assert_eq!(
347                general_purpose::STANDARD.encode(pk),
348                general_purpose::STANDARD.encode(so_pk.0)
349            );
350            assert_eq!(
351                general_purpose::STANDARD.encode(sk),
352                general_purpose::STANDARD.encode(so_sk.0)
353            );
354        }
355    }
356
357    #[test]
358    fn test_() {
359        use libsodium_sys::{
360            crypto_sign_ed25519_pk_to_curve25519 as so_crypto_sign_ed25519_pk_to_curve25519,
361            crypto_sign_ed25519_sk_to_curve25519 as so_crypto_sign_ed25519_sk_to_curve25519,
362        };
363
364        for _ in 0..10 {
365            let (pk, sk) = crypto_sign_ed25519_keypair();
366            let mut xpk = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
367            let mut xsk = [0u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES];
368            crypto_sign_ed25519_pk_to_curve25519(&mut xpk, &pk).expect("pk failed");
369            crypto_sign_ed25519_sk_to_curve25519(&mut xsk, &sk);
370
371            let mut so_xpk = [0u8; CRYPTO_SCALARMULT_CURVE25519_BYTES];
372            let mut so_xsk = [0u8; CRYPTO_SCALARMULT_CURVE25519_SCALARBYTES];
373
374            unsafe {
375                so_crypto_sign_ed25519_pk_to_curve25519(so_xpk.as_mut_ptr(), pk.as_ptr());
376                so_crypto_sign_ed25519_sk_to_curve25519(so_xsk.as_mut_ptr(), sk.as_ptr());
377            }
378
379            assert_eq!(
380                general_purpose::STANDARD.encode(xpk),
381                general_purpose::STANDARD.encode(so_xpk)
382            );
383            assert_eq!(
384                general_purpose::STANDARD.encode(xsk),
385                general_purpose::STANDARD.encode(so_xsk)
386            );
387        }
388    }
389}