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:
Tony Arcieri 2023-01-26 13:41:20 -07:00 committed by GitHub
parent 27ba9dd614
commit 861784f57e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 187 additions and 10 deletions

107
src/context.rs Normal file
View 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);
}
}

View file

@ -48,7 +48,6 @@ pub(crate) enum InternalError {
length_c: usize, length_c: usize,
}, },
/// An ed25519ph signature can only take up to 255 octets of context. /// An ed25519ph signature can only take up to 255 octets of context.
#[cfg(feature = "digest")]
PrehashedContextLength, PrehashedContextLength,
/// A mismatched (public, secret) key pair. /// A mismatched (public, secret) key pair.
MismatchedKeypair, MismatchedKeypair,
@ -77,7 +76,6 @@ impl Display for InternalError {
{} has length {}, {} has length {}.", {} has length {}, {} has length {}.",
na, la, nb, lb, nc, lc na, la, nb, lb, nc, lc
), ),
#[cfg(feature = "digest")]
InternalError::PrehashedContextLength => write!( InternalError::PrehashedContextLength => write!(
f, f,
"An ed25519ph signature can only take up to 255 octets of context" "An ed25519ph signature can only take up to 255 octets of context"

View file

@ -73,7 +73,7 @@
//! # use ed25519_dalek::Signature; //! # use ed25519_dalek::Signature;
//! # use ed25519_dalek::Signer; //! # use ed25519_dalek::Signer;
//! use ed25519_dalek::{VerifyingKey, Verifier}; //! use ed25519_dalek::{VerifyingKey, Verifier};
//! # let mut csprng = OsRng{}; //! # let mut csprng = OsRng;
//! # let signing_key: SigningKey = SigningKey::generate(&mut csprng); //! # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
//! # let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let message: &[u8] = b"This is a test of the tsunami alert system.";
//! # let signature: Signature = signing_key.sign(message); //! # let signature: Signature = signing_key.sign(message);
@ -97,7 +97,7 @@
//! # use rand::rngs::OsRng; //! # use rand::rngs::OsRng;
//! # use ed25519_dalek::{SigningKey, Signature, Signer, VerifyingKey}; //! # use ed25519_dalek::{SigningKey, Signature, Signer, VerifyingKey};
//! use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH}; //! 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 signing_key: SigningKey = SigningKey::generate(&mut csprng);
//! # let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let message: &[u8] = b"This is a test of the tsunami alert system.";
//! # let signature: Signature = signing_key.sign(message); //! # let signature: Signature = signing_key.sign(message);
@ -258,6 +258,7 @@ pub use ed25519;
#[cfg(feature = "batch")] #[cfg(feature = "batch")]
mod batch; mod batch;
mod constants; mod constants;
mod context;
mod errors; mod errors;
mod signature; mod signature;
mod signing; mod signing;
@ -265,15 +266,20 @@ mod verifying;
#[cfg(feature = "digest")] #[cfg(feature = "digest")]
pub use curve25519_dalek::digest::Digest; pub use curve25519_dalek::digest::Digest;
#[cfg(feature = "digest")]
pub use sha2::Sha512;
#[cfg(feature = "batch")] #[cfg(feature = "batch")]
pub use crate::batch::*; pub use crate::batch::*;
pub use crate::constants::*; pub use crate::constants::*;
pub use crate::context::Context;
pub use crate::errors::*; pub use crate::errors::*;
pub use crate::signing::*; pub use crate::signing::*;
pub use crate::verifying::*; pub use crate::verifying::*;
// Re-export the `Signer` and `Verifier` traits from the `signature` crate // 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::{Signer, Verifier};
pub use ed25519::Signature; pub use ed25519::Signature;

View file

@ -40,6 +40,7 @@ use signature::DigestSigner;
use zeroize::{Zeroize, ZeroizeOnDrop}; use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::constants::*; use crate::constants::*;
use crate::context::Context;
use crate::errors::*; use crate::errors::*;
use crate::signature::*; use crate::signature::*;
use crate::verifying::*; use crate::verifying::*;
@ -158,6 +159,15 @@ impl SigningKey {
self.verifying_key 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. /// Generate an ed25519 signing key.
/// ///
/// # Example /// # Example
@ -200,9 +210,7 @@ impl SigningKey {
/// ///
/// # Inputs /// # Inputs
/// ///
/// * `prehashed_message` is an instantiated hash digest with 512-bits of /// * `prehashed_message` is an instantiated SHA-512 digest of the message
/// output which has had the message to be signed previously fed into its
/// state.
/// * `context` is an optional context string, up to 255 bytes inclusive, /// * `context` is an optional context string, up to 255 bytes inclusive,
/// which may be used to provide additional domain separation. If not /// which may be used to provide additional domain separation. If not
/// set, this will default to an empty string. /// set, this will default to an empty string.
@ -211,6 +219,13 @@ impl SigningKey {
/// ///
/// An Ed25519ph [`Signature`] on the `prehashed_message`. /// 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 /// # Examples
/// ///
#[cfg_attr(all(feature = "rand_core", feature = "digest"), doc = "```")] #[cfg_attr(all(feature = "rand_core", feature = "digest"), doc = "```")]
@ -226,7 +241,7 @@ impl SigningKey {
/// ///
/// # #[cfg(feature = "std")] /// # #[cfg(feature = "std")]
/// # fn main() { /// # fn main() {
/// let mut csprng = OsRng{}; /// let mut csprng = OsRng;
/// let signing_key: SigningKey = SigningKey::generate(&mut csprng); /// let signing_key: SigningKey = SigningKey::generate(&mut csprng);
/// let message: &[u8] = b"All I want is to pet all of the dogs."; /// let message: &[u8] = b"All I want is to pet all of the dogs.";
/// ///
@ -274,7 +289,7 @@ impl SigningKey {
/// # use rand::rngs::OsRng; /// # use rand::rngs::OsRng;
/// # /// #
/// # fn do_test() -> Result<Signature, SignatureError> { /// # fn do_test() -> Result<Signature, SignatureError> {
/// # let mut csprng = OsRng{}; /// # let mut csprng = OsRng;
/// # let signing_key: SigningKey = SigningKey::generate(&mut csprng); /// # let signing_key: SigningKey = SigningKey::generate(&mut csprng);
/// # let message: &[u8] = b"All I want is to pet all of the dogs."; /// # let message: &[u8] = b"All I want is to pet all of the dogs.";
/// # let mut prehashed: Sha512 = Sha512::new(); /// # let mut prehashed: Sha512 = Sha512::new();
@ -348,7 +363,7 @@ impl SigningKey {
/// use rand::rngs::OsRng; /// use rand::rngs::OsRng;
/// ///
/// # fn do_test() -> Result<(), SignatureError> { /// # fn do_test() -> Result<(), SignatureError> {
/// let mut csprng = OsRng{}; /// let mut csprng = OsRng;
/// let signing_key: SigningKey = SigningKey::generate(&mut csprng); /// let signing_key: SigningKey = SigningKey::generate(&mut csprng);
/// let message: &[u8] = b"All I want is to pet all of the dogs."; /// 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`]. /// 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")] #[cfg(feature = "digest")]
impl<D> DigestSigner<D, Signature> for SigningKey impl<D> DigestSigner<D, Signature> for SigningKey
where 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 { impl Verifier<Signature> for SigningKey {
/// Verify a signature on a message with this signing key's public key. /// Verify a signature on a message with this signing key's public key.
fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> { fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {

View file

@ -38,6 +38,7 @@ use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes};
use signature::DigestVerifier; use signature::DigestVerifier;
use crate::constants::*; use crate::constants::*;
use crate::context::Context;
use crate::errors::*; use crate::errors::*;
use crate::signature::*; use crate::signature::*;
use crate::signing::*; use crate::signing::*;
@ -153,6 +154,15 @@ impl VerifyingKey {
Ok(VerifyingKey(compressed, point)) 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 /// Internal utility function for clamping a scalar representation and multiplying by the
/// basepont to produce a public key. /// basepont to produce a public key.
fn clamp_and_mul_base(bits: [u8; 32]) -> VerifyingKey { 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 { impl TryFrom<&[u8]> for VerifyingKey {
type Error = SignatureError; type Error = SignatureError;