Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: New facility for exposing "bundled signatures". #186

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 38 additions & 19 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,37 +38,56 @@ pub(crate) enum InternalError {
VerifyError,
/// Two arrays did not match in size, making the called signature
/// verification method impossible.
ArrayLengthError{ name_a: &'static str, length_a: usize,
name_b: &'static str, length_b: usize,
name_c: &'static str, length_c: usize, },
ArrayLengthError {
name_a: &'static str,
length_a: usize,
name_b: &'static str,
length_b: usize,
name_c: &'static str,
length_c: usize,
},
/// An ed25519ph signature can only take up to 255 octets of context.
PrehashedContextLengthError,
/// A public key did not match the key provided in a bundled signature.
WrongKeyError,
}

impl Display for InternalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
InternalError::PointDecompressionError
=> write!(f, "Cannot decompress Edwards point"),
InternalError::ScalarFormatError
=> write!(f, "Cannot use scalar with high-bit set"),
InternalError::BytesLengthError{ name: n, length: l}
=> write!(f, "{} must be {} bytes in length", n, l),
InternalError::VerifyError
=> write!(f, "Verification equation was not satisfied"),
InternalError::ArrayLengthError{ name_a: na, length_a: la,
name_b: nb, length_b: lb,
name_c: nc, length_c: lc, }
=> write!(f, "Arrays must be the same length: {} has length {},
{} has length {}, {} has length {}.", na, la, nb, lb, nc, lc),
InternalError::PrehashedContextLengthError
=> write!(f, "An ed25519ph signature can only take up to 255 octets of context"),
InternalError::PointDecompressionError => write!(f, "Cannot decompress Edwards point"),
InternalError::ScalarFormatError => write!(f, "Cannot use scalar with high-bit set"),
InternalError::BytesLengthError { name: n, length: l } => {
write!(f, "{} must be {} bytes in length", n, l)
}
InternalError::VerifyError => write!(f, "Verification equation was not satisfied"),
InternalError::ArrayLengthError {
name_a: na,
length_a: la,
name_b: nb,
length_b: lb,
name_c: nc,
length_c: lc,
} => write!(
f,
"Arrays must be the same length: {} has length {},
{} has length {}, {} has length {}.",
na, la, nb, lb, nc, lc
),
InternalError::PrehashedContextLengthError => write!(
f,
"An ed25519ph signature can only take up to 255 octets of context"
),
InternalError::WrongKeyError => write!(
f,
"Provided public key did not match key in bundled signature"
),
}
}
}

#[cfg(feature = "std")]
impl Error for InternalError { }
impl Error for InternalError {}

/// Errors which may occur while processing signatures and keypairs.
///
Expand Down
228 changes: 157 additions & 71 deletions src/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use serde::de::Error as SerdeError;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "serde")]
use serde_bytes::{Bytes as SerdeBytes, ByteBuf as SerdeByteBuf};
use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes};

use crate::constants::*;
use crate::errors::*;
Expand Down Expand Up @@ -131,7 +131,8 @@ impl PublicKey {
return Err(InternalError::BytesLengthError {
name: "PublicKey",
length: PUBLIC_KEY_LENGTH,
}.into());
}
.into());
}
let mut bits: [u8; 32] = [0u8; 32];
bits.copy_from_slice(&bytes[..32]);
Expand Down Expand Up @@ -160,6 +161,43 @@ impl PublicKey {
PublicKey(compressed, point)
}

/// Create a [`BundledSignature`] object containing the information needed
/// to validate a provided `signature` on a provided `prehashed_message` and
/// `context with this key.
///
/// Verifying the resulting `BundledSignature` object has the same behavior
/// as if you had called `verify_prehashed()`.
#[allow(non_snake_case)]
pub fn prepare_bundled_signature_prehashed<D>(
&self,
prehashed_message: D,
context: Option<&[u8]>,
signature: &ed25519::Signature,
) -> Result<BundledSignature, SignatureError>
where
D: Digest<OutputSize = U64>,
{
let signature = InternalSignature::try_from(signature)?;

let mut h: Sha512 = Sha512::default();

let ctx: &[u8] = context.unwrap_or(b"");
debug_assert!(
ctx.len() <= 255,
"The context must not be longer than 255 octets."
);

h.update(b"SigEd25519 no Ed25519 collisions");
h.update(&[1]); // Ed25519ph
h.update(&[ctx.len() as u8]);
h.update(ctx);
h.update(signature.R.as_bytes());
h.update(self.as_bytes());
h.update(prehashed_message.finalize().as_slice());

Ok(BundledSignature::new(*self, h, signature))
}

/// Verify a `signature` on a `prehashed_message` using the Ed25519ph algorithm.
///
/// # Inputs
Expand All @@ -178,7 +216,6 @@ impl PublicKey {
/// `Keypair` on the `prehashed_message`.
///
/// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1
#[allow(non_snake_case)]
pub fn verify_prehashed<D>(
&self,
prehashed_message: D,
Expand All @@ -188,33 +225,42 @@ impl PublicKey {
where
D: Digest<OutputSize = U64>,
{
self.prepare_bundled_signature_prehashed(prehashed_message, context, signature)?
.verify()
}

/// Create a [`BundledSignature`] object containing the information needed
/// to strictly validate a provided `signature` on a provided `message` with this
/// key.
///
/// Verifying the resulting `BundledSignature` object has the same behavior
/// as if you had called `verify_strict()`.
#[allow(non_snake_case)]
pub fn prepare_bundled_signature_strict(
&self,
message: &[u8],
signature: &ed25519::Signature,
) -> Result<BundledSignature, SignatureError> {
let signature = InternalSignature::try_from(signature)?;

let mut h: Sha512 = Sha512::default();
let R: EdwardsPoint;
let k: Scalar;
let mut h: Sha512 = Sha512::new();
let signature_R: EdwardsPoint;

let ctx: &[u8] = context.unwrap_or(b"");
debug_assert!(ctx.len() <= 255, "The context must not be longer than 255 octets.");
match signature.R.decompress() {
None => return Err(InternalError::VerifyError.into()),
Some(x) => signature_R = x,
}

let minus_A: EdwardsPoint = -self.1;
// Logical OR is fine here as we're not trying to be constant time.
if signature_R.is_small_order() || self.1.is_small_order() {
return Err(InternalError::VerifyError.into());
}

h.update(b"SigEd25519 no Ed25519 collisions");
h.update(&[1]); // Ed25519ph
h.update(&[ctx.len() as u8]);
h.update(ctx);
h.update(signature.R.as_bytes());
h.update(self.as_bytes());
h.update(prehashed_message.finalize().as_slice());

k = Scalar::from_hash(h);
R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s);
h.update(&message);

if R.compress() == signature.R {
Ok(())
} else {
Err(InternalError::VerifyError.into())
}
Ok(BundledSignature::new(*self, h, signature))
}

/// Strictly verify a signature on a message with this keypair's public key.
Expand Down Expand Up @@ -279,43 +325,46 @@ impl PublicKey {
/// # Return
///
/// Returns `Ok(())` if the signature is valid, and `Err` otherwise.
#[allow(non_snake_case)]
pub fn verify_strict(
&self,
message: &[u8],
signature: &ed25519::Signature,
) -> Result<(), SignatureError>
{
) -> Result<(), SignatureError> {
self.prepare_bundled_signature_strict(message, signature)?
.verify()
}

/// Create a [`BundledSignature`] object containing the information needed
/// to validate a provided `signature` on a provided `message` with this
/// key.
///
/// Verifying the resulting `BundledSignature` object has the same behavior
/// as if you had called `verify()`.
#[allow(non_snake_case)]
pub fn prepare_bundled_signature(
&self,
message: &[u8],
signature: &ed25519::Signature,
) -> Result<BundledSignature, SignatureError> {
let signature = InternalSignature::try_from(signature)?;

let mut h: Sha512 = Sha512::new();
let R: EdwardsPoint;
let k: Scalar;
let minus_A: EdwardsPoint = -self.1;
let signature_R: EdwardsPoint;

match signature.R.decompress() {
None => return Err(InternalError::VerifyError.into()),
Some(x) => signature_R = x,
}

// Logical OR is fine here as we're not trying to be constant time.
if signature_R.is_small_order() || self.1.is_small_order() {
return Err(InternalError::VerifyError.into());
}

h.update(signature.R.as_bytes());
h.update(self.as_bytes());
h.update(&message);

k = Scalar::from_hash(h);
R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s);
Ok(BundledSignature::new(*self, h, signature))
}

if R == signature_R {
Ok(())
} else {
Err(InternalError::VerifyError.into())
/// Check whether a bundled signature/message/key combination is valid, and
/// its key matches this key.
pub fn verify_bundled(&self, bundled: &BundledSignature) -> Result<(), SignatureError> {
if !bundled.matches_key(self) {
return Err(InternalError::WrongKeyError.into());
}

bundled.verify()
}
}

Expand All @@ -325,32 +374,8 @@ impl Verifier<ed25519::Signature> for PublicKey {
/// # 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 mut h: Sha512 = Sha512::new();
let R: EdwardsPoint;
let k: Scalar;
let minus_A: EdwardsPoint = -self.1;

h.update(signature.R.as_bytes());
h.update(self.as_bytes());
h.update(&message);

k = Scalar::from_hash(h);
R = EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s);

if R.compress() == signature.R {
Ok(())
} else {
Err(InternalError::VerifyError.into())
}
fn verify(&self, message: &[u8], signature: &ed25519::Signature) -> Result<(), SignatureError> {
self.prepare_bundled_signature(message, signature)?.verify()
}
}

Expand All @@ -374,3 +399,64 @@ impl<'d> Deserialize<'d> for PublicKey {
PublicKey::from_bytes(bytes.as_ref()).map_err(SerdeError::custom)
}
}

