1use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
10use sha2::{Digest, Sha512};
11
12use crate::kdf::DerivedKey;
13use crate::Error;
14
15pub struct Keypair {
18 signing_key: SigningKey,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct PublicKey(VerifyingKey);
24
25impl Keypair {
26 pub fn from_seed(seed: &[u8; 32]) -> Self {
28 Self {
29 signing_key: SigningKey::from_bytes(seed),
30 }
31 }
32
33 pub fn from_derived_key(key: &DerivedKey) -> Self {
35 Self::from_seed(key.as_bytes())
36 }
37
38 pub fn from_random() -> Self {
40 use rand::rngs::OsRng;
41 Self {
42 signing_key: SigningKey::generate(&mut OsRng),
43 }
44 }
45
46 pub fn from_signing_key(key: SigningKey) -> Self {
48 Self { signing_key: key }
49 }
50
51 pub fn public_key(&self) -> PublicKey {
53 PublicKey(self.signing_key.verifying_key())
54 }
55
56 pub fn sign(&self, message: &[u8]) -> Vec<u8> {
58 let sig: Signature = self.signing_key.sign(message);
59 sig.to_bytes().to_vec()
60 }
61
62 pub fn to_seed_bytes(&self) -> [u8; 32] {
65 self.signing_key.to_bytes()
66 }
67
68 pub fn to_x25519_secret_bytes(&self) -> [u8; 32] {
75 let seed = self.signing_key.to_bytes();
76 let hash = Sha512::digest(seed);
77 let mut secret = [0u8; 32];
78 secret.copy_from_slice(&hash[..32]);
79 secret[0] &= 248;
80 secret[31] &= 127;
81 secret[31] |= 64;
82 secret
83 }
84}
85
86impl PublicKey {
87 pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), Error> {
89 let sig = Signature::from_slice(signature).map_err(|_| Error::SignatureVerification)?;
90 self.0
91 .verify(message, &sig)
92 .map_err(|_| Error::SignatureVerification)
93 }
94
95 pub fn to_hex(&self) -> String {
97 hex::encode(self.0.as_bytes())
98 }
99
100 pub fn from_hex(s: &str) -> Result<Self, Error> {
102 let bytes = hex::decode(s).map_err(|e| Error::InvalidHex(e.to_string()))?;
103 let arr: [u8; 32] = bytes
104 .try_into()
105 .map_err(|v: Vec<u8>| Error::InvalidLength {
106 expected: 32,
107 got: v.len(),
108 })?;
109 let key = VerifyingKey::from_bytes(&arr).map_err(|_| Error::InvalidPublicKey)?;
110 Ok(Self(key))
111 }
112
113 pub fn as_bytes(&self) -> [u8; 32] {
116 self.0.to_bytes()
117 }
118
119 pub fn to_x25519_public_bytes(&self) -> [u8; 32] {
124 self.0.to_montgomery().to_bytes()
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::kdf::{derive_argon2id, Salt};
132
133 const TEST_PASSPHRASE: &str = "correct horse battery staple extra words";
134
135 fn fixed_seed() -> [u8; 32] {
136 [7u8; 32]
137 }
138
139 fn derived_keypair() -> Keypair {
140 let salt =
141 Salt::from_hex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
142 .unwrap();
143 let key = derive_argon2id(TEST_PASSPHRASE, &salt).unwrap();
144 Keypair::from_derived_key(&key)
145 }
146
147 #[test]
148 fn from_seed_deterministic() {
149 let seed = fixed_seed();
150 let a = Keypair::from_seed(&seed);
151 let b = Keypair::from_seed(&seed);
152 assert_eq!(a.public_key(), b.public_key());
153 }
154
155 #[test]
156 fn from_derived_key_deterministic() {
157 let kp1 = derived_keypair();
158 let kp2 = derived_keypair();
159 assert_eq!(kp1.public_key(), kp2.public_key());
160 }
161
162 #[test]
163 fn random_keypairs_differ() {
164 let a = Keypair::from_random();
165 let b = Keypair::from_random();
166 assert_ne!(a.public_key(), b.public_key());
167 }
168
169 #[test]
170 fn sign_verify_roundtrip() {
171 let kp = Keypair::from_seed(&fixed_seed());
172 let sig = kp.sign(b"hello");
173 kp.public_key().verify(b"hello", &sig).unwrap();
174 }
175
176 #[test]
177 fn verify_rejects_tampered_message() {
178 let kp = Keypair::from_seed(&fixed_seed());
179 let sig = kp.sign(b"original");
180 assert!(kp.public_key().verify(b"tampered", &sig).is_err());
181 }
182
183 #[test]
184 fn verify_rejects_other_key() {
185 let kp_a = Keypair::from_seed(&[1u8; 32]);
186 let kp_b = Keypair::from_seed(&[2u8; 32]);
187 let sig = kp_a.sign(b"hello");
188 assert!(kp_b.public_key().verify(b"hello", &sig).is_err());
189 }
190
191 #[test]
192 fn public_key_hex_roundtrip() {
193 let kp = Keypair::from_seed(&fixed_seed());
194 let pk = kp.public_key();
195 let parsed = PublicKey::from_hex(&pk.to_hex()).unwrap();
196 assert_eq!(pk, parsed);
197 }
198
199 #[test]
200 fn public_key_invalid_hex_rejected() {
201 assert!(matches!(
202 PublicKey::from_hex("zzzz").unwrap_err(),
203 Error::InvalidHex(_)
204 ));
205 }
206
207 #[test]
208 fn public_key_invalid_length_rejected() {
209 assert!(matches!(
210 PublicKey::from_hex("00").unwrap_err(),
211 Error::InvalidLength { expected: 32, .. }
212 ));
213 }
214
215 #[test]
216 fn seed_roundtrip_preserves_keypair() {
217 let seed = fixed_seed();
218 let kp = Keypair::from_seed(&seed);
219 let extracted = kp.to_seed_bytes();
220 let kp2 = Keypair::from_seed(&extracted);
221 assert_eq!(kp.public_key(), kp2.public_key());
222 }
223}