did_simple/crypto/ed25519.rs
1//! Implementation of the ed25519ph signing algorithm.
2
3use curve25519_dalek::edwards::CompressedEdwardsY;
4
5use super::Context;
6use crate::key_algos::StaticSigningAlgo as _;
7
8pub use ed25519_dalek::{ed25519::Signature, Digest, Sha512, SignatureError};
9
10/// Re-exported for lower level control
11pub use ed25519_dalek;
12
13/// An ed25519 public key. Used for verifying messages.
14///
15/// We recommend deserializing bytes into this type using
16/// [`Self::try_from_bytes()`]. Then you can either use this type, which has
17/// simpler function signatures, or you can call [`Self::into_inner()`] and use
18/// the lower level [`ed25519_dalek`] crate directly, which is slightly less
19/// opinionated and has more customization of options made available.
20#[derive(Debug, Eq, PartialEq, Hash)]
21pub struct VerifyingKey(ed25519_dalek::VerifyingKey);
22
23impl VerifyingKey {
24 pub const LEN: usize = Self::key_len();
25
26 /// Instantiates `PubKey` from some bytes. Performs all necessary validation
27 /// that the key is valid and of sufficient strength.
28 ///
29 /// Note that we will reject any keys that are too weak (aka low order).
30 pub fn try_from_bytes(bytes: &[u8; Self::LEN]) -> Result<Self, TryFromBytesError> {
31 let compressed_edwards = CompressedEdwardsY(bytes.to_owned());
32 let Some(edwards) = compressed_edwards.decompress() else {
33 return Err(TryFromBytesError::NotOnCurve);
34 };
35 let key = ed25519_dalek::VerifyingKey::from(edwards);
36 if key.is_weak() {
37 return Err(TryFromBytesError::WeakKey);
38 }
39 Ok(Self(key))
40 }
41
42 // TODO: Turn this into inline const when that feature stabilizes
43 const fn key_len() -> usize {
44 let len = crate::key_algos::Ed25519::VERIFYING_KEY_LEN;
45 assert!(len == ed25519_dalek::PUBLIC_KEY_LENGTH);
46 len
47 }
48
49 pub fn into_inner(self) -> ed25519_dalek::VerifyingKey {
50 self.0
51 }
52
53 /// Verifies `message` using the ed25519ph algorithm.
54 ///
55 /// # Example
56 /// ```
57 /// use did_simple::crypto::{Context, ed25519::{SigningKey, VerifyingKey}};
58 ///
59 /// let signing_key = SigningKey::random();
60 /// let verifying_key = signing_key.verifying_key();
61 /// const CTX: Context = Context::from_bytes("MySuperCoolProtocol".as_bytes());
62 ///
63 /// let msg = "everyone can read and verify this message";
64 /// let sig = signing_key.sign(msg, CTX);
65 ///
66 /// assert!(verifying_key.verify(msg, CTX, &sig).is_ok());
67 /// ```
68 pub fn verify(
69 &self,
70 message: impl AsRef<[u8]>,
71 context: Context,
72 signature: &Signature,
73 ) -> Result<(), SignatureError> {
74 let digest = Sha512::new().chain_update(message);
75 self.verify_digest(digest, context, signature)
76 }
77
78 /// Same as `verify`, but allows you to populate `message_digest` separately
79 /// from signing.
80 ///
81 /// This can be useful if for example, it is undesirable to buffer the message
82 /// into a single slice, or the message is being streamed asynchronously.
83 /// You can instead update the digest chunk by chunk, and pass the digest
84 /// in after you are done reading all the data.
85 ///
86 /// # Example
87 ///
88 /// ```
89 /// use did_simple::crypto::{Context, ed25519::{Sha512, Digest, SigningKey, VerifyingKey}};
90 ///
91 /// let signing_key = SigningKey::random();
92 /// let verifying_key = signing_key.verifying_key();
93 /// const CTX: Context = Context::from_bytes("MySuperCoolProtocol".as_bytes());
94 ///
95 /// let sig = signing_key.sign("this is my message", CTX);
96 ///
97 /// let mut digest = Sha512::new();
98 /// digest.update("this is ");
99 /// digest.update("my message");
100 /// assert!(verifying_key.verify_digest(digest, CTX, &sig).is_ok());
101 pub fn verify_digest(
102 &self,
103 message_digest: Sha512,
104 context: Context,
105 signature: &Signature,
106 ) -> Result<(), SignatureError> {
107 self.0
108 .verify_prehashed_strict(message_digest, Some(context.0), signature)
109 }
110}
111
112impl TryFrom<ed25519_dalek::VerifyingKey> for VerifyingKey {
113 type Error = TryFromBytesError;
114
115 fn try_from(value: ed25519_dalek::VerifyingKey) -> Result<Self, Self::Error> {
116 Self::try_from_bytes(value.as_bytes())
117 }
118}
119
120/// An ed25519 private key. Used for signing messages.
121#[derive(Debug)]
122pub struct SigningKey(ed25519_dalek::SigningKey);
123
124impl SigningKey {
125 pub const LEN: usize = Self::key_len();
126
127 pub fn from_bytes(bytes: &[u8; Self::LEN]) -> Self {
128 let signing = ed25519_dalek::SigningKey::from_bytes(bytes);
129 let _pub = VerifyingKey::try_from(signing.verifying_key())
130 .expect("this should never fail. if it does, please open an issue");
131 Self(signing)
132 }
133
134 // TODO: Turn this into inline const when that feature stabilizes
135 const fn key_len() -> usize {
136 let len = crate::key_algos::Ed25519::SIGNING_KEY_LEN;
137 assert!(len == ed25519_dalek::SECRET_KEY_LENGTH);
138 len
139 }
140
141 pub fn into_inner(self) -> ed25519_dalek::SigningKey {
142 self.0
143 }
144
145 /// Generates a new random [`SigningKey`].
146 #[cfg(feature = "random")]
147 pub fn random() -> Self {
148 let mut csprng = rand_core::OsRng;
149 Self(ed25519_dalek::SigningKey::generate(&mut csprng))
150 }
151
152 /// Generates a new random [`SigningKey`] from the given RNG.
153 #[cfg(feature = "random")]
154 pub fn random_from_rng<R: rand_core::CryptoRngCore + ?Sized>(rng: &mut R) -> Self {
155 Self(ed25519_dalek::SigningKey::generate(rng))
156 }
157
158 /// Gets the public [`VerifyingKey`] that corresponds to this private [`SigningKey`].
159 pub fn verifying_key(&self) -> VerifyingKey {
160 VerifyingKey::try_from(self.0.verifying_key())
161 .expect("this should never fail. if it does, please open an issue")
162 }
163
164 /// Signs `message` using the ed25519ph algorithm.
165 ///
166 /// # Example
167 ///
168 /// ```
169 /// use did_simple::crypto::{Context, ed25519::{SigningKey, VerifyingKey}};
170 ///
171 /// let signing_key = SigningKey::random();
172 /// let verifying_key = signing_key.verifying_key();
173 /// const CTX: Context = Context::from_bytes("MySuperCoolProtocol".as_bytes());
174 ///
175 /// let msg = "everyone can read and verify this message";
176 /// let sig = signing_key.sign(msg, CTX);
177 ///
178 /// assert!(verifying_key.verify(msg, CTX, &sig).is_ok());
179 /// ```
180 pub fn sign(&self, message: impl AsRef<[u8]>, context: Context) -> Signature {
181 let digest = Sha512::new().chain_update(message);
182 self.sign_digest(digest, context)
183 }
184
185 /// Same as `sign`, but allows you to populate `message_digest` separately
186 /// from signing.
187 ///
188 /// This can be useful if for example, it is undesirable to buffer the message
189 /// into a single slice, or the message is being streamed asynchronously.
190 /// You can instead update the digest chunk by chunk, and pass the digest
191 /// in after you are done reading all the data.
192 ///
193 /// # Example
194 ///
195 /// ```
196 /// use did_simple::crypto::{Context, ed25519::{Sha512, Digest, SigningKey, VerifyingKey}};
197 ///
198 /// let signing_key = SigningKey::random();
199 /// let verifying_key = signing_key.verifying_key();
200 /// const CTX: Context = Context::from_bytes("MySuperCoolProtocol".as_bytes());
201 ///
202 /// let mut digest = Sha512::new();
203 /// digest.update("this is ");
204 /// digest.update("my message");
205 /// let sig = signing_key.sign_digest(digest, CTX);
206 ///
207 /// assert!(verifying_key.verify("this is my message", CTX, &sig).is_ok());
208 pub fn sign_digest(&self, message_digest: Sha512, context: Context) -> Signature {
209 self.0
210 .sign_prehashed(message_digest, Some(context.0))
211 .expect("this should never fail. if it does, please open an issue")
212 }
213}
214
215#[derive(thiserror::Error, Debug)]
216pub enum TryFromBytesError {
217 #[error(
218 "the provided bytes was not the y coordinate of a valid point on the curve"
219 )]
220 NotOnCurve,
221 #[error("public key has a low order and is too weak, which would allow the key to generate signatures that work for almost any message. To prevent this, we reject weak keys.")]
222 WeakKey,
223}
224
225#[cfg(test)]
226mod test {}