Skip to content

Commit

Permalink
ec/suite_b: Make more LeakyLimb->Limb conversions explicit.
Browse files Browse the repository at this point in the history
Rename `Modulus` to `PublicModulus` and add a new `Modulus`:

    PublicModulus::limbs: [LeakyLimb; _]
    Modulus::limbs: [Limb; _]

Refactor the callers to (approximately) minimize the number of
types a `Modulus` is constructed, as it will require a copy if/when
`Limb` becomes a type distinct from `LeakyLimb`.

(In the future, it is likely that more methods of `CommonOps` and
`ScalarOps` will move to `Modulus<Q>` and `Modulus<N>`,
respectively.)
  • Loading branch information
briansmith committed Dec 10, 2024
1 parent 9b4bb53 commit d7a133b
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 187 deletions.
6 changes: 3 additions & 3 deletions mk/generate_curves.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
use super::{
elem::{binary_op, binary_op_assign},
elem_sqr_mul, elem_sqr_mul_acc, Modulus, *,
elem_sqr_mul, elem_sqr_mul_acc, PublicModulus, *,
};
pub(super) const NUM_LIMBS: usize = (%(bits)d + LIMB_BITS - 1) / LIMB_BITS;
Expand All @@ -42,9 +42,9 @@
num_limbs: elem::NumLimbs::P%(bits)s,
order_bits: %(bits)d,
q: Modulus {
q: PublicModulus {
p: limbs_from_hex("%(q)x"),
rr: limbs_from_hex(%(q_rr)s),
rr: PublicElem::from_hex(%(q_rr)s),
},
n: PublicElem::from_hex("%(n)x"),
Expand Down
10 changes: 7 additions & 3 deletions src/ec/suite_b.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ use crate::{arithmetic::montgomery::*, cpu, ec, error, io::der, pkcs8};
//
fn verify_affine_point_is_on_the_curve(
ops: &CommonOps,
q: &Modulus<Q>,
(x, y): (&Elem<R>, &Elem<R>),
) -> Result<(), error::Unspecified> {
verify_affine_point_is_on_the_curve_scaled(
ops,
q,
(x, y),
&Elem::from(&ops.a),
&Elem::from(&ops.b),
Expand All @@ -52,6 +54,7 @@ fn verify_affine_point_is_on_the_curve(
// This function also verifies that the point is not at infinity.
fn verify_jacobian_point_is_on_the_curve(
ops: &CommonOps,
q: &Modulus<Q>,
p: &Point,
) -> Result<Elem<R>, error::Unspecified> {
let z = ops.point_z(p);
Expand Down Expand Up @@ -109,7 +112,7 @@ fn verify_jacobian_point_is_on_the_curve(
let z4_a = ops.elem_product(&z4, &Elem::from(&ops.a));
let z6 = ops.elem_product(&z4, &z2);
let z6_b = ops.elem_product(&z6, &Elem::from(&ops.b));
verify_affine_point_is_on_the_curve_scaled(ops, (&x, &y), &z4_a, &z6_b)?;
verify_affine_point_is_on_the_curve_scaled(ops, q, (&x, &y), &z4_a, &z6_b)?;
Ok(z2)
}

Expand Down Expand Up @@ -140,16 +143,17 @@ fn verify_jacobian_point_is_on_the_curve(
// Jean-Pierre Seifert.
fn verify_affine_point_is_on_the_curve_scaled(
ops: &CommonOps,
q: &Modulus<Q>,
(x, y): (&Elem<R>, &Elem<R>),
a_scaled: &Elem<R>,
b_scaled: &Elem<R>,
) -> Result<(), error::Unspecified> {
let lhs = ops.elem_squared(y);

let mut rhs = ops.elem_squared(x);
ops.elem_add(&mut rhs, a_scaled);
q.elem_add(&mut rhs, a_scaled);
ops.elem_mul(&mut rhs, x);
ops.elem_add(&mut rhs, b_scaled);
q.elem_add(&mut rhs, b_scaled);

if !ops.elems_are_equal(&lhs, &rhs).leak() {
return Err(error::Unspecified);
Expand Down
9 changes: 6 additions & 3 deletions src/ec/suite_b/ecdh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ fn ecdh(
// The "NSA Guide" steps are from section 3.1 of the NSA guide, "Ephemeral
// Unified Model."

let q = public_key_ops.common.elem_modulus();

// NSA Guide Step 1 is handled separately.

// NIST SP 800-56Ar2 5.6.2.2.2.
Expand All @@ -101,7 +103,7 @@ fn ecdh(
// `parse_uncompressed_point` verifies that the point is not at infinity
// and that it is on the curve, using the Partial Public-Key Validation
// Routine.
let peer_public_key = parse_uncompressed_point(public_key_ops, peer_public_key, cpu)?;
let peer_public_key = parse_uncompressed_point(public_key_ops, &q, peer_public_key, cpu)?;

// NIST SP 800-56Ar2 Step 1.
// NSA Guide Step 3 (except point at infinity check).
Expand All @@ -123,7 +125,8 @@ fn ecdh(
// information about their values can be recovered. This doesn't meet the
// NSA guide's explicit requirement to "zeroize" them though.
// TODO: this only needs common scalar ops
let my_private_key = private_key_as_scalar(private_key_ops, my_private_key);
let n = private_key_ops.common.scalar_modulus();
let my_private_key = private_key_as_scalar(&n, my_private_key);
let product = private_key_ops.point_mul(&my_private_key, &peer_public_key, cpu);

// NIST SP 800-56Ar2 Steps 2, 3, 4, and 5.
Expand All @@ -134,7 +137,7 @@ fn ecdh(
// `big_endian_affine_from_jacobian` verifies that the result is not at
// infinity and also does an extra check to verify that the point is on
// the curve.
big_endian_affine_from_jacobian(private_key_ops, out, None, &product, cpu)
big_endian_affine_from_jacobian(private_key_ops, &q, out, None, &product, cpu)

// NSA Guide Step 5 & 6 are deferred to the caller. Again, we have a
// pretty liberal interpretation of the NIST's spec's "Destroy" that
Expand Down
25 changes: 12 additions & 13 deletions src/ec/suite_b/ecdsa/digest_scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,27 @@ use crate::{digest, ec::suite_b::ops::*};
/// right will give a value less than 2**255, which is less than `n`. The
/// analogous argument applies for P-384. However, it does *not* apply in
/// general; for example, it doesn't apply to P-521.
pub fn digest_scalar(ops: &ScalarOps, msg: digest::Digest) -> Scalar {
digest_scalar_(ops, msg.as_ref())
pub(super) fn digest_scalar(n: &Modulus<N>, msg: digest::Digest) -> Scalar {
digest_scalar_(n, msg.as_ref())
}

#[cfg(test)]
pub(crate) fn digest_bytes_scalar(ops: &ScalarOps, digest: &[u8]) -> Scalar {
digest_scalar_(ops, digest)
pub(super) fn digest_bytes_scalar(n: &Modulus<N>, digest: &[u8]) -> Scalar {
digest_scalar_(n, digest)
}

// This is a separate function solely so that we can test specific digest
// values like all-zero values and values larger than `n`.
fn digest_scalar_(ops: &ScalarOps, digest: &[u8]) -> Scalar {
let len = ops.scalar_bytes_len();
fn digest_scalar_(n: &Modulus<N>, digest: &[u8]) -> Scalar {
let len = n.bytes_len();
let digest = if digest.len() > len {
&digest[..len]
} else {
digest
};

scalar_parse_big_endian_partially_reduced_variable_consttime(
ops.common,
untrusted::Input::from(digest),
)
.unwrap()
scalar_parse_big_endian_partially_reduced_variable_consttime(n, untrusted::Input::from(digest))
.unwrap()
}

#[cfg(test)]
Expand Down Expand Up @@ -94,18 +91,20 @@ mod tests {
panic!("Unsupported curve+digest: {}+{}", curve_name, digest_name);
}
};
let n = ops.scalar_ops.scalar_modulus();

assert_eq!(input.len(), digest_alg.output_len());
assert_eq!(output.len(), ops.scalar_ops.scalar_bytes_len());
assert_eq!(output.len(), n.bytes_len());

let expected = scalar_parse_big_endian_variable(
ops.public_key_ops.common,
&n,
limb::AllowZero::Yes,
untrusted::Input::from(&output),
)
.unwrap();

let actual = digest_bytes_scalar(ops.scalar_ops, &input);
let actual = digest_bytes_scalar(&n, &input);
assert_eq!(
ops.scalar_ops.leak_limbs(&actual),
ops.scalar_ops.leak_limbs(&expected)
Expand Down
18 changes: 11 additions & 7 deletions src/ec/suite_b/ecdsa/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,10 @@ impl EcdsaKeyPair {
rng: &dyn rand::SecureRandom,
) -> Result<Self, error::KeyRejected> {
let cpu = cpu::features();

let (seed, public_key) = key_pair.split();
let d = private_key::private_key_as_scalar(alg.private_key_ops, &seed);
let n = alg.private_scalar_ops.scalar_ops.scalar_modulus();
let d = private_key::private_key_as_scalar(&n, &seed);
let d = alg.private_scalar_ops.to_mont(&d, cpu);

let nonce_key = NonceRandomKey::new(alg, &seed, rng)?;
Expand Down Expand Up @@ -238,21 +240,23 @@ impl EcdsaKeyPair {
let scalar_ops = ops.scalar_ops;
let cops = scalar_ops.common;
let private_key_ops = self.alg.private_key_ops;
let q = cops.elem_modulus();
let n = scalar_ops.scalar_modulus();

for _ in 0..100 {
// XXX: iteration conut?
// Step 1.
let k = private_key::random_scalar(self.alg.private_key_ops, rng)?;
let k = private_key::random_scalar(self.alg.private_key_ops, &n, rng)?;
let k_inv = ops.scalar_inv_to_mont(&k, cpu);

// Step 2.
let r = private_key_ops.point_mul_base(&k, cpu);

// Step 3.
let r = {
let (x, _) = private_key::affine_from_jacobian(private_key_ops, &r, cpu)?;
let (x, _) = private_key::affine_from_jacobian(private_key_ops, &q, &r, cpu)?;
let x = cops.elem_unencoded(&x);
elem_reduced_to_scalar(cops, &x)
n.elem_reduced_to_scalar(&x)
};
if cops.is_zero(&r) {
continue;
Expand All @@ -261,12 +265,12 @@ impl EcdsaKeyPair {
// Step 4 is done by the caller.

// Step 5.
let e = digest_scalar(scalar_ops, h);
let e = digest_scalar(&n, h);

// Step 6.
let s = {
let dr = scalar_ops.scalar_product(&self.d, &r, cpu);
let e_plus_dr = scalar_sum(cops, &e, dr);
let mut e_plus_dr = scalar_ops.scalar_product(&self.d, &r, cpu);
n.elem_add(&mut e_plus_dr, &e);
scalar_ops.scalar_product(&k_inv, &e_plus_dr, cpu)
};
if cops.is_zero(&s) {
Expand Down
23 changes: 12 additions & 11 deletions src/ec/suite_b/ecdsa/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ impl signature::VerificationAlgorithm for EcdsaVerificationAlgorithm {

// NSA Guide Step 3: "Convert the bit string H to an integer e as
// described in Appendix B.2."
digest_scalar(self.ops.scalar_ops, h)
let n = self.ops.scalar_ops.scalar_modulus();
digest_scalar(&n, h)
};

self.verify_digest(public_key, e, signature)
Expand All @@ -84,6 +85,8 @@ impl EcdsaVerificationAlgorithm {

let public_key_ops = self.ops.public_key_ops;
let scalar_ops = self.ops.scalar_ops;
let q = public_key_ops.common.elem_modulus();
let n = scalar_ops.scalar_modulus();

// NSA Guide Prerequisites:
//
Expand All @@ -102,16 +105,16 @@ impl EcdsaVerificationAlgorithm {
// can do. Prerequisite #2 is handled implicitly as the domain
// parameters are hard-coded into the source. Prerequisite #3 is
// handled by `parse_uncompressed_point`.
let peer_pub_key = parse_uncompressed_point(public_key_ops, public_key, cpu)?;
let peer_pub_key = parse_uncompressed_point(public_key_ops, &q, public_key, cpu)?;

let (r, s) = signature.read_all(error::Unspecified, |input| {
(self.split_rs)(scalar_ops, input)
})?;

// NSA Guide Step 1: "If r and s are not both integers in the interval
// [1, n − 1], output INVALID."
let r = scalar_parse_big_endian_variable(public_key_ops.common, limb::AllowZero::No, r)?;
let s = scalar_parse_big_endian_variable(public_key_ops.common, limb::AllowZero::No, s)?;
let r = scalar_parse_big_endian_variable(&n, limb::AllowZero::No, r)?;
let s = scalar_parse_big_endian_variable(&n, limb::AllowZero::No, s)?;

// NSA Guide Step 4: "Compute w = s**−1 mod n, using the routine in
// Appendix B.1."
Expand All @@ -134,7 +137,7 @@ impl EcdsaVerificationAlgorithm {
// `verify_affine_point_is_on_the_curve_scaled` for details on why).
// But, we're going to avoid converting to affine for performance
// reasons, so we do the verification using the Jacobian coordinates.
let z2 = verify_jacobian_point_is_on_the_curve(public_key_ops.common, &product)?;
let z2 = verify_jacobian_point_is_on_the_curve(public_key_ops.common, &q, &product)?;

// NSA Guide Step 7: "Compute v = xR mod n."
// NSA Guide Step 8: "Compare v and r0. If v = r0, output VALID;
Expand All @@ -158,9 +161,9 @@ impl EcdsaVerificationAlgorithm {
if sig_r_equals_x(self.ops, &r, &x, &z2) {
return Ok(());
}
if self.ops.elem_less_than_vartime(&r, &self.ops.q_minus_n) {
if q.elem_less_than_vartime(&r, &self.ops.q_minus_n) {
let n = Elem::from(self.ops.n());
self.ops.scalar_ops.common.elem_add(&mut r, &n);
q.elem_add(&mut r, &n);
if sig_r_equals_x(self.ops, &r, &x, &z2) {
return Ok(());
}
Expand Down Expand Up @@ -316,11 +319,9 @@ mod tests {
panic!("Unsupported curve: {}", curve_name);
}
};
let n = alg.ops.scalar_ops.scalar_modulus();

let digest = super::super::digest_scalar::digest_bytes_scalar(
alg.ops.scalar_ops,
&digest[..],
);
let digest = super::super::digest_scalar::digest_bytes_scalar(&n, &digest[..]);
let actual_result = alg.verify_digest(
untrusted::Input::from(&public_key[..]),
digest,
Expand Down
Loading

0 comments on commit d7a133b

Please sign in to comment.