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 {}