Skip to content

Commit

Permalink
Replace recompute_R with a separate RCompute
Browse files Browse the repository at this point in the history
This struct can be use to implement verifiers with incremental updates
  • Loading branch information
mkj committed Dec 27, 2023
1 parent 0b45e00 commit 092eebb
Showing 1 changed file with 74 additions and 55 deletions.
129 changes: 74 additions & 55 deletions ed25519-dalek/src/verifying.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,58 +187,8 @@ impl VerifyingKey {
self.point.is_small_order()
}

// 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<CtxDigest>(
context: Option<&[u8]>,
R: &CompressedEdwardsY,
A: &CompressedEdwardsY,
M: &[u8],
) -> 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
h.update([c.len() as u8]);
h.update(c);
}
h.update(R.as_bytes());
h.update(A.as_bytes());
h.update(M);

Scalar::from_hash(h)
}

// Helper function for verification. Computes the _expected_ R component of the signature. The
// caller compares this to the real R component. If `context.is_some()`, this does the
// prehashed variant of the computation using its contents.
// Note that this returns the compressed form of R and the caller does a byte comparison. This
// means that all our verification functions do not accept non-canonically encoded R values.
// 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<CtxDigest>(
&self,
context: Option<&[u8]>,
signature: &InternalSignature,
M: &[u8],
) -> 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
/// [`Self::RCompute`]). `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
Expand All @@ -254,7 +204,7 @@ impl VerifyingKey {
{
let signature = InternalSignature::try_from(signature)?;

let expected_R = self.recompute_R::<CtxDigest>(None, &signature, message);
let expected_R = RCompute::<CtxDigest>::compute(self, signature, None, message);
if expected_R == signature.R {
Ok(())
} else {
Expand Down Expand Up @@ -290,7 +240,8 @@ impl VerifyingKey {
);

let message = prehashed_message.finalize();
let expected_R = self.recompute_R::<CtxDigest>(Some(ctx), &signature, &message);

let expected_R = RCompute::<CtxDigest>::compute(self, signature, Some(ctx), &message);

if expected_R == signature.R {
Ok(())
Expand Down Expand Up @@ -416,7 +367,7 @@ impl VerifyingKey {
return Err(InternalError::Verify.into());
}

let expected_R = self.recompute_R::<Sha512>(None, &signature, message);
let expected_R = RCompute::<Sha512>::compute(self, signature, None, message);
if expected_R == signature.R {
Ok(())
} else {
Expand Down Expand Up @@ -478,7 +429,7 @@ impl VerifyingKey {
}

let message = prehashed_message.finalize();
let expected_R = self.recompute_R::<Sha512>(Some(ctx), &signature, &message);
let expected_R = RCompute::<Sha512>::compute(self, signature, Some(ctx), &message);

if expected_R == signature.R {
Ok(())
Expand Down Expand Up @@ -507,6 +458,74 @@ impl VerifyingKey {
}
}

// Helper for verification. Computes the _expected_ R component of the signature. The
// caller compares this to the real R component.
// For prehashed variants a `h` with the context already included can be provided.
// Note that this returns the compressed form of R and the caller does a byte comparison. This
// means that all our verification functions do not accept non-canonically encoded R values.
// See the validation criteria blog post for more details:
// https://hdevalence.ca/blog/2020-10-04-its-25519am
pub(crate) struct RCompute<CtxDigest> {
key: VerifyingKey,
signature: InternalSignature,
h: CtxDigest,
}

#[allow(non_snake_case)]
impl<CtxDigest> RCompute<CtxDigest>
where
CtxDigest: Digest<OutputSize = U64>,
{
pub fn compute(
key: &VerifyingKey,
signature: InternalSignature,
prehash_ctx: Option<&[u8]>,
message: &[u8],
) -> CompressedEdwardsY {
let mut c = Self::new(key, signature, prehash_ctx);
c.update(message);
c.finish()
}

pub fn new(
key: &VerifyingKey,
signature: InternalSignature,
prehash_ctx: Option<&[u8]>,
) -> Self {
let R = &signature.R;
let A = &key.compressed;

let mut h = CtxDigest::new();
if let Some(c) = prehash_ctx {
h.update(b"SigEd25519 no Ed25519 collisions");
h.update([1]); // Ed25519ph
h.update([c.len() as u8]);
h.update(c);
}

h.update(R.as_bytes());
h.update(A.as_bytes());
Self {
key: *key,
signature,
h,
}
}

pub fn update(&mut self, m: &[u8]) {
self.h.update(m)
}

pub fn finish(self) -> CompressedEdwardsY {
let k = Scalar::from_hash(self.h);

let minus_A: EdwardsPoint = -self.key.point;
// Recall the (non-batched) verification equation: -[k]A + [s]B = R
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &self.signature.s)
.compress()
}
}

impl Verifier<ed25519::Signature> for VerifyingKey {
/// Verify a signature on a message with this keypair's public key.
///
Expand Down

0 comments on commit 092eebb

Please sign in to comment.