mirror of
https://github.com/saymrwulf/curve25519-dalek-source.git
synced 2026-09-04 20:24:10 +00:00
Add hazmat module with ExpandedSecretKey, raw_sign, raw_sign_prehashed (#299)
* Added raw_sign() and raw_sign_prehashed() functions * Renamed `nonce` to `hash_prefix` in signing because it's really not a nonce * Moved raw signing to hazmat module * impl From<EdwardsPoint> for VerifyingKey * Brought back ExpandedSecretKey; made raw_* functions take it as input * Added remaining features to docs.rs feature set * Removed redundant ExpandedSecretKey def; made raw signing use a generic CtxDigest * Implemented raw_verify with generic CtxDigest * Implemented raw_verify_prehashed with generic MsgDigest and CtxDigest * Wrote hazmat tests; fixed errors; switched ordering of MsgDigest and CtxDigest * Updated changelog * ExpandedSecretKey::from_bytes takes an array and is now infallible * Add TODO comment for split_array_ref * Added from_slice and TryFrom<&[u8]> for ExpandedSecretKey --------- Co-authored-by: Tony Arcieri <bascule@gmail.com>
This commit is contained in:
parent
90f10ed096
commit
4afbf09e1c
8 changed files with 526 additions and 171 deletions
|
|
@ -22,6 +22,7 @@ Entries are listed in reverse chronological order per undeprecated major series.
|
|||
* Make all batch verification deterministic remove `batch_deterministic` ([#256](https://github.com/dalek-cryptography/ed25519-dalek/pull/256))
|
||||
* Remove `ExpandedSecretKey` API ((#205)[https://github.com/dalek-cryptography/ed25519-dalek/pull/205])
|
||||
* Rename `Keypair` → `SigningKey` and `PublicKey` → `VerifyingKey`
|
||||
* Make `hazmat` feature to expose, `ExpandedSecretKey`, `raw_sign()`, `raw_sign_prehashed()`, `raw_verify()`, and `raw_verify_prehashed()`
|
||||
|
||||
### Other changes
|
||||
|
||||
|
|
|
|||
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -46,6 +46,15 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.3"
|
||||
|
|
@ -272,6 +281,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
|||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -290,6 +300,7 @@ name = "ed25519-dalek"
|
|||
version = "2.0.0-rc.2"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"blake2",
|
||||
"criterion",
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
|
|
@ -301,6 +312,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"signature",
|
||||
"toml",
|
||||
"zeroize",
|
||||
|
|
@ -736,6 +748,16 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha3"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.0.0"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ rustdoc-args = [
|
|||
"--html-in-header", "docs/assets/rustdoc-include-katex-header.html",
|
||||
"--cfg", "docsrs",
|
||||
]
|
||||
features = ["batch", "pkcs8"]
|
||||
features = ["batch", "digest", "hazmat", "pem", "serde"]
|
||||
|
||||
[dependencies]
|
||||
curve25519-dalek = { version = "=4.0.0-rc.2", default-features = false, features = ["digest"] }
|
||||
|
|
@ -38,6 +38,8 @@ zeroize = { version = "1.5", default-features = false, optional = true }
|
|||
|
||||
[dev-dependencies]
|
||||
curve25519-dalek = { version = "=4.0.0-rc.2", default-features = false, features = ["digest", "rand_core"] }
|
||||
blake2 = "0.10"
|
||||
sha3 = "0.10"
|
||||
hex = "0.4"
|
||||
bincode = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
|
@ -62,7 +64,9 @@ asm = ["sha2/asm"]
|
|||
batch = ["alloc", "merlin", "rand_core"]
|
||||
fast = ["curve25519-dalek/precomputed-tables"]
|
||||
digest = ["signature/digest"]
|
||||
# This features turns off stricter checking for scalar malleability in signatures
|
||||
# Exposes the hazmat module
|
||||
hazmat = []
|
||||
# Turns off stricter checking for scalar malleability in signatures
|
||||
legacy_compatibility = []
|
||||
pkcs8 = ["ed25519/pkcs8"]
|
||||
pem = ["alloc", "ed25519/pem", "pkcs8"]
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ This crate is `#[no_std]` compatible with `default-features = false`.
|
|||
| `pkcs8` | | Enables [PKCS#8](https://en.wikipedia.org/wiki/PKCS_8) serialization/deserialization for `SigningKey` and `VerifyingKey` |
|
||||
| `pem` | | Enables PEM serialization support for PKCS#8 private keys and SPKI public keys. Also enables `alloc`. |
|
||||
| `legacy_compatibility` | | **Unsafe:** Disables certain signature checks. See [below](#malleability-and-the-legacy_compatibility-feature) |
|
||||
| `hazmat` | | **Unsafe:** Exposes the `hazmat` module for raw signing/verifying. Misuse of these functions will expose the private key, as in the [signing oracle attack](https://github.com/MystenLabs/ed25519-unsafe-libs). |
|
||||
|
||||
# Major Changes
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ See [CHANGELOG.md](CHANGELOG.md) for a list of changes made in past version of t
|
|||
* Make all batch verification deterministic remove `batch_deterministic` ([#256](https://github.com/dalek-cryptography/ed25519-dalek/pull/256))
|
||||
* Remove `ExpandedSecretKey` API ((#205)[https://github.com/dalek-cryptography/ed25519-dalek/pull/205])
|
||||
* Rename `Keypair` → `SigningKey` and `PublicKey` → `VerifyingKey`
|
||||
* Make `hazmat` feature to expose, `ExpandedSecretKey`, `raw_sign()`, `raw_sign_prehashed()`, `raw_verify()`, and `raw_verify_prehashed()`
|
||||
|
||||
# Documentation
|
||||
|
||||
|
|
|
|||
280
src/hazmat.rs
Normal file
280
src/hazmat.rs
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
//! Low-level interfaces to ed25519 functions
|
||||
//!
|
||||
//! # ⚠️ Warning: Hazmat
|
||||
//!
|
||||
//! These primitives are easy-to-misuse low-level interfaces.
|
||||
//!
|
||||
//! If you are an end user / non-expert in cryptography, **do not use any of these functions**.
|
||||
//! Failure to use them correctly can lead to catastrophic failures including **full private key
|
||||
//! recovery.**
|
||||
|
||||
// Permit dead code because 1) this module is only public when the `hazmat` feature is set, and 2)
|
||||
// even without `hazmat` we still need this module because this is where `ExpandedSecretKey` is
|
||||
// defined.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::{InternalError, SignatureError};
|
||||
|
||||
use curve25519_dalek::Scalar;
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
// These are used in the functions that are made public when the hazmat feature is set
|
||||
use crate::{Signature, VerifyingKey};
|
||||
use curve25519_dalek::digest::{generic_array::typenum::U64, Digest};
|
||||
|
||||
/// Contains the secret scalar and domain separator used for generating signatures.
|
||||
///
|
||||
/// This is used internally for signing.
|
||||
///
|
||||
/// In the usual Ed25519 signing algorithm, `scalar` and `hash_prefix` are defined such that
|
||||
/// `scalar || hash_prefix = H(sk)` where `sk` is the signing key and `H` is SHA-512.
|
||||
/// **WARNING:** Deriving the values for these fields in any other way can lead to full key
|
||||
/// recovery, as documented in [`raw_sign`] and [`raw_sign_prehashed`].
|
||||
///
|
||||
/// Instances of this secret are automatically overwritten with zeroes when they fall out of scope.
|
||||
pub struct ExpandedSecretKey {
|
||||
/// The secret scalar used for signing
|
||||
pub scalar: Scalar,
|
||||
/// The domain separator used when hashing the message to generate the pseudorandom `r` value
|
||||
pub hash_prefix: [u8; 32],
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl Drop for ExpandedSecretKey {
|
||||
fn drop(&mut self) {
|
||||
self.scalar.zeroize();
|
||||
self.hash_prefix.zeroize()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl ZeroizeOnDrop for ExpandedSecretKey {}
|
||||
|
||||
// Some conversion methods for `ExpandedSecretKey`. The signing methods are defined in
|
||||
// `signing.rs`, since we need them even when `not(feature = "hazmat")`
|
||||
impl ExpandedSecretKey {
|
||||
/// Convert this `ExpandedSecretKey` into an array of 64 bytes.
|
||||
pub fn to_bytes(&self) -> [u8; 64] {
|
||||
let mut bytes: [u8; 64] = [0u8; 64];
|
||||
|
||||
bytes[..32].copy_from_slice(self.scalar.as_bytes());
|
||||
bytes[32..].copy_from_slice(&self.hash_prefix[..]);
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Construct an `ExpandedSecretKey` from an array of 64 bytes.
|
||||
pub fn from_bytes(bytes: &[u8; 64]) -> Self {
|
||||
// TODO: Use bytes.split_array_ref once it’s in MSRV.
|
||||
let mut lower: [u8; 32] = [0u8; 32];
|
||||
let mut upper: [u8; 32] = [0u8; 32];
|
||||
|
||||
lower.copy_from_slice(&bytes[00..32]);
|
||||
upper.copy_from_slice(&bytes[32..64]);
|
||||
|
||||
ExpandedSecretKey {
|
||||
scalar: Scalar::from_bytes_mod_order(lower),
|
||||
hash_prefix: upper,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct an `ExpandedSecretKey` from a slice of 64 bytes.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Result` whose okay value is an EdDSA `ExpandedSecretKey` or whose error value is an
|
||||
/// `SignatureError` describing the error that occurred, namely that the given slice's length
|
||||
/// is not 64.
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn from_slice(bytes: &[u8]) -> Result<Self, SignatureError> {
|
||||
if bytes.len() != 64 {
|
||||
Err(InternalError::BytesLength {
|
||||
name: "ExpandedSecretKey",
|
||||
length: 64,
|
||||
}
|
||||
.into())
|
||||
} else {
|
||||
// If the input is 64 bytes long, coerce it to a 64-byte array
|
||||
Ok(Self::from_bytes(bytes.try_into().unwrap()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for ExpandedSecretKey {
|
||||
type Error = SignatureError;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
Self::from_slice(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute an ordinary Ed25519 signature over the given message. `CtxDigest` is the digest used to
|
||||
/// calculate the pseudorandomness needed for signing. According to the Ed25519 spec, `CtxDigest =
|
||||
/// Sha512`.
|
||||
///
|
||||
/// # ⚠️ Unsafe
|
||||
///
|
||||
/// Do NOT use this function unless you absolutely must. Using the wrong values in
|
||||
/// `ExpandedSecretKey` can leak your signing key. See
|
||||
/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack.
|
||||
pub fn raw_sign<CtxDigest>(
|
||||
esk: &ExpandedSecretKey,
|
||||
message: &[u8],
|
||||
verifying_key: &VerifyingKey,
|
||||
) -> Signature
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
esk.raw_sign::<CtxDigest>(message, verifying_key)
|
||||
}
|
||||
|
||||
/// Compute a signature over the given prehashed message, the Ed25519ph algorithm defined in
|
||||
/// [RFC8032 §5.1][rfc8032]. `MsgDigest` is the digest function used to hash the signed message.
|
||||
/// `CtxDigest` is the digest function used to calculate the pseudorandomness needed for signing.
|
||||
/// According to the Ed25519 spec, `MsgDigest = CtxDigest = Sha512`.
|
||||
///
|
||||
/// # ⚠️ Unsafe
|
||||
//
|
||||
/// Do NOT use this function unless you absolutely must. Using the wrong values in
|
||||
/// `ExpandedSecretKey` can leak your signing key. See
|
||||
/// [here](https://github.com/MystenLabs/ed25519-unsafe-libs) for more details on this attack.
|
||||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `esk` is the [`ExpandedSecretKey`] being used for signing
|
||||
/// * `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.
|
||||
/// * `verifying_key` is a [`VerifyingKey`] which corresponds to this secret key.
|
||||
/// * `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.
|
||||
///
|
||||
/// `scalar` and `hash_prefix` are usually selected such that `scalar || hash_prefix = H(sk)` where
|
||||
/// `sk` is the signing key
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Result` whose `Ok` value is an Ed25519ph [`Signature`] on the
|
||||
/// `prehashed_message` if the context was 255 bytes or less, otherwise
|
||||
/// a `SignatureError`.
|
||||
///
|
||||
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn raw_sign_prehashed<'a, CtxDigest, MsgDigest>(
|
||||
esk: &ExpandedSecretKey,
|
||||
prehashed_message: MsgDigest,
|
||||
verifying_key: &VerifyingKey,
|
||||
context: Option<&'a [u8]>,
|
||||
) -> Result<Signature, SignatureError>
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
esk.raw_sign_prehashed::<CtxDigest, MsgDigest>(prehashed_message, verifying_key, context)
|
||||
}
|
||||
|
||||
/// The ordinary non-batched Ed25519 verification check, rejecting non-canonical R
|
||||
/// values.`CtxDigest` is the digest used to calculate the pseudorandomness needed for signing.
|
||||
/// According to the Ed25519 spec, `CtxDigest = Sha512`.
|
||||
pub fn raw_verify<CtxDigest>(
|
||||
vk: &VerifyingKey,
|
||||
message: &[u8],
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
vk.raw_verify::<CtxDigest>(message, signature)
|
||||
}
|
||||
|
||||
/// The batched Ed25519 verification check, rejecting non-canonical R values. `MsgDigest` is the
|
||||
/// digest used to hash the signed message. `CtxDigest` is the digest used to calculate the
|
||||
/// pseudorandomness needed for signing. According to the Ed25519 spec, `MsgDigest = CtxDigest =
|
||||
/// Sha512`.
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn raw_verify_prehashed<CtxDigest, MsgDigest>(
|
||||
vk: &VerifyingKey,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
vk.raw_verify_prehashed::<CtxDigest, MsgDigest>(prehashed_message, context, signature)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use curve25519_dalek::Scalar;
|
||||
use rand::{rngs::OsRng, CryptoRng, RngCore};
|
||||
|
||||
// Pick distinct, non-spec 512-bit hash functions for message and sig-context hashing
|
||||
type CtxDigest = blake2::Blake2b512;
|
||||
type MsgDigest = sha3::Sha3_512;
|
||||
|
||||
impl ExpandedSecretKey {
|
||||
// Make a random expanded secret key for testing purposes. This is NOT how you generate
|
||||
// expanded secret keys IRL. They're the hash of a seed.
|
||||
fn random<R: RngCore + CryptoRng>(mut rng: R) -> Self {
|
||||
// The usual signing algorithm clamps its scalars
|
||||
let scalar_bytes = [0u8; 32];
|
||||
let scalar = Scalar::from_bits_clamped(scalar_bytes);
|
||||
|
||||
let mut hash_prefix = [0u8; 32];
|
||||
rng.fill_bytes(&mut hash_prefix);
|
||||
|
||||
ExpandedSecretKey {
|
||||
scalar,
|
||||
hash_prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that raw_sign and raw_verify work when a non-spec CtxDigest is used
|
||||
#[test]
|
||||
fn sign_verify_nonspec() {
|
||||
// Generate the keypair
|
||||
let mut rng = OsRng;
|
||||
let esk = ExpandedSecretKey::random(&mut rng);
|
||||
let vk = VerifyingKey::from(&esk);
|
||||
|
||||
let msg = b"Then one day, a piano fell on my head";
|
||||
|
||||
// Sign and verify
|
||||
let sig = raw_sign::<CtxDigest>(&esk, msg, &vk);
|
||||
raw_verify::<CtxDigest>(&vk, msg, &sig).unwrap();
|
||||
}
|
||||
|
||||
// Check that raw_sign_prehashed and raw_verify_prehashed work when distinct, non-spec
|
||||
// MsgDigest and CtxDigest are used
|
||||
#[cfg(feature = "digest")]
|
||||
#[test]
|
||||
fn sign_verify_prehashed_nonspec() {
|
||||
use curve25519_dalek::digest::Digest;
|
||||
|
||||
// Generate the keypair
|
||||
let mut rng = OsRng;
|
||||
let esk = ExpandedSecretKey::random(&mut rng);
|
||||
let vk = VerifyingKey::from(&esk);
|
||||
|
||||
// Hash the message
|
||||
let msg = b"And then I got trampled by a herd of buffalo";
|
||||
let mut h = MsgDigest::new();
|
||||
h.update(msg);
|
||||
|
||||
let ctx_str = &b"consequences"[..];
|
||||
|
||||
// Sign and verify prehashed
|
||||
let sig = raw_sign_prehashed::<CtxDigest, MsgDigest>(&esk, h.clone(), &vk, Some(ctx_str))
|
||||
.unwrap();
|
||||
raw_verify_prehashed::<CtxDigest, MsgDigest>(&vk, h, Some(ctx_str), &sig).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -264,6 +264,11 @@ mod signature;
|
|||
mod signing;
|
||||
mod verifying;
|
||||
|
||||
#[cfg(feature = "hazmat")]
|
||||
pub mod hazmat;
|
||||
#[cfg(not(feature = "hazmat"))]
|
||||
mod hazmat;
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
pub use curve25519_dalek::digest::Digest;
|
||||
#[cfg(feature = "digest")]
|
||||
|
|
|
|||
188
src/signing.rs
188
src/signing.rs
|
|
@ -20,12 +20,11 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||
|
||||
use sha2::Sha512;
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
use curve25519_dalek::digest::generic_array::typenum::U64;
|
||||
use curve25519_dalek::digest::Digest;
|
||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use curve25519_dalek::{
|
||||
digest::{generic_array::typenum::U64, Digest},
|
||||
edwards::{CompressedEdwardsY, EdwardsPoint},
|
||||
scalar::Scalar,
|
||||
};
|
||||
|
||||
use ed25519::signature::{KeypairRef, Signer, Verifier};
|
||||
|
||||
|
|
@ -37,11 +36,14 @@ use signature::DigestSigner;
|
|||
#[cfg(feature = "zeroize")]
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::errors::*;
|
||||
use crate::signature::*;
|
||||
use crate::verifying::*;
|
||||
use crate::Signature;
|
||||
use crate::{
|
||||
constants::{KEYPAIR_LENGTH, SECRET_KEY_LENGTH},
|
||||
errors::{InternalError, SignatureError},
|
||||
hazmat::ExpandedSecretKey,
|
||||
signature::InternalSignature,
|
||||
verifying::VerifyingKey,
|
||||
Signature,
|
||||
};
|
||||
|
||||
/// ed25519 secret key as defined in [RFC8032 § 5.1.5]:
|
||||
///
|
||||
|
|
@ -202,7 +204,9 @@ impl SigningKey {
|
|||
///
|
||||
/// # Inputs
|
||||
///
|
||||
/// * `prehashed_message` is an instantiated SHA-512 digest of the message
|
||||
/// * `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.
|
||||
/// * `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.
|
||||
|
|
@ -213,10 +217,10 @@ impl SigningKey {
|
|||
///
|
||||
/// # 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.
|
||||
/// The RFC only permits SHA-512 to be used for prehashing, i.e., `MsgDigest = Sha512`. 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
|
||||
///
|
||||
|
|
@ -297,15 +301,15 @@ impl SigningKey {
|
|||
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
|
||||
/// [terrible_idea]: https://github.com/isislovecruft/scripts/blob/master/gpgkey2bc.py
|
||||
#[cfg(feature = "digest")]
|
||||
pub fn sign_prehashed<D>(
|
||||
pub fn sign_prehashed<MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: D,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
) -> Result<Signature, SignatureError>
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
ExpandedSecretKey::from(&self.secret_key).sign_prehashed(
|
||||
ExpandedSecretKey::from(&self.secret_key).raw_sign_prehashed::<Sha512, MsgDigest>(
|
||||
prehashed_message,
|
||||
&self.verifying_key,
|
||||
context,
|
||||
|
|
@ -334,6 +338,13 @@ impl SigningKey {
|
|||
/// Returns `true` if the `signature` was a valid signature created by this
|
||||
/// [`SigningKey`] on the `prehashed_message`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing, i.e., `MsgDigest = Sha512`. 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 = "```")]
|
||||
|
|
@ -378,14 +389,14 @@ impl SigningKey {
|
|||
///
|
||||
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
|
||||
#[cfg(feature = "digest")]
|
||||
pub fn verify_prehashed<D>(
|
||||
pub fn verify_prehashed<MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: D,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
self.verifying_key
|
||||
.verify_prehashed(prehashed_message, context, signature)
|
||||
|
|
@ -485,7 +496,7 @@ impl Signer<Signature> for SigningKey {
|
|||
/// Sign a message with this signing key's secret key.
|
||||
fn try_sign(&self, message: &[u8]) -> Result<Signature, SignatureError> {
|
||||
let expanded: ExpandedSecretKey = (&self.secret_key).into();
|
||||
Ok(expanded.sign(message, &self.verifying_key))
|
||||
Ok(expanded.raw_sign::<Sha512>(message, &self.verifying_key))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -697,57 +708,9 @@ impl<'d> Deserialize<'d> for SigningKey {
|
|||
}
|
||||
}
|
||||
|
||||
/// An "expanded" secret key.
|
||||
///
|
||||
/// This is produced by using an hash function with 512-bits output to digest a
|
||||
/// `SecretKey`. The output digest is then split in half, the lower half being
|
||||
/// the actual `key` used to sign messages, after twiddling with some bits.¹ The
|
||||
/// upper half is used a sort of half-baked, ill-designed² pseudo-domain-separation
|
||||
/// "nonce"-like thing, which is used during signature production by
|
||||
/// concatenating it with the message to be signed before the message is hashed.
|
||||
///
|
||||
/// Instances of this secret are automatically overwritten with zeroes when they
|
||||
/// fall out of scope.
|
||||
//
|
||||
// ¹ This results in a slight bias towards non-uniformity at one spectrum of
|
||||
// the range of valid keys. Oh well: not my idea; not my problem.
|
||||
//
|
||||
// ² It is the author's view (specifically, isis agora lovecruft, in the event
|
||||
// you'd like to complain about me, again) that this is "ill-designed" because
|
||||
// this doesn't actually provide true hash domain separation, in that in many
|
||||
// real-world applications a user wishes to have one key which is used in
|
||||
// several contexts (such as within tor, which does domain separation
|
||||
// manually by pre-concatenating static strings to messages to achieve more
|
||||
// robust domain separation). In other real-world applications, such as
|
||||
// bitcoind, a user might wish to have one master keypair from which others are
|
||||
// derived (à la BIP32) and different domain separators between keys derived at
|
||||
// different levels (and similarly for tree-based key derivation constructions,
|
||||
// such as hash-based signatures). Leaving the domain separation to
|
||||
// application designers, who thus far have produced incompatible,
|
||||
// slightly-differing, ad hoc domain separation (at least those application
|
||||
// designers who knew enough cryptographic theory to do so!), is therefore a
|
||||
// bad design choice on the part of the cryptographer designing primitives
|
||||
// which should be simple and as foolproof as possible to use for
|
||||
// non-cryptographers. Further, later in the ed25519 signature scheme, as
|
||||
// specified in RFC8032, the public key is added into *another* hash digest
|
||||
// (along with the message, again); it is unclear to this author why there's
|
||||
// not only one but two poorly-thought-out attempts at domain separation in the
|
||||
// same signature scheme, and which both fail in exactly the same way. For a
|
||||
// better-designed, Schnorr-based signature scheme, see Trevor Perrin's work on
|
||||
// "generalised EdDSA" and "VXEdDSA".
|
||||
pub(crate) struct ExpandedSecretKey {
|
||||
pub(crate) scalar: Scalar,
|
||||
pub(crate) nonce: [u8; 32],
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl Drop for ExpandedSecretKey {
|
||||
fn drop(&mut self) {
|
||||
self.scalar.zeroize();
|
||||
self.nonce.zeroize()
|
||||
}
|
||||
}
|
||||
|
||||
/// The spec-compliant way to define an expanded secret key. This computes `SHA512(sk)`, clamps the
|
||||
/// first 32 bytes and uses it as a scalar, and uses the second 32 bytes as a domain separator for
|
||||
/// hashing.
|
||||
impl From<&SecretKey> for ExpandedSecretKey {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn from(secret_key: &SecretKey) -> ExpandedSecretKey {
|
||||
|
|
@ -758,24 +721,42 @@ impl From<&SecretKey> for ExpandedSecretKey {
|
|||
// The try_into here converts to fixed-size array
|
||||
ExpandedSecretKey {
|
||||
scalar: Scalar::from_bits_clamped(lower.try_into().unwrap()),
|
||||
nonce: upper.try_into().unwrap(),
|
||||
hash_prefix: upper.try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExpandedSecretKey {
|
||||
/// Sign a message with this `ExpandedSecretKey`.
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn sign(&self, message: &[u8], verifying_key: &VerifyingKey) -> Signature {
|
||||
let mut h: Sha512 = Sha512::new();
|
||||
//
|
||||
// Signing functions. These are pub(crate) so that the `hazmat` module can use them
|
||||
//
|
||||
|
||||
h.update(self.nonce);
|
||||
impl ExpandedSecretKey {
|
||||
/// The plain, non-prehashed, signing function for Ed25519. `CtxDigest` is the digest used to
|
||||
/// calculate the pseudorandomness needed for signing. According to the spec, `CtxDigest =
|
||||
/// Sha512`, and `self` is derived via the method defined in `impl From<&SigningKey> for
|
||||
/// ExpandedSecretKey`.
|
||||
///
|
||||
/// This definition is loose in its parameters so that end-users of the `hazmat` module can
|
||||
/// change how the `ExpandedSecretKey` is calculated and which hash function to use.
|
||||
#[allow(non_snake_case)]
|
||||
#[inline(always)]
|
||||
pub(crate) fn raw_sign<CtxDigest>(
|
||||
&self,
|
||||
message: &[u8],
|
||||
verifying_key: &VerifyingKey,
|
||||
) -> Signature
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let mut h = CtxDigest::new();
|
||||
|
||||
h.update(self.hash_prefix);
|
||||
h.update(message);
|
||||
|
||||
let r = Scalar::from_hash(h);
|
||||
let R: CompressedEdwardsY = EdwardsPoint::mul_base(&r).compress();
|
||||
|
||||
h = Sha512::new();
|
||||
h = CtxDigest::new();
|
||||
h.update(R.as_bytes());
|
||||
h.update(verifying_key.as_bytes());
|
||||
h.update(message);
|
||||
|
|
@ -786,38 +767,27 @@ impl ExpandedSecretKey {
|
|||
InternalSignature { R, s }.into()
|
||||
}
|
||||
|
||||
/// Sign a `prehashed_message` with this `ExpandedSecretKey` using the
|
||||
/// Ed25519ph algorithm defined in [RFC8032 §5.1][rfc8032].
|
||||
/// The prehashed signing function for Ed25519 (i.e., Ed25519ph). `CtxDigest` is the digest
|
||||
/// function used to calculate the pseudorandomness needed for signing. `MsgDigest` is the
|
||||
/// digest function used to hash the signed message. According to the spec, `MsgDigest =
|
||||
/// CtxDigest = Sha512`, and `self` is derived via the method defined in `impl
|
||||
/// From<&SigningKey> for ExpandedSecretKey`.
|
||||
///
|
||||
/// # 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.
|
||||
/// * `verifying_key` is a [`VerifyingKey`] which corresponds to this secret key.
|
||||
/// * `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.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Result` whose `Ok` value is an Ed25519ph [`Signature`] on the
|
||||
/// `prehashed_message` if the context was 255 bytes or less, otherwise
|
||||
/// a `SignatureError`.
|
||||
///
|
||||
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
|
||||
/// This definition is loose in its parameters so that end-users of the `hazmat` module can
|
||||
/// change how the `ExpandedSecretKey` is calculated and which `CtxDigest` function to use.
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn sign_prehashed<'a, D>(
|
||||
#[inline(always)]
|
||||
pub(crate) fn raw_sign_prehashed<'a, CtxDigest, MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: D,
|
||||
prehashed_message: MsgDigest,
|
||||
verifying_key: &VerifyingKey,
|
||||
context: Option<&'a [u8]>,
|
||||
) -> Result<Signature, SignatureError>
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let mut h: Sha512;
|
||||
let mut prehash: [u8; 64] = [0u8; 64];
|
||||
|
||||
let ctx: &[u8] = context.unwrap_or(b""); // By default, the context is an empty string.
|
||||
|
|
@ -843,18 +813,18 @@ impl ExpandedSecretKey {
|
|||
//
|
||||
// This is a really fucking stupid bandaid, and the damned scheme is
|
||||
// still bleeding from malleability, for fuck's sake.
|
||||
h = Sha512::new()
|
||||
let mut h = CtxDigest::new()
|
||||
.chain_update(b"SigEd25519 no Ed25519 collisions")
|
||||
.chain_update([1]) // Ed25519ph
|
||||
.chain_update([ctx_len])
|
||||
.chain_update(ctx)
|
||||
.chain_update(self.nonce)
|
||||
.chain_update(self.hash_prefix)
|
||||
.chain_update(&prehash[..]);
|
||||
|
||||
let r = Scalar::from_hash(h);
|
||||
let R: CompressedEdwardsY = EdwardsPoint::mul_base(&r).compress();
|
||||
|
||||
h = Sha512::new()
|
||||
h = CtxDigest::new()
|
||||
.chain_update(b"SigEd25519 no Ed25519 collisions")
|
||||
.chain_update([1]) // Ed25519ph
|
||||
.chain_update([ctx_len])
|
||||
|
|
|
|||
191
src/verifying.rs
191
src/verifying.rs
|
|
@ -13,13 +13,12 @@ use core::convert::TryFrom;
|
|||
use core::fmt::Debug;
|
||||
use core::hash::{Hash, Hasher};
|
||||
|
||||
#[cfg(feature = "digest")]
|
||||
use curve25519_dalek::digest::generic_array::typenum::U64;
|
||||
use curve25519_dalek::digest::Digest;
|
||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||
use curve25519_dalek::edwards::EdwardsPoint;
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
use curve25519_dalek::{
|
||||
digest::{generic_array::typenum::U64, Digest},
|
||||
edwards::{CompressedEdwardsY, EdwardsPoint},
|
||||
montgomery::MontgomeryPoint,
|
||||
scalar::Scalar,
|
||||
};
|
||||
|
||||
use ed25519::signature::Verifier;
|
||||
|
||||
|
|
@ -36,10 +35,13 @@ use crate::context::Context;
|
|||
#[cfg(feature = "digest")]
|
||||
use signature::DigestVerifier;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::errors::*;
|
||||
use crate::signature::*;
|
||||
use crate::signing::*;
|
||||
use crate::{
|
||||
constants::PUBLIC_KEY_LENGTH,
|
||||
errors::{InternalError, SignatureError},
|
||||
hazmat::ExpandedSecretKey,
|
||||
signature::InternalSignature,
|
||||
signing::SigningKey,
|
||||
};
|
||||
|
||||
/// An ed25519 public key.
|
||||
///
|
||||
|
|
@ -100,6 +102,15 @@ impl From<&SigningKey> for VerifyingKey {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<EdwardsPoint> for VerifyingKey {
|
||||
fn from(point: EdwardsPoint) -> VerifyingKey {
|
||||
VerifyingKey {
|
||||
point,
|
||||
compressed: point.compress(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VerifyingKey {
|
||||
/// Convert this public key to a byte array.
|
||||
#[inline]
|
||||
|
|
@ -188,16 +199,20 @@ impl VerifyingKey {
|
|||
VerifyingKey { compressed, point }
|
||||
}
|
||||
|
||||
// A helper function that computes H(R || A || M). If `context.is_some()`, this does the
|
||||
// prehashed variant of the computation using its contents.
|
||||
// A helper function that computes `H(R || A || M)` where `H` is the 512-bit hash function
|
||||
// given by `CtxDigest` (this is SHA-512 in spec-compliant Ed25519). If `context.is_some()`,
|
||||
// this does the prehashed variant of the computation using its contents.
|
||||
#[allow(non_snake_case)]
|
||||
fn compute_challenge(
|
||||
fn compute_challenge<CtxDigest>(
|
||||
context: Option<&[u8]>,
|
||||
R: &CompressedEdwardsY,
|
||||
A: &CompressedEdwardsY,
|
||||
M: &[u8],
|
||||
) -> Scalar {
|
||||
let mut h = Sha512::new();
|
||||
) -> Scalar
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let mut h = CtxDigest::new();
|
||||
if let Some(c) = context {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
|
|
@ -219,18 +234,83 @@ impl VerifyingKey {
|
|||
// See the validation criteria blog post for more details:
|
||||
// https://hdevalence.ca/blog/2020-10-04-its-25519am
|
||||
#[allow(non_snake_case)]
|
||||
fn recompute_r(
|
||||
fn recompute_R<CtxDigest>(
|
||||
&self,
|
||||
context: Option<&[u8]>,
|
||||
signature: &InternalSignature,
|
||||
M: &[u8],
|
||||
) -> CompressedEdwardsY {
|
||||
let k = Self::compute_challenge(context, &signature.R, &self.compressed, M);
|
||||
) -> CompressedEdwardsY
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let k = Self::compute_challenge::<CtxDigest>(context, &signature.R, &self.compressed, M);
|
||||
let minus_A: EdwardsPoint = -self.point;
|
||||
// Recall the (non-batched) verification equation: -[k]A + [s]B = R
|
||||
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s).compress()
|
||||
}
|
||||
|
||||
/// The ordinary non-batched Ed25519 verification check, rejecting non-canonical R values. (see
|
||||
/// [`Self::recompute_R`]). `CtxDigest` is the digest used to calculate the pseudorandomness
|
||||
/// needed for signing. According to the spec, `CtxDigest = Sha512`.
|
||||
///
|
||||
/// This definition is loose in its parameters so that end-users of the `hazmat` module can
|
||||
/// change how the `ExpandedSecretKey` is calculated and which hash function to use.
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn raw_verify<CtxDigest>(
|
||||
&self,
|
||||
message: &[u8],
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let signature = InternalSignature::try_from(signature)?;
|
||||
|
||||
let expected_R = self.recompute_R::<CtxDigest>(None, &signature, message);
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InternalError::Verify.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// The prehashed non-batched Ed25519 verification check, rejecting non-canonical R values.
|
||||
/// (see [`Self::recompute_R`]). `CtxDigest` is the digest used to calculate the
|
||||
/// pseudorandomness needed for signing. `MsgDigest` is the digest used to hash the signed
|
||||
/// message. According to the spec, `MsgDigest = CtxDigest = Sha512`.
|
||||
///
|
||||
/// This definition is loose in its parameters so that end-users of the `hazmat` module can
|
||||
/// change how the `ExpandedSecretKey` is calculated and which hash function to use.
|
||||
#[cfg(feature = "digest")]
|
||||
#[allow(non_snake_case)]
|
||||
pub(crate) fn raw_verify_prehashed<CtxDigest, MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
CtxDigest: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let signature = InternalSignature::try_from(signature)?;
|
||||
|
||||
let ctx: &[u8] = context.unwrap_or(b"");
|
||||
debug_assert!(
|
||||
ctx.len() <= 255,
|
||||
"The context must not be longer than 255 octets."
|
||||
);
|
||||
|
||||
let message = prehashed_message.finalize();
|
||||
let expected_R = self.recompute_R::<CtxDigest>(Some(ctx), &signature, &message);
|
||||
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InternalError::Verify.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a `signature` on a `prehashed_message` using the Ed25519ph algorithm.
|
||||
///
|
||||
/// # Inputs
|
||||
|
|
@ -246,34 +326,26 @@ impl VerifyingKey {
|
|||
/// # Returns
|
||||
///
|
||||
/// Returns `true` if the `signature` was a valid signature created by this
|
||||
/// `Keypair` on the `prehashed_message`.
|
||||
/// [`SigningKey`] on the `prehashed_message`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing, i.e., `MsgDigest = Sha512`. 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")]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn verify_prehashed<D>(
|
||||
pub fn verify_prehashed<MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: D,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let signature = InternalSignature::try_from(signature)?;
|
||||
|
||||
let ctx: &[u8] = context.unwrap_or(b"");
|
||||
debug_assert!(
|
||||
ctx.len() <= 255,
|
||||
"The context must not be longer than 255 octets."
|
||||
);
|
||||
|
||||
let message = prehashed_message.finalize();
|
||||
let expected_R = self.recompute_r(Some(ctx), &signature, &message);
|
||||
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InternalError::Verify.into())
|
||||
}
|
||||
self.raw_verify_prehashed::<Sha512, MsgDigest>(prehashed_message, context, signature)
|
||||
}
|
||||
|
||||
/// Strictly verify a signature on a message with this keypair's public key.
|
||||
|
|
@ -356,7 +428,7 @@ impl VerifyingKey {
|
|||
return Err(InternalError::Verify.into());
|
||||
}
|
||||
|
||||
let expected_R = self.recompute_r(None, &signature, message);
|
||||
let expected_R = self.recompute_R::<Sha512>(None, &signature, message);
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
|
|
@ -380,17 +452,24 @@ impl VerifyingKey {
|
|||
/// # Returns
|
||||
///
|
||||
/// Returns `true` if the `signature` was a valid signature created by this
|
||||
/// `Keypair` on the `prehashed_message`.
|
||||
/// [`SigningKey`] on the `prehashed_message`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The RFC only permits SHA-512 to be used for prehashing, i.e., `MsgDigest = Sha512`. 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")]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn verify_prehashed_strict<D>(
|
||||
pub fn verify_prehashed_strict<MsgDigest>(
|
||||
&self,
|
||||
prehashed_message: D,
|
||||
prehashed_message: MsgDigest,
|
||||
context: Option<&[u8]>,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError>
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
let signature = InternalSignature::try_from(signature)?;
|
||||
|
||||
|
|
@ -411,7 +490,7 @@ impl VerifyingKey {
|
|||
}
|
||||
|
||||
let message = prehashed_message.finalize();
|
||||
let expected_R = self.recompute_r(Some(ctx), &signature, &message);
|
||||
let expected_R = self.recompute_R::<Sha512>(Some(ctx), &signature, &message);
|
||||
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
|
|
@ -442,28 +521,20 @@ impl Verifier<ed25519::Signature> for VerifyingKey {
|
|||
/// # Return
|
||||
///
|
||||
/// Returns `Ok(())` if the signature is valid, and `Err` otherwise.
|
||||
#[allow(non_snake_case)]
|
||||
fn verify(&self, message: &[u8], signature: &ed25519::Signature) -> Result<(), SignatureError> {
|
||||
let signature = InternalSignature::try_from(signature)?;
|
||||
|
||||
let expected_R = self.recompute_r(None, &signature, message);
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InternalError::Verify.into())
|
||||
}
|
||||
self.raw_verify::<Sha512>(message, signature)
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`VerifyingKey::verify_prehashed`] with `context` set to [`None`].
|
||||
#[cfg(feature = "digest")]
|
||||
impl<D> DigestVerifier<D, ed25519::Signature> for VerifyingKey
|
||||
impl<MsgDigest> DigestVerifier<MsgDigest, ed25519::Signature> for VerifyingKey
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
fn verify_digest(
|
||||
&self,
|
||||
msg_digest: D,
|
||||
msg_digest: MsgDigest,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError> {
|
||||
self.verify_prehashed(msg_digest, None, signature)
|
||||
|
|
@ -473,13 +544,13 @@ 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>
|
||||
impl<MsgDigest> DigestVerifier<MsgDigest, ed25519::Signature> for Context<'_, '_, VerifyingKey>
|
||||
where
|
||||
D: Digest<OutputSize = U64>,
|
||||
MsgDigest: Digest<OutputSize = U64>,
|
||||
{
|
||||
fn verify_digest(
|
||||
&self,
|
||||
msg_digest: D,
|
||||
msg_digest: MsgDigest,
|
||||
signature: &ed25519::Signature,
|
||||
) -> Result<(), SignatureError> {
|
||||
self.key()
|
||||
|
|
|
|||
Loading…
Reference in a new issue