Skip to content

Commit

Permalink
did-simple: implement ed25519 sign and verify
Browse files Browse the repository at this point in the history
  • Loading branch information
TheButlah committed May 24, 2024
1 parent 2d50b49 commit 6506820
Show file tree
Hide file tree
Showing 6 changed files with 377 additions and 26 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions crates/did-simple/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ description = "Dead simple DIDs"
publish = true

[features]
default = ["ed25519"]
default = ["ed25519", "random"]
ed25519 = [
"dep:curve25519-dalek",
"dep:ed25519-dalek",
]
random = ["dep:rand_core", "ed25519-dalek?/rand_core"]

# Only applications should enable this! If you use did-simple as a dependency,
# don't enable this feature - let applications set it instead.
Expand All @@ -24,8 +25,9 @@ allow-unsafe = []
bs58 = "0.5.1"
bytes = "1.6.0"
thiserror = "1.0.60"
ed25519-dalek = { version = "2.1.1", optional = true }
ed25519-dalek = { version = "2.1.1", optional = true, features = ["digest"] }
curve25519-dalek = { version = "4.1.2", optional = true }
rand_core = { version = "0.6.4", optional = true, features = ["getrandom"] }

[dev-dependencies]
eyre = "0.6.12"
Expand Down
201 changes: 188 additions & 13 deletions crates/did-simple/src/crypto/ed25519.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
//! Implementation of the ed25519ph signing algorithm.

use curve25519_dalek::edwards::CompressedEdwardsY;
use ed25519_dalek::VerifyingKey;

use crate::key_algos::StaticKeyAlgo as _;
use super::Context;
use crate::key_algos::StaticSigningAlgo as _;

pub use ed25519_dalek::{ed25519::Signature, Digest, Sha512, SignatureError};

/// An ed25519 public key.
#[allow(dead_code)]
pub struct PubKey(VerifyingKey);
/// Re-exported for lower level control
pub use ed25519_dalek;

