1use std::ops::Deref;
16
17use rand::{CryptoRng, Rng};
18use signature::{Signer as _, Verifier};
19use zeroize::{ZeroizeOnDrop, Zeroizing};
20
21use crate::{
22 crypto::{hash::HashAlgorithm, Signer},
23 errors::{bail, ensure, ensure_eq, Result},
24 ser::Serialize,
25 types::{Ed25519PublicParams, EddsaLegacyPublicParams, Mpi, SignatureBytes},
26};
27
28const MIN_HASH_LEN_BITS: usize = 256;
29
30pub const KEY_LEN: usize = 32;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
37pub enum Mode {
38 EdDSALegacy,
42
43 Ed25519,
47}
48
49#[derive(Clone, PartialEq, Eq, ZeroizeOnDrop, derive_more::Debug)]
51#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
52pub struct SecretKey {
53 #[debug("..")]
55 #[cfg_attr(test, proptest(strategy = "tests::key_gen()"))]
56 secret: ed25519_dalek::SigningKey,
57 #[zeroize(skip)]
58 pub(crate) mode: Mode,
59}
60
61impl From<&SecretKey> for Ed25519PublicParams {
62 fn from(value: &SecretKey) -> Self {
63 debug_assert_eq!(value.mode, Mode::Ed25519);
64 Self {
65 key: value.secret.verifying_key(),
66 }
67 }
68}
69
70impl From<&SecretKey> for EddsaLegacyPublicParams {
71 fn from(value: &SecretKey) -> Self {
72 debug_assert_eq!(value.mode, Mode::EdDSALegacy);
73 Self::Ed25519 {
74 key: value.secret.verifying_key(),
75 }
76 }
77}
78
79impl Deref for SecretKey {
80 type Target = ed25519_dalek::SigningKey;
81
82 fn deref(&self) -> &Self::Target {
83 &self.secret
84 }
85}
86
87impl SecretKey {
88 pub fn generate<R: Rng + CryptoRng>(mut rng: R, mode: Mode) -> Self {
93 let mut bytes = Zeroizing::new([0u8; ed25519_dalek::SECRET_KEY_LENGTH]);
94 rng.fill_bytes(&mut *bytes);
95 let secret = ed25519_dalek::SigningKey::from_bytes(&bytes);
96
97 SecretKey { secret, mode }
98 }
99
100 pub fn try_from_bytes(raw_secret: [u8; KEY_LEN], mode: Mode) -> Result<Self> {
101 let secret = ed25519_dalek::SigningKey::from(raw_secret);
102 Ok(Self { secret, mode })
103 }
104
105 pub fn as_bytes(&self) -> &[u8; KEY_LEN] {
107 self.secret.as_bytes()
108 }
109
110 pub fn mode(&self) -> Mode {
112 self.mode
113 }
114}
115
116impl Signer for SecretKey {
117 fn sign(&self, hash: HashAlgorithm, digest: &[u8]) -> Result<SignatureBytes> {
118 let Some(digest_size) = hash.digest_size() else {
119 bail!("EdDSA signature: invalid hash algorithm: {:?}", hash);
120 };
121 ensure_eq!(
122 digest.len(),
123 digest_size,
124 "Unexpected digest length {} for hash algorithm {:?}",
125 digest.len(),
126 hash,
127 );
128 ensure!(
129 digest_size * 8 >= MIN_HASH_LEN_BITS,
130 "EdDSA signature: hash algorithm {:?} is too weak for Ed25519",
131 hash,
132 );
133
134 let signature = self.secret.sign(digest);
135 let bytes = signature.to_bytes();
136
137 let sig = match self.mode {
138 Mode::EdDSALegacy => {
139 let r = Mpi::from_slice(&bytes[..32]);
140 let s = Mpi::from_slice(&bytes[32..]);
141 SignatureBytes::Mpis(vec![r, s])
142 }
143 Mode::Ed25519 => SignatureBytes::Native(bytes.to_vec().into()),
144 };
145 Ok(sig)
146 }
147}
148
149impl Serialize for SecretKey {
150 fn to_writer<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
151 match self.mode {
152 Mode::EdDSALegacy => {
153 Mpi::from_slice(&self.secret.as_bytes()[..]).to_writer(writer)?;
154 }
155 Mode::Ed25519 => {
156 let x = self.as_bytes();
157 writer.write_all(x)?;
158 }
159 }
160
161 Ok(())
162 }
163
164 fn write_len(&self) -> usize {
165 match self.mode {
166 Mode::EdDSALegacy => Mpi::from_slice(self.secret.as_bytes()).write_len(),
167 Mode::Ed25519 => KEY_LEN,
168 }
169 }
170}
171
172pub fn verify(
174 key: &ed25519_dalek::VerifyingKey,
175 hash: HashAlgorithm,
176 hashed: &[u8],
177 sig_bytes: &[u8],
178) -> Result<()> {
179 let Some(digest_size) = hash.digest_size() else {
180 bail!("EdDSA signature: invalid hash algorithm: {:?}", hash);
181 };
182 ensure!(
183 digest_size * 8 >= MIN_HASH_LEN_BITS,
184 "EdDSA signature: hash algorithm {:?} is too weak for Ed25519",
185 hash,
186 );
187
188 let sig = sig_bytes.try_into()?;
189
190 Ok(key.verify(hashed, &sig)?)
191}
192
193#[cfg(test)]
194mod tests {
195 use proptest::prelude::*;
196
197 prop_compose! {
198 pub fn key_gen()(bytes: [u8; 32]) -> ed25519_dalek::SigningKey {
199 ed25519_dalek::SigningKey::from_bytes(&bytes)
200 }
201 }
202}