/// A `BundledSignature` contains a signature, plus the information from the
/// message and public key needed to check it.
///
/// Under some circumstances, you may want want to bundle the information needed
/// to verify a signature later on (in another thread, say). That's easy enough
/// if your protocol uses the "precomputed hash" variant of ed25519, but
/// otherwise it can become unwieldy (say, if your message is very large, and
/// passing it by reference is inconvenient).
///
/// Instead, you can use one of the `prepare_bundled_signature*()` functions
/// from [`PublicKey`] to create a (comparatively) small [`BundledSignature`].
/// The `BundledSignature` contains the public key, the signature, and the
/// necessary SHA512 hash that Ed25519 uses internally to verify that the
/// signature is correct for the given public key and message.
#[derive(Clone, Debug)]
pub struct BundledSignature {
public_key: PublicKey,
k: Scalar,
signature: InternalSignature,
}

impl BundledSignature {
/// Construct a new BundledSignature from its parts.
fn new(public_key: PublicKey, h: Sha512, signature: InternalSignature) -> Self {
let k = Scalar::from_hash(h);
Self {
public_key,
k,
signature,
}
}

/// Check whether this signature/message/key combination contains a valid
/// signature.
///
/// # Return
///
/// Returns `Ok(())` if the signature is valid, and `Err` otherwise.
#[allow(non_snake_case)]
#[inline]
pub fn verify(&self) -> Result<(), SignatureError> {
let minus_A: EdwardsPoint = -self.public_key.1;
let R = EdwardsPoint::vartime_double_scalar_mul_basepoint(
&self.k,
&(minus_A),
&self.signature.s,
);

if R.compress() == self.signature.R {
Ok(())
} else {
Err(InternalError::VerifyError.into())
}
}

/// Return true if this BundledSignature was constructed with the public key `key`.
pub fn matches_key(&self, key: &PublicKey) -> bool {
&self.public_key == key
}
}