impl PubKey {
/// An ed25519 public key. Used for verifying messages.
///
/// We recommend deserializing bytes into this type using
/// [`Self::try_from_bytes()`]. Then you can either use this type, which has
/// simpler function signatures, or you can call [`Self::into_inner()`] and use
/// the lower level [`ed25519_dalek`] crate directly, which is slightly less
/// opinionated and has more customization of options made available.
#[derive(Debug, Eq, PartialEq, Hash)]
pub struct VerifyingKey(ed25519_dalek::VerifyingKey);

impl VerifyingKey {
pub const LEN: usize = Self::key_len();

/// Instantiates `PubKey` from some bytes. Performs all necessary validation
/// that the key is valid and of sufficient strength.
///
/// Note that we will reject any keys that are too weak (aka low order).
pub fn try_from(bytes: &[u8; Self::LEN]) -> Result<Self, TryFromBytesError> {
pub fn try_from_bytes(bytes: &[u8; Self::LEN]) -> Result<Self, TryFromBytesError> {
let compressed_edwards = CompressedEdwardsY(bytes.to_owned());
let Some(edwards) = compressed_edwards.decompress() else {
return Err(TryFromBytesError::NotOnCurve);
};
let key = VerifyingKey::from(edwards);
let key = ed25519_dalek::VerifyingKey::from(edwards);
if key.is_weak() {
return Err(TryFromBytesError::WeakKey);
}
Expand All @@ -28,10 +41,174 @@ impl PubKey {

// TODO: Turn this into inline const when that feature stabilizes
const fn key_len() -> usize {
let len = crate::key_algos::Ed25519::PUB_KEY_LEN;
let len = crate::key_algos::Ed25519::VERIFYING_KEY_LEN;
assert!(len == ed25519_dalek::PUBLIC_KEY_LENGTH);
len
}

pub fn into_inner(self) -> ed25519_dalek::VerifyingKey {
self.0
}

/// Verifies `message` using the ed25519ph algorithm.
///
/// # Example
/// ```
/// use did_simple::crypto::{Context, ed25519::{SigningKey, VerifyingKey}};
///
/// let signing_key = SigningKey::random();
/// let verifying_key = signing_key.verifying_key();
/// const CTX: Context = Context::from_bytes("MySuperCoolProtocol".as_bytes());
///
/// let msg = "everyone can read and verify this message";
/// let sig = signing_key.sign(msg, CTX);
///
/// assert!(verifying_key.verify(msg, CTX, &sig).is_ok());
/// ```
pub fn verify(
&self,
message: impl AsRef<[u8]>,
context: Context,
signature: &Signature,
) -> Result<(), SignatureError> {
let digest = Sha512::new().chain_update(message);
self.verify_digest(digest, context, signature)
}

/// Same as `verify`, but allows you to populate `message_digest` separately
/// from signing.
///
/// This can be useful if for example, it is undesirable to buffer the message
/// into a single slice, or the message is being streamed asynchronously.
/// You can instead update the digest chunk by chunk, and pass the digest
/// in after you are done reading all the data.
///
/// # Example
///
/// ```
/// use did_simple::crypto::{Context, ed25519::{Sha512, Digest, SigningKey, VerifyingKey}};
///
/// let signing_key = SigningKey::random();
/// let verifying_key = signing_key.verifying_key();
/// const CTX: Context = Context::from_bytes("MySuperCoolProtocol".as_bytes());
///
/// let sig = signing_key.sign("this is my message", CTX);
/// let mut digest = Sha512::new();
/// digest.update("this is ");
/// digest.update("my message");
/// assert!(verifying_key.verify_digest(digest, CTX, &sig).is_ok());
pub fn verify_digest(
&self,
message_digest: Sha512,
context: Context,
signature: &Signature,
) -> Result<(), SignatureError> {
self.0
.verify_prehashed_strict(message_digest, Some(context.0), signature)
}
}

impl TryFrom<ed25519_dalek::VerifyingKey> for VerifyingKey {
type Error = TryFromBytesError;

fn try_from(value: ed25519_dalek::VerifyingKey) -> Result<Self, Self::Error> {
Self::try_from_bytes(value.as_bytes())
}
}

/// An ed25519 private key. Used for signing messages.
#[derive(Debug)]
pub struct SigningKey(ed25519_dalek::SigningKey);

impl SigningKey {
pub const LEN: usize = Self::key_len();

pub fn from_bytes(bytes: &[u8; Self::LEN]) -> Self {
let signing = ed25519_dalek::SigningKey::from_bytes(bytes);
let _pub = VerifyingKey::try_from(signing.verifying_key())
.expect("this should never fail. if it does, please open an issue");
Self(signing)
}

// TODO: Turn this into inline const when that feature stabilizes
const fn key_len() -> usize {
let len = crate::key_algos::Ed25519::SIGNING_KEY_LEN;
assert!(len == ed25519_dalek::SECRET_KEY_LENGTH);
len
}

pub fn into_inner(self) -> ed25519_dalek::SigningKey {
self.0
}

/// Generates a new random [`SigningKey`].
#[cfg(feature = "random")]
pub fn random() -> Self {
let mut csprng = rand_core::OsRng;
Self(ed25519_dalek::SigningKey::generate(&mut csprng))
}

/// Generates a new random [`SigningKey`] from the given RNG.
#[cfg(feature = "random")]
pub fn random_from_rng<R: rand_core::CryptoRngCore + ?Sized>(rng: &mut R) -> Self {
Self(ed25519_dalek::SigningKey::generate(rng))
}

/// Gets the public [`VerifyingKey`] that corresponds to this private [`SigningKey`].
pub fn verifying_key(&self) -> VerifyingKey {
VerifyingKey::try_from(self.0.verifying_key())
.expect("this should never fail. if it does, please open an issue")
}

/// Signs `message` using the ed25519ph algorithm.
///
/// # Example
///
/// ```
/// use did_simple::crypto::{Context, ed25519::{SigningKey, VerifyingKey}};
///
/// let signing_key = SigningKey::random();
/// let verifying_key = signing_key.verifying_key();
/// const CTX: Context = Context::from_bytes("MySuperCoolProtocol".as_bytes());
///
/// let msg = "everyone can read and verify this message";
/// let sig = signing_key.sign(msg, CTX);
///
/// assert!(verifying_key.verify(msg, CTX, &sig).is_ok());
/// ```
pub fn sign(&self, message: impl AsRef<[u8]>, context: Context) -> Signature {
let digest = Sha512::new().chain_update(message);
self.sign_digest(digest, context)
}

/// Same as `sign`, but allows you to populate `message_digest` separately
/// from signing.
///
/// This can be useful if for example, it is undesirable to buffer the message
/// into a single slice, or the message is being streamed asynchronously.
/// You can instead update the digest chunk by chunk, and pass the digest
/// in after you are done reading all the data.
///
/// # Example
///
/// ```
/// use did_simple::crypto::{Context, ed25519::{Sha512, Digest, SigningKey, VerifyingKey}};
///
/// let signing_key = SigningKey::random();
/// let verifying_key = signing_key.verifying_key();
/// const CTX: Context = Context::from_bytes("MySuperCoolProtocol".as_bytes());
///
/// let mut digest = Sha512::new();
/// digest.update("this is ");
/// digest.update("my message");
/// let sig = signing_key.sign_digest(digest, CTX);
///
/// assert!(verifying_key.verify("this is my message", CTX, &sig).is_ok());
pub fn sign_digest(&self, message_digest: Sha512, context: Context) -> Signature {
self.0
.sign_prehashed(message_digest, Some(context.0))
.expect("this should never fail. if it does, please open an issue")
}
}

#[derive(thiserror::Error, Debug)]
Expand All @@ -44,7 +221,5 @@ pub enum TryFromBytesError {
WeakKey,
}

/// Errors which may occur while processing signatures and keypairs.
#[derive(thiserror::Error, Debug)]
#[error("invalid signature")]
pub struct SignatureError(#[from] ed25519_dalek::SignatureError);
#[cfg(test)]
mod test {}
Loading

0 comments on commit 6506820

Please sign in to comment.