mirror of
https://github.com/dalek-cryptography/ed25519-dalek.git
synced 2026-01-09 11:17:53 -05:00
Add Context type (#273)
* Add `Context` type Adds a generic type which can be used with `SigningKey` and `VerifyingKey` for storing a context string value along with the key for use with `DigestSigner` and `DigestVerifier`. * Added Context tests, docs, and re-exports * Added docs about SHA-512 for prehashing; re-re-exported Sha512 Co-authored-by: Tony Arcieri <bascule@gmail.com> Co-authored-by: Michael Rosenberg <michael@mrosenberg.pub>
This commit is contained in:
107
src/context.rs
Normal file
107
src/context.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use crate::{InternalError, SignatureError};
|
||||
|
||||
/// Ed25519 contexts as used by Ed25519ph.
|
||||
///
|
||||
/// Contexts are domain separator strings that can be used to isolate uses of
|
||||
/// the algorithm between different protocols (which is very hard to reliably do
|
||||
/// otherwise) and between different uses within the same protocol.
|
||||
///
|
||||
/// To create a context, call either of the following:
|
||||
///
|
||||
/// - [`SigningKey::with_context`](crate::SigningKey::with_context)
|
||||
/// - [`VerifyingKey::with_context`](crate::VerifyingKey::with_context)
|
||||
///
|
||||
/// For more information, see [RFC8032 § 8.3](https://www.rfc-editor.org/rfc/rfc8032#section-8.3).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
#[cfg_attr(feature = "digest", doc = "```")]
|
||||
#[cfg_attr(not(feature = "digest"), doc = "```ignore")]
|
||||
/// # fn main() {
|
||||
/// use ed25519_dalek::{Signature, SigningKey, VerifyingKey, Sha512};
|
||||
/// # use curve25519_dalek::digest::Digest;
|
||||
/// # use rand::rngs::OsRng;
|
||||
/// use ed25519_dalek::{DigestSigner, DigestVerifier};
|
||||
///
|
||||
/// # let mut csprng = OsRng;
|
||||
/// # let signing_key = SigningKey::generate(&mut csprng);
|
||||
/// # let verifying_key = signing_key.verifying_key();
|
||||
/// let context_str = b"Local Channel 3";
|
||||
/// let prehashed_message = Sha512::default().chain_update(b"Stay tuned for more news at 7");
|
||||
///
|
||||
/// // Signer
|
||||
/// let signing_context = signing_key.with_context(context_str).unwrap();
|
||||
/// let signature = signing_context.sign_digest(prehashed_message.clone());
|
||||
///
|
||||
/// // Verifier
|
||||
/// let verifying_context = verifying_key.with_context(context_str).unwrap();
|
||||
/// let verified: bool = verifying_context
|
||||
/// .verify_digest(prehashed_message, &signature)
|
||||
/// .is_ok();
|
||||
///
|
||||
/// # assert!(verified);
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Context<'k, 'v, K> {
|
||||
/// Key this context is being used with.
|
||||
key: &'k K,
|
||||
|
||||
/// Context value: a bytestring no longer than 255 octets.
|
||||
value: &'v [u8],
|
||||
}
|
||||
|
||||
impl<'k, 'v, K> Context<'k, 'v, K> {
|
||||
/// Maximum length of the context value in octets.
|
||||
pub const MAX_LENGTH: usize = 255;
|
||||
|
||||
/// Create a new Ed25519ph context.
|
||||
pub(crate) fn new(key: &'k K, value: &'v [u8]) -> Result<Self, SignatureError> {
|
||||
if value.len() <= Self::MAX_LENGTH {
|
||||
Ok(Self { key, value })
|
||||
} else {
|
||||
Err(SignatureError::from(InternalError::PrehashedContextLength))
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrow the key.
|
||||
pub fn key(&self) -> &'k K {
|
||||
self.key
|
||||
}
|
||||
|
||||
/// Borrow the context string value.
|
||||
pub fn value(&self) -> &'v [u8] {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "digest"))]
|
||||
mod test {
|
||||
use crate::{Signature, SigningKey, VerifyingKey};
|
||||
use curve25519_dalek::digest::Digest;
|
||||
use ed25519::signature::{DigestSigner, DigestVerifier};
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::Sha512;
|
||||
|
||||
#[test]
|
||||
fn context_correctness() {
|
||||
let mut csprng = OsRng;
|
||||
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
let verifying_key: VerifyingKey = signing_key.verifying_key();
|
||||
|
||||
let context_str = b"Local Channel 3";
|
||||
let prehashed_message = Sha512::default().chain_update(b"Stay tuned for more news at 7");
|
||||
|
||||
// Signer
|
||||
let signing_context = signing_key.with_context(context_str).unwrap();
|
||||
let signature: Signature = signing_context.sign_digest(prehashed_message.clone());
|
||||
|
||||
// Verifier
|
||||
let verifying_context = verifying_key.with_context(context_str).unwrap();
|
||||
let verified: bool = verifying_context
|
||||
.verify_digest(prehashed_message, &signature)
|
||||
.is_ok();
|
||||
|
||||
assert!(verified);
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,6 @@ pub(crate) enum InternalError {
|
||||
length_c: usize,
|
||||
},
|
||||
/// An ed25519ph signature can only take up to 255 octets of context.
|
||||
#[cfg(feature = "digest")]
|
||||
PrehashedContextLength,
|
||||
/// A mismatched (public, secret) key pair.
|
||||
MismatchedKeypair,
|
||||
@@ -77,7 +76,6 @@ impl Display for InternalError {
|
||||
{} has length {}, {} has length {}.",
|
||||
na, la, nb, lb, nc, lc
|
||||
),
|
||||
#[cfg(feature = "digest")]
|
||||
InternalError::PrehashedContextLength => write!(
|
||||
f,
|
||||
"An ed25519ph signature can only take up to 255 octets of context"
|
||||
|
||||
10
src/lib.rs
10
src/lib.rs
@@ -73,7 +73,7 @@
|
||||
//! # use ed25519_dalek::Signature;
|
||||
//! # use ed25519_dalek::Signer;
|
||||
//! use ed25519_dalek::{VerifyingKey, Verifier};
|
||||
//! # let mut csprng = OsRng{};
|
||||
//! # let mut csprng = OsRng;
|
||||
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||
//! # let signature: Signature = signing_key.sign(message);
|
||||
@@ -97,7 +97,7 @@
|
||||
//! # use rand::rngs::OsRng;
|
||||
//! # use ed25519_dalek::{SigningKey, Signature, Signer, VerifyingKey};
|
||||
//! use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH};
|
||||
//! # let mut csprng = OsRng{};
|
||||
//! # let mut csprng = OsRng;
|
||||
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
//! # let message: &[u8] = b"This is a test of the tsunami alert system.";
|
||||
//! # let signature: Signature = signing_key.sign(message);
|
||||
@@ -258,6 +258,7 @@ pub use ed25519;
|
||||
#[cfg(feature = "batch")]
|
||||
mod batch;
|
||||
mod constants;
|
||||
mod context;
|
||||
mod errors;
|
||||
mod signature;
|
||||
mod signing;
|
||||
@@ -265,15 +266,20 @@ mod verifying;
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
pub use curve25519_dalek::digest::Digest;
|
||||
#[cfg(feature = "digest")]
|
||||
pub use sha2::Sha512;
|
||||
|
||||
#[cfg(feature = "batch")]
|
||||
pub use crate::batch::*;
|
||||
pub use crate::constants::*;
|
||||
pub use crate::context::Context;
|
||||
pub use crate::errors::*;
|
||||
pub use crate::signing::*;
|
||||
pub use crate::verifying::*;
|
||||
|
||||
// Re-export the `Signer` and `Verifier` traits from the `signature` crate
|
||||
#[cfg(feature = "digest")]
|
||||
pub use ed25519::signature::{DigestSigner, DigestVerifier};
|
||||
pub use ed25519::signature::{Signer, Verifier};
|
||||
pub use ed25519::Signature;
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ use signature::DigestSigner;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::errors::*;
|
||||
use crate::signature::*;
|
||||
use crate::verifying::*;
|
||||
@@ -158,6 +159,15 @@ impl SigningKey {
|
||||
self.verifying_key
|
||||
}
|
||||
|
||||
/// Create a signing context that can be used for Ed25519ph with
|
||||
/// [`DigestSigner`].
|
||||
pub fn with_context<'k, 'v>(
|
||||
&'k self,
|
||||
context_value: &'v [u8],
|
||||
) -> Result<Context<'k, 'v, Self>, SignatureError> {
|
||||
Context::new(self, context_value)
|
||||
}
|
||||
|
||||
/// Generate an ed25519 signing key.
|
||||
///
|
||||
/// # Example
|
||||
@@ -200,9 +210,7 @@ impl SigningKey {
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `prehashed_message` is an instantiated hash digest with 512-bits of
|
||||
/// output which has had the message to be signed previously fed into its
|
||||
/// state.
|
||||
/// * `prehashed_message` is an instantiated SHA-512 digest of the message
|
||||
/// * `context` is an optional context string, up to 255 bytes inclusive,
|
||||
/// which may be used to provide additional domain separation. If not
|
||||
/// set, this will default to an empty string.
|
||||
@@ -211,6 +219,13 @@ impl SigningKey {
|
||||
///
|
||||
/// An Ed25519ph [`Signature`] on the `prehashed_message`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing. This function technically works,
|
||||
/// and is probably safe to use, with any secure hash function with 512-bit digests, but
|
||||
/// anything outside of SHA-512 is NOT specification-compliant. We expose [`crate::Sha512`] for
|
||||
/// user convenience.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
#[cfg_attr(all(feature = "rand_core", feature = "digest"), doc = "```")]
|
||||
@@ -226,7 +241,7 @@ impl SigningKey {
|
||||
///
|
||||
/// # #[cfg(feature = "std")]
|
||||
/// # fn main() {
|
||||
/// let mut csprng = OsRng{};
|
||||
/// let mut csprng = OsRng;
|
||||
/// let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
/// let message: &[u8] = b"All I want is to pet all of the dogs.";
|
||||
///
|
||||
@@ -274,7 +289,7 @@ impl SigningKey {
|
||||
/// # use rand::rngs::OsRng;
|
||||
/// #
|
||||
/// # fn do_test() -> Result<Signature, SignatureError> {
|
||||
/// # let mut csprng = OsRng{};
|
||||
/// # let mut csprng = OsRng;
|
||||
/// # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
/// # let message: &[u8] = b"All I want is to pet all of the dogs.";
|
||||
/// # let mut prehashed: Sha512 = Sha512::new();
|
||||
@@ -348,7 +363,7 @@ impl SigningKey {
|
||||
/// use rand::rngs::OsRng;
|
||||
///
|
||||
/// # fn do_test() -> Result<(), SignatureError> {
|
||||
/// let mut csprng = OsRng{};
|
||||
/// let mut csprng = OsRng;
|
||||
/// let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
/// let message: &[u8] = b"All I want is to pet all of the dogs.";
|
||||
///
|
||||
@@ -485,6 +500,12 @@ impl Signer<Signature> for SigningKey {
|
||||
}
|
||||
|
||||
/// Equivalent to [`SigningKey::sign_prehashed`] with `context` set to [`None`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing. This function technically works, and is
|
||||
/// probably safe to use, with any secure hash function with 512-bit digests, but anything outside
|
||||
/// of SHA-512 is NOT specification-compliant. We expose [`crate::Sha512`] for user convenience.
|
||||
#[cfg(feature = "digest")]
|
||||
impl<D> DigestSigner<D, Signature> for SigningKey
|
||||
where
|
||||
@@ -495,6 +516,24 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`SigningKey::sign_prehashed`] with `context` set to [`Some`]
|
||||
/// containing `self.value()`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing. This function technically works, and is
|
||||
/// probably safe to use, with any secure hash function with 512-bit digests, but anything outside
|
||||
/// of SHA-512 is NOT specification-compliant. We expose [`crate::Sha512`] for user convenience.
|
||||
#[cfg(feature = "digest")]
|
||||
impl<D> DigestSigner<D, Signature> for Context<'_, '_, SigningKey>
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
{
|
||||
fn try_sign_digest(&self, msg_digest: D) -> Result<Signature, SignatureError> {
|
||||
self.key().sign_prehashed(msg_digest, Some(self.value()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Verifier<Signature> for SigningKey {
|
||||
/// Verify a signature on a message with this signing key's public key.
|
||||
fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
|
||||
|
||||
@@ -38,6 +38,7 @@ use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes};
|
||||
use signature::DigestVerifier;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::context::Context;
|
||||
use crate::errors::*;
|
||||
use crate::signature::*;
|
||||
use crate::signing::*;
|
||||
@@ -153,6 +154,15 @@ impl VerifyingKey {
|
||||
Ok(VerifyingKey(compressed, point))
|
||||
}
|
||||
|
||||
/// Create a verifying context that can be used for Ed25519ph with
|
||||
/// [`DigestVerifier`].
|
||||
pub fn with_context<'k, 'v>(
|
||||
&'k self,
|
||||
context_value: &'v [u8],
|
||||
) -> Result<Context<'k, 'v, Self>, SignatureError> {
|
||||
Context::new(self, context_value)
|
||||
}
|
||||
|
||||
/// Internal utility function for clamping a scalar representation and multiplying by the
|
||||
/// basepont to produce a public key.
|
||||
fn clamp_and_mul_base(bits: [u8; 32]) -> VerifyingKey {
|
||||
@@ -431,6 +441,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`VerifyingKey::verify_prehashed`] with `context` set to [`Some`]
|
||||
/// containing `self.value()`.
|
||||
#[cfg(feature = "digest")]
|
||||
impl<D> DigestVerifier<D, ed25519::Signature> for Context<'_, '_, VerifyingKey>
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
{
|
||||
fn verify_digest(
|
||||
&self,
|
||||
msg_digest: D,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError> {
|
||||
self.key()
|
||||
.verify_prehashed(msg_digest, Some(self.value()), signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for VerifyingKey {
|
||||
type Error = SignatureError;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user