Skip to main content

origin_crypto_sdk/pqc/
ed448.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Ed448 signature scheme per RFC 8032.
4//!
5//! Ed448 is the Edwards-curve digital signature algorithm operating on
6//! the "Edwards448" curve (Goldilocks). Standardized for use in TLS 1.3
7//! (RFC 8446) and DNSSEC (RFC 8080).
8//!
9//! # Curve parameters (RFC 8032 §5.1.2)
10//!
11//! - Prime field: p = 2^448 - 2^224 - 1
12//! - Curve: -x^2 + y^2 = 1 + d*x^2*y^2, with d = -39081
13//! - Base point: (x, y) where y = 5/7, x is the larger root
14//! - Order: n = 2^446 - 13818066809895115352007386748515426880336692474882178609894523903805
15//! - Cofactor: 4
16//!
17//! # Implementation
18//!
19//! This module is a thin wrapper around [`ed448_goldilocks`], the
20//! RustCrypto Ed448 implementation. We expose:
21//!
22//! - [`SigningKey`] — 57-byte private key (any bytes, high bit ignored)
23//! - [`VerifyingKey`] — 57-byte public key
24//! - [`Signature`] — 114-byte signature (R || S, each 57 bytes)
25//!
26//! # Security
27//!
28//! - Conjectured security: ~223 bits (above the 2^128 target)
29//! - Deterministic signatures: same (key, message) → same sig
30//! - Constant-time: no branches on secret data
31//! - Domain separation: "SigEd448" prefix prevents cross-curve attacks
32//!
33//! # Difference from Ed41417
34//!
35//! Ed448 and Ed41417 are NOT the same curve. They are two different
36//! high-security Edwards curves:
37//!
38//! - **Ed448** (Mike Hamburg, 2015) — operates over a 448-bit Goldilocks
39//!   prime, standardized in RFC 8032 / RFC 8446 / RFC 8080. Conjectured
40//!   ~223 bits of security. Standard for TLS 1.3 and DNSSEC.
41//! - **Ed41417** (Daniel J. Bernstein, 2014) — operates over a 414-bit
42//!   prime, used in specific protocols like X41417.
43
44use ed448_goldilocks::Signature as InnerSignature;
45use ed448_goldilocks::SigningKey as InnerSigningKey;
46use ed448_goldilocks::VerifyingKey as InnerVerifyingKey;
47
48use crate::error::{CryptoError, Result};
49
50/// Size of a private key in bytes (any 57 bytes; high bit ignored per RFC 8032).
51pub const SECRET_KEY_SIZE: usize = 57;
52
53/// Size of a public key in bytes.
54pub const PUBLIC_KEY_SIZE: usize = 57;
55
56/// Size of a signature in bytes.
57pub const SIGNATURE_SIZE: usize = 114;
58
59/// Ed448 signing key (RFC 8032).
60///
61/// Wraps a 57-byte private key. The high bit of the last byte is
62/// ignored per RFC 8032 (the "clamping" step is part of signing, not
63/// the key storage).
64#[derive(Clone)]
65pub struct SigningKey(InnerSigningKey);
66
67impl SigningKey {
68    /// Construct a signing key from raw 57-byte secret bytes.
69    pub fn from_bytes(bytes: &[u8; SECRET_KEY_SIZE]) -> Result<Self> {
70        InnerSigningKey::try_from(bytes.as_slice())
71            .map(SigningKey)
72            .map_err(|e| CryptoError::InvalidKey(format!("invalid Ed448 secret key: {e}")))
73    }
74
75    /// Sign a message. Returns a 114-byte Ed448 signature.
76    ///
77    /// Uses "pure" Ed448 (not Ed448ph) — the message is hashed directly
78    /// without a prehash separator.
79    pub fn sign(&self, message: &[u8]) -> Signature {
80        Signature(self.0.sign_raw(message))
81    }
82
83    /// Get the corresponding 57-byte public key.
84    pub fn verifying_key(&self) -> VerifyingKey {
85        VerifyingKey(self.0.verifying_key())
86    }
87
88    /// Get the 57-byte secret key bytes.
89    pub fn to_bytes(&self) -> [u8; SECRET_KEY_SIZE] {
90        let mut out = [0u8; SECRET_KEY_SIZE];
91        out.copy_from_slice(&self.0.to_bytes()[..]);
92        out
93    }
94}
95
96impl Drop for SigningKey {
97    fn drop(&mut self) {
98        // Inner SigningKey drops its secret via ZeroizeOnDrop
99    }
100}
101
102impl std::fmt::Debug for SigningKey {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        f.debug_struct("SigningKey")
105            .field("public_key", &hex::encode(self.verifying_key().to_bytes()))
106            .finish_non_exhaustive()
107    }
108}
109
110/// Ed448 verifying key (RFC 8032).
111pub struct VerifyingKey(InnerVerifyingKey);
112
113impl Clone for VerifyingKey {
114    fn clone(&self) -> Self {
115        Self(self.0)
116    }
117}
118
119impl std::fmt::Debug for VerifyingKey {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        f.debug_struct("VerifyingKey")
122            .field("bytes", &hex::encode(self.0.to_bytes()))
123            .finish()
124    }
125}
126
127impl VerifyingKey {
128    /// Parse a verifying key from 57 bytes.
129    pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_SIZE]) -> Result<Self> {
130        InnerVerifyingKey::from_bytes(bytes)
131            .map(VerifyingKey)
132            .map_err(|e| CryptoError::InvalidKey(format!("invalid Ed448 public key: {e}")))
133    }
134
135    /// Get the 57-byte public key bytes.
136    pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE] {
137        self.0.to_bytes()
138    }
139
140    /// Verify a 114-byte signature over a message.
141    ///
142    /// Returns `Ok(())` if the signature is valid, or an error otherwise.
143    /// The argument order is `verify(message, signature)` to match the
144    /// convention used by the hybrid constructions in this SDK.
145    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<()> {
146        self.0
147            .verify_raw(&signature.0, message)
148            .map_err(|_| CryptoError::AuthenticationFailed)
149    }
150}
151
152/// Ed448 signature (RFC 8032).
153///
154/// A signature is the concatenation of two 57-byte components: R (the
155/// encoded Edwards point) and S (the encoded scalar). Total 114 bytes.
156pub struct Signature(InnerSignature);
157
158impl Signature {
159    /// Serialize the signature to 114 bytes.
160    pub fn to_bytes(&self) -> [u8; SIGNATURE_SIZE] {
161        self.0.to_bytes()
162    }
163
164    /// Parse a signature from 114 bytes.
165    pub fn from_bytes(bytes: &[u8; SIGNATURE_SIZE]) -> Result<Self> {
166        InnerSignature::try_from(bytes.as_slice())
167            .map(Signature)
168            .map_err(|e| CryptoError::InvalidParameter(format!("invalid Ed448 signature: {e}")))
169    }
170}
171
172impl Clone for Signature {
173    fn clone(&self) -> Self {
174        Self(self.0)
175    }
176}
177
178impl std::fmt::Debug for Signature {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        f.debug_struct("Signature")
181            .field("bytes", &hex::encode(self.to_bytes()))
182            .finish()
183    }
184}
185
186// Compile-time size sanity check
187const _: () = {
188    assert!(SECRET_KEY_SIZE == 57);
189    assert!(PUBLIC_KEY_SIZE == 57);
190    assert!(SIGNATURE_SIZE == 114);
191};
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn key_sizes() {
199        assert_eq!(SECRET_KEY_SIZE, 57);
200        assert_eq!(PUBLIC_KEY_SIZE, 57);
201        assert_eq!(SIGNATURE_SIZE, 114);
202    }
203
204    fn sk(seed_byte: u8) -> SigningKey {
205        let seed = [seed_byte; SECRET_KEY_SIZE];
206        SigningKey::from_bytes(&seed).expect("test seed must be valid")
207    }
208
209    #[test]
210    fn sign_verify_roundtrip() {
211        let sk = sk(0x42);
212        let pk = sk.verifying_key();
213        let msg = b"hello Ed448";
214        let sig = sk.sign(msg);
215        assert!(pk.verify(msg, &sig).is_ok());
216    }
217
218    #[test]
219    fn verify_rejects_tampered_message() {
220        let sk = sk(0x42);
221        let pk = sk.verifying_key();
222        let sig = sk.sign(b"original");
223        assert!(pk.verify(b"tampered", &sig).is_err());
224    }
225
226    #[test]
227    fn verify_rejects_tampered_signature() {
228        let sk = sk(0x42);
229        let pk = sk.verifying_key();
230        let mut sig = sk.sign(b"x");
231        let mut bytes = sig.to_bytes();
232        bytes[10] ^= 0x01;
233        let bad_sig = Signature::from_bytes(&bytes).unwrap();
234        assert!(pk.verify(b"x", &bad_sig).is_err());
235    }
236
237    #[test]
238    fn deterministic_signatures() {
239        // Ed448 (and EdDSA in general) is deterministic: same key + message
240        // → same signature, no random nonce.
241        let sk1 = sk(0x42);
242        let sk2 = sk(0x42);
243        let msg = b"deterministic test";
244        let sig1 = sk1.sign(msg);
245        let sig2 = sk2.sign(msg);
246        assert_eq!(sig1.to_bytes(), sig2.to_bytes());
247    }
248
249    #[test]
250    fn public_key_derivation() {
251        let sk = sk(0x42);
252        let pk = sk.verifying_key();
253        let bytes = pk.to_bytes();
254        let restored = VerifyingKey::from_bytes(&bytes).unwrap();
255        assert_eq!(restored.to_bytes(), pk.to_bytes());
256    }
257}