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

Make scalars always reduced #519

Merged
merged 22 commits into from
Mar 28, 2023
Merged

Conversation

rozbb
Copy link
Contributor

@rozbb rozbb commented Mar 20, 2023

This makes unreduced Scalar unrepresentable by removing Scalar::{from_bits, from_bytes_clamped}. As a consequence, we can remove the additional reduction steps in scalar addition and subtraction. Benchmarks on my M1 Macbook Air show runtime diffs of -52% and -67% on scalar addition and subtraction, respectively.

Since clamping is necessary elsewhere, this change exposes new methods MontgomeryPoint::{mul_clamped, mul_base_clamped}, EdwardsPoint::{mul_clamped, mul_base_clamped}, and BasepointTable::mul_base_clamped, which take byte sequences.

This partially addresses #507 (scalar performance) and #514 (scalar arithmetic well-definedness).

The first snippet of #514 points out that it is possible to construct a clamped scalar s and a point P such that s * P and s.reduce() * P are not equal. This change makes this test case impossible to represent, since unreduced scalars are now unrepresentable.

Upstream changes

dalek-cryptography/ed25519-dalek#293

dalek-cryptography/x25519-dalek#120

@rozbb rozbb requested a review from tarcieri March 20, 2023 08:58
src/edwards.rs Outdated Show resolved Hide resolved
src/scalar.rs Outdated Show resolved Hide resolved
src/scalar.rs Outdated Show resolved Hide resolved
@tarcieri
Copy link
Contributor

tarcieri commented Mar 21, 2023

I like the API and the direction. Preventing the clamped scalars from leaking as returned values seems like an elegant approach, while covering the important use cases.

@rozbb
Copy link
Contributor Author

rozbb commented Mar 26, 2023

@jrose-signal would you mind taking a look at this and verifying it wouldn't break your libraries?

Cargo.toml Show resolved Hide resolved
@@ -342,6 +370,9 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a MontgomeryPoint {
W: FieldElement::ONE,
};

// NOTE: The below swap-double-add routine skips the first iteration, i.e., it assumes the
// MSB of `scalar` is 0. This is allowed, since it follows from Scalar invariant #1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that this was the case because the following test fails.

let a_bytes = [0xff; 32];
let s1 = Scalar::from_bytes_mod_order(a_bytes);
let s2 = Scalar { bytes: a_bytes };
assert_eq!(
    s1 * constants::X25519_BASEPOINT,
    s2 * constants::X25519_BASEPOINT
);

That's kinda surprising. There's no NAF or any fancy arithmetic necessary for the Montgomery ladder. Anyways, neither here nor there, because it works for all scalars < 2^255, which is all we care about.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, that's expected for me. One scalar is reduced, the other is not, but Scalar { bytes: a_bytes } would not be valid under Scalar's (new) invariant and can't be constructed outside the crate due to field visibility.

sidebar: I'm curious if Scalar could be made into a newtype for ScalarImpl now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, well it's only surprising because there's nothing inherent about Montgomery multiplication that even requires invariant 1 in the first place. In fact, if you change the below line to

let mut bits = core::iter::once(0).chain(scalar.bits_le().rev());

then these asserts actually pass.

What is ScalarImpl?

@rozbb rozbb marked this pull request as ready for review March 27, 2023 09:24
@jrose-signal
Copy link
Contributor

@jrose-signal would you mind taking a look at this and verifying it wouldn't break your libraries?

Yes, this is fine for us (and I had someone more cryptographer than me confirm). I did find one place we were using Scalar::from_bits directly: XEd25519 signatures. But those Scalars are always multiplied by the Edwards basepoint, so switching to from_bytes_mod_order for an eager reduction is fine (and doesn't seem to cause any significant slowdown).

src/scalar.rs Show resolved Hide resolved
@rozbb rozbb merged commit f460ae1 into dalek-cryptography:main Mar 28, 2023
@rozbb
Copy link
Contributor Author

rozbb commented Mar 29, 2023

@jrose-signal thanks for the quick turnaround! We'll be propagating the API change to ed- and x- soon

@elichai
Copy link
Contributor

elichai commented Jul 17, 2023

We currently use the Scalar::from_bits function in order to do full-group (non-clamped) Scalar*Point multiplication via the MontgomeryPoint API,
We need this to be over the whole group + twist and not specifically in the prime subgroup,
Is this something that will still be supported?

(Specifically, this is needed for something similar to Moller04 https://www.bmoeller.de/pdf/pke-pseudo-esorics2004.pdf)

@tarcieri
Copy link
Contributor

@elichai would a dedicated API for this, e.g. MontgomeryPoint::mul_unclamped similar to MontgomeryPoint::mul_clamped work for your purposes?

@elichai
Copy link
Contributor

elichai commented Jul 17, 2023

@elichai would a dedicated API for this, e.g. MontgomeryPoint::mul_unclamped similar to MontgomeryPoint::mul_clamped work for your purposes?

Yep, that would definitely work

@elichai
Copy link
Contributor

elichai commented Jul 27, 2023

@elichai would a dedicated API for this, e.g. MontgomeryPoint::mul_unclamped similar to MontgomeryPoint::mul_clamped work for your purposes?

What do you think about this function also overriding invariant #1? i.e. it will allow multiplying with the top bit on, so it can't assume the first bit is zero as in: https://docs.rs/curve25519-dalek/latest/src/curve25519_dalek/montgomery.rs.html#373-374

@tarcieri
Copy link
Contributor

Perhaps it would need to be fallible and return an e.g. CtOption?

@elichai
Copy link
Contributor

elichai commented Jul 27, 2023

Perhaps it would need to be fallible and return an e.g. CtOption?

Why fallible? if it's bigger than 8 \cdot \ell ?

@tarcieri
Copy link
Contributor

Do you have a different solution in mind for it overriding the invariants?

@rozbb
Copy link
Contributor Author

rozbb commented Jul 27, 2023

I think it'd be possible to just extend the iterator to all bits. I'd prefer to not copy the code, so I'll just modify this function and see if we get a performance regression

rozbb added a commit that referenced this pull request Aug 28, 2023
…l_bits_be` (#555)

There is occasionally [a need](#519 (comment)) to multiply a non-prime-order Montgomery point by an integer. There's currently no way to do this, since our only methods are multiplication by `Scalar` (doesn't make sense in the non-prime-order case), and `MontgomeryPoint::mul_base_clamped` clamps the integer before multiplying.

This defines `MontgomeryPoint::mul_bits_be`, which takes a big-endian representation of an integer and multiplies the point by that integer. Its usage is not recommended by default, but it is also not so unsafe as to be gated behind a `hazmat` feature.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants