Skip to main content

origin_crypto_sdk/signing/
classical.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Classical (non-post-quantum) signing primitives.
4//!
5//! This module exposes Ed25519 signing with a clean seed-based API.
6//! For new applications that need quantum resistance, use
7//! [`super::hybrid::HybridSigningKeyBundle`] instead — it pairs Ed25519
8//! with Falcon-1024 so the signature remains unforgeable even if Ed25519
9//! is broken.
10//!
11//! # Example
12//!
13//! ```rust
14//! use origin_crypto_sdk::signing::classical::Ed25519Signer;
15//!
16//! let signer = Ed25519Signer::from_seed(&[0x42u8; 32]);
17//! let sig = signer.sign(b"hello world");
18//! assert!(signer.verify(b"hello world", &sig));
19//! ```
20
21use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
22
23/// Ed25519 signer with a clean seed-based API.
24///
25/// Pure Ed25519 — no post-quantum insurance. For new applications that
26/// need quantum resistance, prefer [`super::hybrid::HybridSigningKeyBundle`].
27#[derive(Clone)]
28pub struct Ed25519Signer {
29    key: SigningKey,
30}
31
32impl std::fmt::Debug for Ed25519Signer {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        // Full redaction: the private `:key` field never surfaces via Debug.
35        f.debug_struct("Ed25519Signer").finish_non_exhaustive()
36    }
37}
38
39impl Ed25519Signer {
40    /// Create a signer from a 32-byte seed.
41    pub fn from_seed(seed: &[u8; 32]) -> Self {
42        Self {
43            key: SigningKey::from_bytes(seed),
44        }
45    }
46
47    /// Create a signer from an existing Ed25519 secret key.
48    pub fn from_secret_key(key: SigningKey) -> Self {
49        Self { key }
50    }
51
52    /// Sign a message. Returns a 64-byte Ed25519 signature.
53    pub fn sign(&self, message: &[u8]) -> [u8; 64] {
54        self.key.sign(message).to_bytes()
55    }
56
57    /// Verify a 64-byte Ed25519 signature against this signer's public key.
58    pub fn verify(&self, message: &[u8], signature: &[u8; 64]) -> bool {
59        let sig = Signature::from_bytes(signature);
60        self.key.verify(message, &sig).is_ok()
61    }
62
63    /// Verify a signature against an arbitrary public key.
64    pub fn verify_with_pubkey(pubkey: &[u8; 32], message: &[u8], signature: &[u8; 64]) -> bool {
65        let Ok(vk) = VerifyingKey::from_bytes(pubkey) else {
66            return false;
67        };
68        let sig = Signature::from_bytes(signature);
69        vk.verify(message, &sig).is_ok()
70    }
71
72    /// Get the 32-byte Ed25519 public key.
73    pub fn public_key(&self) -> [u8; 32] {
74        self.key.verifying_key().to_bytes()
75    }
76
77    /// Get the 32-byte Ed25519 secret key seed.
78    pub fn secret_key(&self) -> [u8; 32] {
79        self.key.to_bytes()
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn sign_verify_roundtrip() {
89        let s = Ed25519Signer::from_seed(&[1u8; 32]);
90        let msg = b"hello";
91        let sig = s.sign(msg);
92        assert!(s.verify(msg, &sig));
93    }
94
95    #[test]
96    fn verify_rejects_tampered_msg() {
97        let s = Ed25519Signer::from_seed(&[1u8; 32]);
98        let sig = s.sign(b"original");
99        assert!(!s.verify(b"tampered", &sig));
100    }
101
102    #[test]
103    fn verify_with_pubkey() {
104        let s = Ed25519Signer::from_seed(&[2u8; 32]);
105        let pubkey = s.public_key();
106        let sig = s.sign(b"x");
107        assert!(Ed25519Signer::verify_with_pubkey(&pubkey, b"x", &sig));
108    }
109}