Merge branch 'release/1.1.0-pre.0'

This commit is contained in:
Henry de Valence 2019-02-14 14:51:03 -08:00
commit 8b2742cb9d
47 changed files with 5790 additions and 1169 deletions

32
CHANGELOG.md Normal file
View file

@ -0,0 +1,32 @@
# Changelog
Entries are listed in reverse chronological order.
## 1.1.0-pre.0
* Restructures the source tree into `serial` and `vector` backends.
* Adds a new IFMA backend which sets speed records.
* Adds support for precomputation for multiscalar multiplication.
* Replaces the `rand` dependency with `rand_core`.
* Generalizes trait bounds on
- `RistrettoPoint::random()`
- `Scalar::random()`
to allow owned and borrowed RNGs and to allow `RngCore` instead of
`Rng`.
## 1.0.3
* Adds `ConstantTimeEq` implementation for compressed points.
## 1.0.2
* Fixes a typo in the naming of variables in Ristretto formulas (no change to functionality).
## 1.0.1
* Depends on the stable `2.0` version of `subtle` instead of `2.0.0-pre.0`.
## 1.0.0
Initial stable release. Yanked due to a dependency mistake (see above).

View file

@ -1,6 +1,6 @@
[package]
name = "curve25519-dalek"
version = "1.0.3"
version = "1.1.0-pre.0"
authors = ["Isis Lovecruft <isis@patternsinthevoid.net>",
"Henry de Valence <hdevalence@hdevalence.ca>"]
readme = "README.md"
@ -26,9 +26,11 @@ features = ["nightly"]
travis-ci = { repository = "dalek-cryptography/curve25519-dalek", branch = "master"}
[dev-dependencies]
rand_os = "0.1.0"
sha2 = { version = "0.8", default-features = false }
bincode = "1"
criterion = "0.2"
rand = "0.6"
[[bench]]
name = "dalek_benchmarks"
@ -41,7 +43,7 @@ harness = false
# match exactly, since the build.rs uses the crate itself as a library.
[dependencies]
rand = { version = "0.6.0", default-features = false }
rand_core = { version = "0.3.0", default-features = false }
byteorder = { version = "^1.2.3", default-features = false, features = ["i128"] }
digest = { version = "0.8", default-features = false }
clear_on_drop = "=0.2.3"
@ -50,7 +52,7 @@ serde = { version = "1.0", optional = true }
packed_simd = { version = "0.3.0", features = ["into_bits"], optional = true }
[build-dependencies]
rand = { version = "0.6.0", default-features = false }
rand_core = { version = "0.3.0", default-features = false }
byteorder = { version = "^1.2.3", default-features = false, features = ["i128"] }
digest = { version = "0.8", default-features = false }
clear_on_drop = "=0.2.3"
@ -61,7 +63,7 @@ packed_simd = { version = "0.3.0", features = ["into_bits"], optional = true }
[features]
nightly = ["subtle/nightly", "clear_on_drop/nightly"]
default = ["std", "u64_backend"]
std = ["alloc", "subtle/std", "rand/std"]
std = ["alloc", "subtle/std", "rand_core/std"]
alloc = []
yolocrypto = []
@ -69,9 +71,10 @@ yolocrypto = []
u32_backend = []
# The u64 backend uses u64s with u128 products.
u64_backend = []
# The AVX2 backend uses u32x8s with u64x4 products.
# It uses the u64 code for serial operations.
avx2_backend = ["nightly", "u64_backend", "packed_simd"]
# The SIMD backend uses parallel formulas, using either AVX2 or AVX512-IFMA.
simd_backend = ["nightly", "u64_backend", "packed_simd"]
# Old name for the SIMD backend, preserved for compatibility
avx2_backend = ["simd_backend"]
# Signals that we're in the main build stage. This is off by default,
# to signal stage 1 of the build, where build.rs loads the library

View file

@ -2,10 +2,12 @@
extern crate rand;
use rand::rngs::OsRng;
use rand::thread_rng;
#[macro_use]
extern crate criterion;
use criterion::BatchSize;
use criterion::Criterion;
extern crate curve25519_dalek;
@ -20,14 +22,10 @@ mod edwards_benches {
use super::*;
use curve25519_dalek::edwards;
use curve25519_dalek::edwards::EdwardsPoint;
use curve25519_dalek::traits::MultiscalarMul;
use curve25519_dalek::traits::VartimeMultiscalarMul;
fn compress(c: &mut Criterion) {
let B = &constants::ED25519_BASEPOINT_POINT;
c.bench_function("EdwardsPoint compression", move |b| {
b.iter(|| B.compress())
});
c.bench_function("EdwardsPoint compression", move |b| b.iter(|| B.compress()));
}
fn decompress(c: &mut Criterion) {
@ -55,25 +53,65 @@ mod edwards_benches {
fn vartime_double_base_scalar_mul(c: &mut Criterion) {
c.bench_function("Variable-time aA+bB, A variable, B fixed", |bench| {
let B = &constants::ED25519_BASEPOINT_POINT;
let a = Scalar::from(298374928u64).invert();
let b = Scalar::from(897987897u64).invert();
let A = B * (b * a);
bench.iter(|| EdwardsPoint::vartime_double_scalar_mul_basepoint(&a, &A, &b));
let mut rng = thread_rng();
let A = &Scalar::random(&mut rng) * &constants::ED25519_BASEPOINT_TABLE;
bench.iter_batched(
|| (Scalar::random(&mut rng), Scalar::random(&mut rng)),
|(a, b)| EdwardsPoint::vartime_double_scalar_mul_basepoint(&a, &A, &b),
BatchSize::SmallInput,
);
});
}
criterion_group! {
name = edwards_benches;
config = Criterion::default();
targets =
compress,
decompress,
consttime_fixed_base_scalar_mul,
consttime_variable_base_scalar_mul,
vartime_double_base_scalar_mul,
}
}
mod multiscalar_benches {
use super::*;
use curve25519_dalek::edwards;
use curve25519_dalek::edwards::EdwardsPoint;
use curve25519_dalek::edwards::VartimeEdwardsPrecomputation;
use curve25519_dalek::traits::MultiscalarMul;
use curve25519_dalek::traits::VartimeMultiscalarMul;
use curve25519_dalek::traits::VartimePrecomputedMultiscalarMul;
fn construct_scalars(n: usize) -> Vec<Scalar> {
let mut rng = thread_rng();
(0..n).map(|_| Scalar::random(&mut rng)).collect()
}
fn construct_points(n: usize) -> Vec<EdwardsPoint> {
let mut rng = thread_rng();
(0..n)
.map(|_| &Scalar::random(&mut rng) * &constants::ED25519_BASEPOINT_TABLE)
.collect()
}
fn construct(n: usize) -> (Vec<Scalar>, Vec<EdwardsPoint>) {
(construct_scalars(n), construct_points(n))
}
fn consttime_multiscalar_mul(c: &mut Criterion) {
c.bench_function_over_inputs(
"Constant-time variable-base multiscalar multiplication",
|b, &&size| {
let mut rng = OsRng::new().unwrap();
let scalars: Vec<Scalar> = (0..size).map(|_| Scalar::random(&mut rng)).collect();
let points: Vec<EdwardsPoint> = scalars
.iter()
.map(|s| s * &constants::ED25519_BASEPOINT_TABLE)
.collect();
b.iter(|| EdwardsPoint::multiscalar_mul(&scalars, &points));
let points = construct_points(size);
// This is supposed to be constant-time, but we might as well
// rerandomize the scalars for every call just in case.
b.iter_batched(
|| construct_scalars(size),
|scalars| EdwardsPoint::multiscalar_mul(&scalars, &points),
BatchSize::SmallInput,
);
},
&MULTISCALAR_SIZES,
);
@ -83,29 +121,107 @@ mod edwards_benches {
c.bench_function_over_inputs(
"Variable-time variable-base multiscalar multiplication",
|b, &&size| {
let mut rng = OsRng::new().unwrap();
let scalars: Vec<Scalar> = (0..size).map(|_| Scalar::random(&mut rng)).collect();
let points: Vec<EdwardsPoint> = scalars
.iter()
.map(|s| s * &constants::ED25519_BASEPOINT_TABLE)
.collect();
b.iter(|| EdwardsPoint::vartime_multiscalar_mul(&scalars, &points));
let points = construct_points(size);
// Rerandomize the scalars for every call to prevent
// false timings from better caching (e.g., the CPU
// cache lifts exactly the right table entries for the
// benchmark into the highest cache levels).
b.iter_batched(
|| construct_scalars(size),
|scalars| EdwardsPoint::vartime_multiscalar_mul(&scalars, &points),
BatchSize::SmallInput,
);
},
&MULTISCALAR_SIZES,
);
}
criterion_group!{
name = edwards_benches;
config = Criterion::default();
fn vartime_precomputed_pure_static(c: &mut Criterion) {
c.bench_function_over_inputs(
"Variable-time fixed-base multiscalar multiplication",
move |b, &&total_size| {
let static_size = total_size;
let static_points = construct_points(static_size);
let precomp = VartimeEdwardsPrecomputation::new(&static_points);
// Rerandomize the scalars for every call to prevent
// false timings from better caching (e.g., the CPU
// cache lifts exactly the right table entries for the
// benchmark into the highest cache levels).
b.iter_batched(
|| construct_scalars(static_size),
|scalars| precomp.vartime_multiscalar_mul(&scalars),
BatchSize::SmallInput,
);
},
&MULTISCALAR_SIZES,
);
}
fn vartime_precomputed_helper(c: &mut Criterion, dynamic_fraction: f64) {
let label = format!(
"Variable-time mixed-base multiscalar multiplication ({:.0}pct dyn)",
100.0 * dynamic_fraction,
);
c.bench_function_over_inputs(
&label,
move |b, &&total_size| {
let dynamic_size = ((total_size as f64) * dynamic_fraction) as usize;
let static_size = total_size - dynamic_size;
let static_points = construct_points(static_size);
let dynamic_points = construct_points(dynamic_size);
let precomp = VartimeEdwardsPrecomputation::new(&static_points);
// Rerandomize the scalars for every call to prevent
// false timings from better caching (e.g., the CPU
// cache lifts exactly the right table entries for the
// benchmark into the highest cache levels). Timings
// should be independent of points so we don't
// randomize them.
b.iter_batched(
|| {
(
construct_scalars(static_size),
construct_scalars(dynamic_size),
)
},
|(static_scalars, dynamic_scalars)| {
precomp.vartime_mixed_multiscalar_mul(
&static_scalars,
&dynamic_scalars,
&dynamic_points,
)
},
BatchSize::SmallInput,
);
},
&MULTISCALAR_SIZES,
);
}
fn vartime_precomputed_00_pct_dynamic(c: &mut Criterion) {
vartime_precomputed_helper(c, 0.0);
}
fn vartime_precomputed_20_pct_dynamic(c: &mut Criterion) {
vartime_precomputed_helper(c, 0.2);
}
fn vartime_precomputed_50_pct_dynamic(c: &mut Criterion) {
vartime_precomputed_helper(c, 0.5);
}
criterion_group! {
name = multiscalar_benches;
// Lower the sample size to run the benchmarks faster
config = Criterion::default().sample_size(15);
targets =
compress,
decompress,
consttime_fixed_base_scalar_mul,
consttime_variable_base_scalar_mul,
vartime_double_base_scalar_mul,
consttime_multiscalar_mul,
vartime_multiscalar_mul,
vartime_precomputed_pure_static,
vartime_precomputed_00_pct_dynamic,
vartime_precomputed_20_pct_dynamic,
vartime_precomputed_50_pct_dynamic,
}
}
@ -141,7 +257,7 @@ mod ristretto_benches {
);
}
criterion_group!{
criterion_group! {
name = ristretto_benches;
config = Criterion::default();
targets =
@ -162,7 +278,7 @@ mod montgomery_benches {
});
}
criterion_group!{
criterion_group! {
name = montgomery_benches;
config = Criterion::default();
targets = montgomery_ladder,
@ -194,7 +310,7 @@ mod scalar_benches {
);
}
criterion_group!{
criterion_group! {
name = scalar_benches;
config = Criterion::default();
targets =
@ -208,4 +324,5 @@ criterion_main!(
montgomery_benches::montgomery_benches,
ristretto_benches::ristretto_benches,
edwards_benches::edwards_benches,
multiscalar_benches::multiscalar_benches,
);

View file

@ -1,5 +1,13 @@
#![cfg_attr(
all(feature = "simd_backend", target_feature = "avx512ifma"),
feature(simd_ffi)
)]
#![cfg_attr(
all(feature = "simd_backend", target_feature = "avx512ifma"),
feature(link_llvm_intrinsics)
)]
#![cfg_attr(all(feature = "alloc", not(feature = "std")), feature(alloc))]
#![cfg_attr(feature = "nightly", feature(cfg_target_feature))]
#![cfg_attr(feature = "nightly", feature(doc_cfg))]
#![allow(unused_variables)]
#![allow(non_snake_case)]
#![allow(dead_code)]
@ -10,10 +18,10 @@ extern crate byteorder;
extern crate clear_on_drop;
extern crate core;
extern crate digest;
extern crate rand;
extern crate rand_core;
extern crate subtle;
#[cfg(all(feature = "nightly", feature = "avx2_backend"))]
#[cfg(all(feature = "nightly", feature = "packed_simd"))]
extern crate packed_simd;
use std::env;
@ -36,35 +44,31 @@ mod macros;
// Public modules
#[path = "src/scalar.rs"]
mod scalar;
#[path = "src/montgomery.rs"]
mod montgomery;
#[path = "src/edwards.rs"]
mod edwards;
#[path = "src/ristretto.rs"]
mod ristretto;
#[path = "src/constants.rs"]
mod constants;
#[path = "src/edwards.rs"]
mod edwards;
#[path = "src/montgomery.rs"]
mod montgomery;
#[path = "src/ristretto.rs"]
mod ristretto;
#[path = "src/scalar.rs"]
mod scalar;
#[path = "src/traits.rs"]
mod traits;
// Internal modules
#[path = "src/field.rs"]
mod field;
#[path = "src/curve_models/mod.rs"]
mod curve_models;
#[path = "src/backend/mod.rs"]
mod backend;
#[path = "src/field.rs"]
mod field;
#[path = "src/prelude.rs"]
mod prelude;
#[path = "src/scalar_mul/mod.rs"]
mod scalar_mul;
#[path = "src/window.rs"]
mod window;
use edwards::EdwardsBasepointTable;
use curve_models::AffineNielsPoint;
use scalar_mul::window::NafLookupTable8;
fn main() {
// Enable the "stage2_build" feature in the main build stage
@ -81,17 +85,16 @@ fn main() {
format!(
"\n
#[cfg(feature = \"u32_backend\")]
use backend::u32::field::FieldElement32;
use backend::serial::u32::field::FieldElement2625;
#[cfg(feature = \"u64_backend\")]
use backend::u64::field::FieldElement64;
use backend::serial::u64::field::FieldElement51;
use edwards::EdwardsBasepointTable;
use curve_models::AffineNielsPoint;
use backend::serial::curve_models::AffineNielsPoint;
use scalar_mul::window::LookupTable;
use scalar_mul::window::NafLookupTable8;
use window::LookupTable;
/// Table containing precomputed multiples of the Ed25519 basepoint \\\\(B = (x, 4/5)\\\\).
pub const ED25519_BASEPOINT_TABLE: EdwardsBasepointTable = ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN;
@ -101,20 +104,35 @@ pub const ED25519_BASEPOINT_TABLE: EdwardsBasepointTable = ED25519_BASEPOINT_TAB
pub const ED25519_BASEPOINT_TABLE_INNER_DOC_HIDDEN: EdwardsBasepointTable = {:?};
\n\n",
&table
).as_bytes(),
).unwrap();
)
.as_bytes(),
)
.unwrap();
// Now generate AFFINE_ODD_MULTIPLES_OF_BASEPOINT
let B = &constants::ED25519_BASEPOINT_POINT;
let odd_multiples = NafLookupTable8::<AffineNielsPoint>::from(B);
// if we are going to build the serial scalar_mul backend
#[cfg(not(all(
feature = "simd_backend",
any(target_feature = "avx2", target_feature = "avx512ifma")
)))]
{
use backend::serial::curve_models::AffineNielsPoint;
use window::NafLookupTable8;
f.write_all(
format!(
"\n
let B = &constants::ED25519_BASEPOINT_POINT;
let odd_multiples = NafLookupTable8::<AffineNielsPoint>::from(B);
f.write_all(
format!(
"\n
use window::NafLookupTable8;
/// Odd multiples of the basepoint `[B, 3B, 5B, 7B, 9B, 11B, 13B, 15B, ..., 127B]`.
pub(crate) const AFFINE_ODD_MULTIPLES_OF_BASEPOINT: NafLookupTable8<AffineNielsPoint> = {:?};
\n\n",
&odd_multiples
).as_bytes(),
).unwrap();
&odd_multiples
)
.as_bytes(),
)
.unwrap();
}
}

View file

@ -1,218 +1,4 @@
A vectorized implementation of group operations on the twisted Edwards
form of Curve25519, using a modification of the 4-way parallel
formulas of Hisil, Wong, Carter, and Dawson.
# Overview
The 2008 paper [_Twisted Edwards Curves Revisited_][hwcd08] by Hisil,
Wong, Carter, and Dawson (HWCD) introduced the “extended coordinates”
and mixed-model representations which are used by most Edwards curve
implementations.
However, they also describe 4-way parallel formulas for point addition
and doubling: a unified addition algorithm taking an effective
\\(2\mathbf M + 1\mathbf D\\), a doubling algorithm taking an
effective \\(1\mathbf M + 1\mathbf S\\), and a dedicated (i.e., for
distinct points) addition algorithm taking an effective \\(2 \mathbf M
\\). They compare these formulas with a 2-way parallel variant of the
Montgomery ladder.
Unlike their serial formulas, which are used widely, their parallel
formulas do not seem to have been implemented in software before. The
2-way parallel Montgomery ladder was used in 2015 by Tung Chou's
`sandy2x` implementation. Curiously, however, although the [`sandy2x`
paper][sandy2x] also implements Edwards arithmetic, and cites HWCD08,
it doesn't mention their parallel Edwards formulas.
A 2015 paper by Hernández and López describes an AVX2 implementation
of X25519. Neither the paper nor the code are publicly available, but
it apparently gives only a [slight speedup][avx2trac], suggesting that
it uses a 4-way parallel Montgomery ladder rather than parallel
Edwards formulas.
The reason may be that HWCD08 describe their formulas as operating on
four independent processors, which would make a software
implementation impractical: all of the operations are too low-latency
to effectively synchronize. But a closer inspection reveals that the
(more expensive) multiplication and squaring steps are uniform, while
the instruction divergence occurs in the (much cheaper) addition and
subtraction steps. This means that a SIMD implementation can perform
the expensive steps uniformly, and handle divergence in the
inexpensive steps using masking.
These notes describe modifications to the original parallel formulas
to allow a SIMD implementation, and this module contains an
implementation of the modified formulas using 256-bit AVX2 vector
operations.
# Parallel formulas in HWCD'08
The doubling formula is presented in the HWCD paper as follows:
| Cost | Processor 1 | Processor 2 | Processor 3 | Processor 4 |
|------------------|--------------------------------|--------------------------------|--------------------------------|--------------------------------|
| | idle | idle | idle | \\( R\_1 \gets X\_1 + Y\_1 \\) |
| \\(1\mathbf S\\) | \\( R\_2 \gets X\_1\^2 \\) | \\( R\_3 \gets Y\_1\^2 \\) | \\( R\_4 \gets Z\_1\^2 \\) | \\( R\_5 \gets R\_1\^2 \\) |
| | \\( R\_6 \gets R\_2 + R\_3 \\) | \\( R\_7 \gets R\_2 - R\_3 \\) | \\( R\_4 \gets 2 R\_4 \\) | idle |
| | idle | \\( R\_1 \gets R\_4 + R\_7 \\) | idle | \\( R\_2 \gets R\_6 - R\_5 \\) |
| \\(1\mathbf M\\) | \\( X\_3 \gets R\_1 R\_2 \\) | \\( Y\_3 \gets R\_6 R\_7 \\) | \\( T\_3 \gets R\_2 R\_6 \\) | \\( Z\_3 \gets R\_1 R\_7 \\) |
and the unified addition algorithm is presented as follows:
| Cost | Processor 1 | Processor 2 | Processor 3 | Processor 4 |
|------------------|--------------------------------|--------------------------------|--------------------------------|--------------------------------|
| | \\( R\_1 \gets Y\_1 - X\_1 \\) | \\( R\_2 \gets Y\_2 - X\_2 \\) | \\( R\_3 \gets Y\_1 + X\_1 \\) | \\( R\_4 \gets Y\_2 + X\_2 \\) |
| \\(1\mathbf M\\) | \\( R\_5 \gets R\_1 R\_2 \\) | \\( R\_6 \gets R\_3 R\_4 \\) | \\( R\_7 \gets T\_1 T\_2 \\) | \\( R\_8 \gets Z\_1 Z\_2 \\) |
| \\(1\mathbf D\\) | idle | idle | \\( R\_7 \gets k R\_7 \\) | \\( R\_8 \gets 2 R\_8 \\) |
| | \\( R\_1 \gets R\_6 - R\_5 \\) | \\( R\_2 \gets R\_8 - R\_7 \\) | \\( R\_3 \gets R\_8 + R\_7 \\) | \\( R\_4 \gets R\_6 + R\_5 \\) |
| \\(1\mathbf M\\) | \\( X\_3 \gets R\_1 R\_2 \\) | \\( Y\_3 \gets R\_3 R\_4 \\) | \\( T\_3 \gets R\_1 R\_4 \\) | \\( Z\_3 \gets R\_2 R\_3 \\) |
Here \\(\mathbf M\\) and \\(\mathbf S\\) represent the cost of
multiplication and squaring of generic field elements, \\(\mathbf D\\)
represents the cost of multiplication by a curve constant (in this
case \\( k = 2d \\)).
Notice that the \\(1\mathbf M\\) and \\(1\mathbf S\\) steps are
uniform. The non-uniform steps are all inexpensive additions or
subtractions, with the exception of the multiplication by the curve
constant \\(k = 2d\\):
$$
R\_7 \gets 2 d R\_7.
$$
HWCD suggest parallelising this step by breaking \\(k = 2d\\) into four
parts as \\(k = k_0 + 2\^n k_1 + 2\^{2n} k_2 + 2\^{3n} k_3 \\) and
computing \\(k_i R_7 \\) in parallel. This is quite awkward, but if
the curve constant is a ratio \\( d = d\_1/d\_2 \\), then projective
coordinates allow us to instead compute
$$
(R\_5, R\_6, R\_7, R\_8) \gets (d\_2 R\_5, d\_2 R\_6, 2d\_1 R\_7, d\_2 R\_8).
$$
This can be performed as a uniform multiplication by a vector of
constants, and if \\(d\_1, d\_2\\) are small, it is relatively
inexpensive. (This trick was suggested by Mike Hamburg).
In the Curve25519 case, we have
$$
d = \frac{d\_1}{d\_2} = \frac{-121665}{121666};
$$
Since \\(2 \cdot 121666 < 2\^{18}\\), all the constants above fit (up
to sign) in 32 bits, so this can be done in parallel as four
multiplications by small constants \\( (121666, 121666, 2\cdot 121665,
2\cdot 121666) \\), followed by a negation to compute \\( - 2\cdot 121665\\).
# Modified parallel formulas
Using the modifications sketched above, we can write SIMD-friendly
versions of the parallel formulas as follows. To avoid confusion with
the original formulas, temporary variables are named \\(S\\) instead
of \\(R\\) and are in static single-assignment form.
## Addition
To add points
\\(P_1 = (X_1 : Y_1 : Z_1 : T_1) \\)
and
\\(P_2 = (X_2 : Y_2 : Z_2 : T_2 ) \\),
we compute
$$
\begin{aligned}
(S\_0 &&,&& S\_1 &&,&& S\_2 &&,&& S\_3 )
&\gets
(Y\_1 - X\_1&&,&& Y\_1 + X\_1&&,&& Y\_2 - X\_2&&,&& Y\_2 + X\_2)
\\\\
(S\_4 &&,&& S\_5 &&,&& S\_6 &&,&& S\_7 )
&\gets
(S\_0 \cdot S\_2&&,&& S\_1 \cdot S\_3&&,&& Z\_1 \cdot Z\_2&&,&& T\_1 \cdot T\_2)
\\\\
(S\_8 &&,&& S\_9 &&,&& S\_{10} &&,&& S\_{11} )
&\gets
(d\_2 \cdot S\_4 &&,&& d\_2 \cdot S\_5 &&,&& 2 d\_2 \cdot S\_6 &&,&& 2 d\_1 \cdot S\_7 )
\\\\
(S\_{12} &&,&& S\_{13} &&,&& S\_{14} &&,&& S\_{15})
&\gets
(S\_9 - S\_8&&,&& S\_9 + S\_8&&,&& S\_{10} - S\_{11}&&,&& S\_{10} + S\_{11})
\\\\
(X\_3&&,&& Y\_3&&,&& Z\_3&&,&& T\_3)
&\gets
(S\_{12} \cdot S\_{14}&&,&& S\_{15} \cdot S\_{13}&&,&& S\_{15} \cdot S\_{14}&&,&& S\_{12} \cdot S\_{13})
\end{aligned}
$$
to obtain \\( P\_3 = (X\_3 : Y\_3 : Z\_3 : T\_3) = P\_1 + P\_2 \\).
This costs \\( 2\mathbf M + 1 \mathbf D\\).
## Readdition
If the point \\( P_2 = (X\_2 : Y\_2 : Z\_2 : T\_2) \\) is fixed, we
can cache the multiplication of the curve constants by computing
$$
\begin{aligned}
(S\_2' &&,&& S\_3' &&,&& Z\_2' &&,&& T\_2' )
&\gets
(d\_2 \cdot (Y\_2 - X\_2)&&,&& d\_2 \cdot (Y\_1 + X\_1)&&,&& 2d\_2 \cdot Z\_2 &&,&& 2d\_1 \cdot T\_2).
\end{aligned}
$$
This costs \\( 1\mathbf D\\); with \\( (S\_2', S\_3', Z\_2', T\_2')\\)
in hand, the addition formulas above become
$$
\begin{aligned}
(S\_0 &&,&& S\_1 &&,&& Z\_1 &&,&& T\_1 )
&\gets
(Y\_1 - X\_1&&,&& Y\_1 + X\_1&&,&& Z\_1 &&,&& T\_1)
\\\\
(S\_8 &&,&& S\_9 &&,&& S\_{10} &&,&& S\_{11} )
&\gets
(S\_0 \cdot S\_2' &&,&& S\_1 \cdot S\_3'&&,&& Z\_1 \cdot Z\_2' &&,&& T\_1 \cdot T\_2')
\\\\
(S\_{12} &&,&& S\_{13} &&,&& S\_{14} &&,&& S\_{15})
&\gets
(S\_9 - S\_8&&,&& S\_9 + S\_8&&,&& S\_{10} - S\_{11}&&,&& S\_{10} + S\_{11})
\\\\
(X\_3&&,&& Y\_3&&,&& Z\_3&&,&& T\_3)
&\gets
(S\_{12} \cdot S\_{14}&&,&& S\_{15} \cdot S\_{13}&&,&& S\_{15} \cdot S\_{14}&&,&& S\_{12} \cdot S\_{13})
\end{aligned}
$$
which costs only \\( 2\mathbf M \\). This precomputation is
essentially similar to the precomputation that HWCD suggest for their
serial formulas. Because the cost of precomputation and then
readdition is the same as addition, it's sufficient to only
implement caching and readdition.
## Doubling
The non-uniform portions of the (re)addition formulas have a fairly
regular structure. Unfortunately, this is not the case for the
doubling formulas, which are much less nice.
To double a point \\( P = (X\_1 : Y\_1 : Z\_1 : T\_1) \\), we compute
$$
\begin{aligned}
(X\_1 &&,&& Y\_1 &&,&& Z\_1 &&,&& S\_0)
&\gets
(X\_1 &&,&& Y\_1 &&,&& Z\_1 &&,&& X\_1 + Y\_1)
\\\\
(S\_1 &&,&& S\_2 &&,&& S\_3 &&,&& S\_4 )
&\gets
(X\_1\^2 &&,&& Y\_1\^2&&,&& Z\_1\^2 &&,&& S\_0\^2)
\\\\
(S\_5 &&,&& S\_6 &&,&& S\_8 &&,&& S\_9 )
&\gets
(S\_1 + S\_2 &&,&& S\_1 - S\_2 &&,&& S\_1 + 2S\_3 - S\_2 &&,&& S\_1 + S\_2 - S\_4)
\\\\
(X\_3 &&,&& Y\_3 &&,&& Z\_3 &&,&& T\_3 )
&\gets
(S\_8 \cdot S\_9 &&,&& S\_5 \cdot S\_6 &&,&& S\_8 \cdot S\_6 &&,&& S\_5 \cdot S\_9)
\end{aligned}
$$
to obtain \\( P\_3 = (X\_3 : Y\_3 : Z\_3 : T\_3) = [2]P\_1 \\).
The intermediate step between the squaring and multiplication requires
a long chain of additions, but with some care and finesse,
described below, it is possible (in our case) to arrange this
computation without requiring an intermediate reduction.
However, it does mean that the doubling formulas have proportionately
more vectorization overhead than the (re)addition formulas. The
effects of this are discussed in the comparison section below.
An AVX2 implementation of the vectorized point operation strategy.
# Field element representation
@ -266,9 +52,7 @@ the form
Since this breaks cleanly into two 128-bit lanes, it may be possible
to adapt it to 128-bit vector instructions such as NEON without too
much difficulty. Going the other direction, to extend this to AVX512,
we could either run two point operations in parallel in lower and upper
halves of the registers, or use 2-way parallelism within a field operation.
much difficulty.
# Avoiding Overflow in Doubling
@ -354,106 +138,3 @@ $$
whose right-hand sides are all bounded with \\( b < 1.75 \\) and
whose left-hand sides are all bounded with \\( b < 2.5 \\),
so that we can avoid any intermediate reductions.
# Comparison to non-vectorized formulas
In theory, the parallel Edwards formulas seem to allow a \\(4\\)-way
speedup from parallelism. However, an actual vectorized
implementation has several slowdowns that cut into this speedup.
First, the parallel formulas can only use a \\( 32 \times 32
\rightarrow 64 \\)-bit integer multiplier, so the speedup from
vectorization must overcome the disadvantage of losing the \\( 64
\times 64 \rightarrow 128\\)-bit (serial) integer multiplier. The
effect of this slowdown is microarchitecture-dependent, since it
requires accounting for the total number of multiplications and
additions and their relative costs. In the future, it will probably
be possible to avoid this slowdown by using the `IFMA52` instructions,
whose parallelism is perfectly suited to these formulas.
Second, the parallel doubling formulas incur both a theoretical and
practical slowdown. The parallel formulas described above work on the
\\( \mathbb P\^3 \\) “extended” coordinates. The \\( \mathbb P\^2 \\)
model introduced earlier by [Bernstein, Birkner, Joye, Lange, and
Peters][bbjlp08] allows slightly faster doublings, so HWCD suggest
mixing coordinate systems while performing scalar multiplication
(attributing the idea to [a 1998 paper][cmo98] by Cohen, Miyagi, and
Ono). The \\( T \\) coordinate is not required for doublings, so when
doublings are followed by doublings, its computation can be skipped.
More details on this approach and the different coordinate systems can
be found in the [`curve_models` module documentation][curve_models].
Unfortunately, this optimization is not compatible with the parallel
formulas, which cannot save time by skipping a single variable, so the
parallel doubling formulas do slightly more work when counting the
total number of field multiplications and squarings.
In addition, the parallel doubling formulas have a less regular
pattern of additions and subtractions than the parallel addition
formulas, so the vectorization overhead is proportionately greater.
Both the parallel addition and parallel doubling formulas also require
some shuffling to rearrange data within the vectors, which places more
pressure on the shuffle unit than is desirable.
This means that the speedup from using a vectorized implementation of
parallel Edwards formulas is likely to be greatest in applications
that do fewer doublings and more additions (like a large multiscalar
multiplication) rather than applications that do fewer additions and
more doublings (like a double-base scalar multiplication).
Third, current Intel CPUs perform thermal throttling when using wide
vector instructions. A detailed description can be found in §15.26 of
[the Intel Optimization Manual][intel], but using wide vector
instructions prevents the core from operating at higher frequencies.
The core can return to the higher-frequency state after 2
milliseconds, but this timer is reset every time high-power
instructions are used.
Any speedup from vectorization therefore has to be weighed against a
slowdown for the next few million instructions. For a mixed workload,
where point operations are interspersed with other tasks, this can
reduce overall performance. This implementation is therefore probably
not suitable for basic applications, like signatures, but is
worthwhile for complex applications, like zero-knowledge proofs, which
do sustained work.
For this reason, the AVX2 backend is not enabled by default, but can
be selected using the `avx2_backend` feature.
# Future work
There are several directions for future improvement:
* Using the vectorized field arithmetic code to parallelize across
point operations rather than within a single point operation. This
is less flexible, but would give a speedup both from allowing use of
the faster mixed-model arithmetic and from reducing shuffle
pressure. One approach in this direction would be to implement
batched scalar-point operations using vectors of points (AoSoA
layout). This less generally useful but would give a speedup for
Bulletproofs.
* Extending the implementation to use the full width of AVX512, either
handling the extra parallelism internally to a single point
operation (by using a 2-way parallel implementation of field
arithmetic instead of a wordsliced one), or externally,
parallelizing across point operations. Internal parallelism would
be preferable but might require too much shuffle pressure.
* Generalizing the implementation to non-AVX2 instructions,
particularly NEON. The current point arithmetic code is written in
terms of field element vectors, which are in turn implemented using
platform SIMD vectors. It should be possible to write an alternate
implementation of the `FieldElement32x4` using NEON without changing
the point arithmetic. NEON has 128-bit vectors rather than 256-bit
vectors, but this may still be worthwhile compared to a serial
implementation.
[sandy2x]: https://eprint.iacr.org/2015/943.pdf
[avx2trac]: https://trac.torproject.org/projects/tor/ticket/8897#comment:28
[hwcd08]: https://www.iacr.org/archive/asiacrypt2008/53500329/53500329.pdf
[curve_models]: https://doc-internal.dalek.rs/curve25519_dalek/curve_models/index.html
[bbjlp08]: https://eprint.iacr.org/2008/013
[cmo98]: https://link.springer.com/content/pdf/10.1007%2F3-540-49649-1_6.pdf
[intel]: https://software.intel.com/sites/default/files/managed/9e/bc/64-ia-32-architectures-optimization-manual.pdf

580
docs/ifma-notes.md Normal file
View file

@ -0,0 +1,580 @@
An AVX512-IFMA implementation of the vectorized point operation
strategy.
# IFMA instructions
AVX512-IFMA is an extension to AVX-512 consisting of two instructions:
* `vpmadd52luq`: packed multiply of unsigned 52-bit integers and add
the low 52 product bits to 64-bit accumulators;
* `vpmadd52huq`: packed multiply of unsigned 52-bit integers and add
the high 52 product bits to 64-bit accumulators;
These operate on 64-bit lanes of their source vectors, taking the low
52 bits of each lane of each source vector, computing the 104-bit
products of each pair, and then adding either the high or low 52 bits
of the 104-bit products to the 64-bit lanes of the destination vector.
The multiplication is performed internally by reusing circuitry for
floating-point arithmetic. Although these instructions are part of
AVX512, the AVX512VL (vector length) extension (present whenever IFMA
is) allows using them with 512, 256, or 128-bit operands.
This provides a major advantage to vectorized integer operations:
previously, vector operations could only use a \\(32 \times 32
\rightarrow 64\\)-bit multiplier, while serial code could use a
\\(64\times 64 \rightarrow 128\\)-bit multiplier.
## IFMA for big-integer multiplications
A detailed example of the intended use of the IFMA instructions can be
found in a 2016 paper by Gueron and Krasnov, [_Accelerating Big
Integer Arithmetic Using Intel IFMA Extensions_][2016_gueron_krasnov].
The basic idea is that multiplication of large integers (such as 1024,
2048, or more bits) can be performed as follows.
First, convert a “packed” 64-bit representation
\\[
\begin{aligned}
x &= x'_0 + x'_1 2^{64} + x'_2 2^{128} + \cdots \\\\
y &= y'_0 + y'_1 2^{64} + y'_2 2^{128} + \cdots
\end{aligned}
\\]
into a “redundant” 52-bit representation
\\[
\begin{aligned}
x &= x_0 + x_1 2^{52} + x_2 2^{104} + \cdots \\\\
y &= y_0 + y_1 2^{52} + y_2 2^{104} + \cdots
\end{aligned}
\\]
with each \\(x_i, y_j\\) in a 64-bit lane.
Writing the product as \\(z = z_0 + z_1 2^{52} + z_2 2^{104} + \cdots\\),
the “schoolbook” multiplication strategy gives
\\[
\begin{aligned}
&z_0 &&=& x_0 & y_0 & & & & & & & & \\\\
&z_1 &&=& x_1 & y_0 &+ x_0 & y_1 & & & & & & \\\\
&z_2 &&=& x_2 & y_0 &+ x_1 & y_1 &+ x_0 & y_2 & & & & \\\\
&z_3 &&=& x_3 & y_0 &+ x_2 & y_1 &+ x_1 & y_2 &+ x_0 & y_3 & & \\\\
&z_4 &&=& \vdots\\;&\\;\vdots &+ x_3 & y_1 &+ x_2 & y_2 &+ x_1 & y_3 &+ \cdots& \\\\
&z_5 &&=& & & \vdots\\;&\\;\vdots &+ x_3 & y_2 &+ x_2 & y_3 &+ \cdots& \\\\
&z_6 &&=& & & & & \vdots\\;&\\;\vdots &+ x_3 & y_3 &+ \cdots& \\\\
&z_7 &&=& & & & & & & \vdots\\;&\\;\vdots &+ \cdots& \\\\
&\vdots&&=& & & & & & & & & \ddots& \\\\
\end{aligned}
\\]
Notice that the product coefficient \\(z_k\\), representing the value
\\(z_k 2^{52k}\\), is the sum of all product terms
\\(
(x_i 2^{52 i}) (y_j 2^{52 j})
\\)
with \\(k = i + j\\).
Write the IFMA operators \\(\mathrm{lo}(a,b)\\), denoting the low
\\(52\\) bits of \\(ab\\), and
\\(\mathrm{hi}(a,b)\\), denoting the high \\(52\\) bits of
\\(ab\\).
Now we can rewrite the product terms as
\\[
\begin{aligned}
(x_i 2^{52 i}) (y_j 2^{52 j})
&=
2^{52 (i+j)}(
\mathrm{lo}(x_i, y_j) +
\mathrm{hi}(x_i, y_j) 2^{52}
)
\\\\
&=
\mathrm{lo}(x_i, y_j) 2^{52 (i+j)} +
\mathrm{hi}(x_i, y_j) 2^{52 (i+j+1)}.
\end{aligned}
\\]
This means that the low half of \\(x_i y_j\\) can be accumulated onto
the product limb \\(z_{i+j}\\) and the high half can be directly
accumulated onto the next-higher product limb \\(z_{i+j+1}\\) with no
additional operations. This allows rewriting the schoolbook
multiplication into the form
\\[
\begin{aligned}
&z_0 &&=& \mathrm{lo}(x_0,&y_0) & & & & & & & & & & \\\\
&z_1 &&=& \mathrm{lo}(x_1,&y_0) &+\mathrm{hi}(x_0,&y_0) &+\mathrm{lo}(x_0,&y_1) & & & & & & \\\\
&z_2 &&=& \mathrm{lo}(x_2,&y_0) &+\mathrm{hi}(x_1,&y_0) &+\mathrm{lo}(x_1,&y_1) &+\mathrm{hi}(x_0,&y_1) &+\mathrm{lo}(x_0,&y_2) & & \\\\
&z_3 &&=& \mathrm{lo}(x_3,&y_0) &+\mathrm{hi}(x_2,&y_0) &+\mathrm{lo}(x_2,&y_1) &+\mathrm{hi}(x_1,&y_1) &+\mathrm{lo}(x_1,&y_2) &+ \cdots& \\\\
&z_4 &&=& \vdots\\;&\\;\vdots &+\mathrm{hi}(x_3,&y_0) &+\mathrm{lo}(x_3,&y_1) &+\mathrm{hi}(x_2,&y_1) &+\mathrm{lo}(x_2,&y_2) &+ \cdots& \\\\
&z_5 &&=& & & \vdots\\;&\\;\vdots & \vdots\\;&\\;\vdots &+\mathrm{hi}(x_3,&y_1) &+\mathrm{lo}(x_3,&y_2) &+ \cdots& \\\\
&z_6 &&=& & & & & & & \vdots\\;&\\;\vdots & \vdots\\;&\\;\vdots &+ \cdots& \\\\
&\vdots&&=& & & & & & & & & & & \ddots& \\\\
\end{aligned}
\\]
Gueron and Krasnov implement multiplication by constructing vectors
out of the columns of this diagram, so that the source operands for
the IFMA instructions are of the form \\((x_0, x_1, x_2, \ldots)\\)
and \\((y_i, y_i, y_i, \ldots)\\).
After performing the multiplication,
the product terms \\(z_i\\) are then repacked into a 64-bit representation.
## An alternative strategy
The strategy described above is aimed at big-integer multiplications,
such as 1024, 2048, or 4096 bits, which would be used for applications
like RSA. However, elliptic curve cryptography uses much smaller field
sizes, such as 256 or 384 bits, so a different strategy is needed.
The parallel Edwards formulas provide parallelism at the level of the
formulas for curve operations. This means that instead of scanning
through the terms of the source operands and parallelizing *within* a
field element (as described above), we can arrange the computation in
product-scanning form and parallelize *across* field elements (as
described below).
The parallel Edwards
formulas provide 4-way parallelism, so they can be implemented using
256-bit vectors using a single 64-bit lane for each element, or using
512-bit vectors using two 64-bit lanes.
The only available CPU supporting IFMA (the
i3-8121U) executes 512-bit IFMA instructions at half rate compared to
256-bit instructions, so for now there's no throughput advantage to
using 512-bit IFMA instructions, and this implementation uses 256-bit
vectors.
To extend this to 512-bit vectors, it's only only necessary to achieve
2-way parallelism, and it's possible (with a small amount of overhead)
to create a hybrid strategy that operates entirely within 128-bit
lanes. This means that cross-lane operations can use the faster
`vpshufd` (1c latency) instead of a general shuffle instruction (3c
latency).
# Choice of radix
The inputs to IFMA instructions are 52 bits wide, so the radix \\(r\\)
used to represent a multiprecision integer must be \\( r \leq 52 \\).
The obvious choice is the "native" radix \\(r = 52\\).
As described above, this choice
has the advantage that for \\(x_i, y_j \in [0,2^{52})\\), the product term
\\[
\begin{aligned}
(x_i 2^{52 i}) (y_j 2^{52 j})
&=
2^{52 (i+j)}(
\mathrm{lo}(x_i, y_j) +
\mathrm{hi}(x_i, y_j) 2^{52}
)
\\\\
&=
\mathrm{lo}(x_i, y_j) 2^{52 (i+j)} +
\mathrm{hi}(x_i, y_j) 2^{52 (i+j+1)},
\end{aligned}
\\]
so that the low and high halves of the product can be directly accumulated
onto the product limbs.
In contrast, when using a smaller radix \\(r = 52 - k\\),
the product term has the form
\\[
\begin{aligned}
(x_i 2^{r i}) (y_j 2^{r j})
&=
2^{r (i+j)}(
\mathrm{lo}(x_i, y_j) +
\mathrm{hi}(x_i, y_j) 2^{52}
)
\\\\
&=
\mathrm{lo}(x_i, y_j) 2^{r (i+j)} +
(
\mathrm{hi}(x_i, y_j) 2^k
)
2^{r (i+j+1)}.
\end{aligned}
\\]
What's happening is that the product \\(x_i y_j\\) of size \\(2r\\)
bits is split not at \\(r\\) but at \\(52\\), so \\(k\\) product bits
are placed into the low half instead of the high half. This means
that the high half of the product cannot be directly accumulated onto
\\(z_{i+j+1}\\), but must first be multiplied by \\(2^k\\) (i.e., left
shifted by \\(k\\)). In addition, the low half of the product is
\\(52\\) bits large instead of \\(r\\) bits.
## Handling offset product terms
[Drucker and Gueron][2018_drucker_gueron] analyze the choice of radix
in the context of big-integer squaring, outlining three ways to handle
the offset product terms, before concluding that all of them are
suboptimal:
1. Shift the results after accumulation;
2. Shift the input operands before multiplication;
3. Split the MAC operation, accumulating into a zeroed register,
shifting the result, and then adding.
The first option is rejected because it could double-shift some
previously accumulated terms, the second doesn't work because the
inputs could become larger than \\(52\\) bits, and the third requires
additional instructions to handle the shifting and adding.
Based on an analysis of total number of instructions, they suggest an
addition to the instruction set, which they call `FMSA` (fused
multiply-shift-add). This would shift the result according to an 8-bit
immediate value before accumulating it into the destination register.
However, this change to the instruction set doesn't seem to be
necessary. Instead, the product terms can be grouped according to
their coefficients, accumulated together, then shifted once before
adding them to the final sum. This uses an extra register, shift, and
add, but only once per product term (accumulation target), not once
per source term (as in the Drucker-Gueron paper).
Moreover, because IFMA instructions execute only on two ports
(presumably 0 and 1), while adds and shifts can execute on three ports
(0, 1, and 5), the adds and shifts can execute independently of the
IFMA operations, as long as there is not too much pressure on port 5.
This means that, although the total number of instructions increases,
the shifts and adds do not necessarily increase the execution time, as
long as throughput is limited by IFMA operations.
Finally, because IFMA instructions have 4 cycle latency and 0.5/1
cycle throughput (for 256/512 bit vectors), maximizing IFMA throughput
requires either 8 (for 256) or 4 (for 512) independent operations. So
accumulating groups of terms independently before adding them at the
end may be necessary anyways, in order to prevent long chains of
dependent instructions.
## Advantages of a smaller radix
Using a smaller radix has other advantages. Although radix \\(52\\)
is an unsaturated representation from the point of view of the
\\(64\\)-bit accumulators (because up to 4096 product terms can be
accumulated without carries), it's a saturated representation from the
point of view of the multiplier (since \\(52\\)-bit values are the
maximum input size).
Because the inputs to a multiplication must have all of their limbs
bounded by \\(2^{52}\\), limbs in excess of \\(2^{52}\\) must be
reduced before they can be used as an input. The
[Gueron-Krasnov][2016_gueron_krasnov] paper suggests normalizing
values using a standard, sequential carry chain: for each limb, add
the carryin from reducing the previous limb, compute the carryout and
reduce the current limb, then move to the next limb.
However, when using a smaller radix, such as \\(51\\), each limb can
store a carry bit and still be used as the input to a multiplication.
This means that the inputs do not need to be normalized, and instead
of using a sequential carry chain, we can compute all carryouts in
parallel, reduce all limbs in parallel, and then add the carryins in
parallel (possibly growing the limb values by one bit).
Because the output of this partial reduction is an acceptable
multiplication input, we can "close the loop" using partial reductions
and never have to normalize to a canonical representation through the
entire computation, in contrast to the Gueron-Krasnov approach, which
converts back to a packed representation after every operation. (This
idea seems to trace back to at least as early as [this 1999
paper][1999_walter]).
Using \\(r = 51\\) is enough to keep a carry bit in each limb and
avoid normalizations. What about an even smaller radix? One reason
to choose a smaller radix would be to align the limb boundaries with
an inline reduction (for instance, choosing \\(r = 43\\) for the
Mersenne field \\(p = 2^{127} - 1\\)), but for \\(p = 2^{255 - 19}\\),
\\(r = 51 = 255/5\\) is the natural choice.
# Multiplication
The inputs to a multiplication are two field elements
\\[
\begin{aligned}
x &= x_0 + x_1 2^{51} + x_2 2^{102} + x_3 2^{153} + x_4 2^{204} \\\\
y &= y_0 + y_1 2^{51} + y_2 2^{102} + y_3 2^{153} + y_4 2^{204},
\end{aligned}
\\]
with limbs in range \\([0,2^{52})\\).
Writing the product terms as
\\[
\begin{aligned}
z &= z_0 + z_1 2^{51} + z_2 2^{102} + z_3 2^{153} + z_4 2^{204} \\\\
&+ z_5 2^{255} + z_6 2^{306} + z_7 2^{357} + z_8 2^{408} + z_9 2^{459},
\end{aligned}
\\]
a schoolbook multiplication in product scanning form takes the form
\\[
\begin{aligned}
z_0 &= x_0 y_0 \\\\
z_1 &= x_1 y_0 + x_0 y_1 \\\\
z_2 &= x_2 y_0 + x_1 y_1 + x_0 y_2 \\\\
z_3 &= x_3 y_0 + x_2 y_1 + x_1 y_2 + x_0 y_3 \\\\
z_4 &= x_4 y_0 + x_3 y_1 + x_2 y_2 + x_1 y_3 + x_0 y_4 \\\\
z_5 &= x_4 y_1 + x_3 y_2 + x_2 y_3 + x_1 y_4 \\\\
z_6 &= x_4 y_2 + x_3 y_3 + x_2 y_4 \\\\
z_7 &= x_4 y_3 + x_3 y_4 \\\\
z_8 &= x_4 y_4 \\\\
z_9 &= 0 \\\\
\end{aligned}
\\]
Each term \\(x_i y_j\\) can be written in terms of IFMA operations as
\\[
x_i y_j = \mathrm{lo}(x_i,y_j) + 2\mathrm{hi}(x_i,y_j)2^{51}.
\\]
Substituting this equation into the schoolbook multiplication, then
moving terms to eliminate the \\(2^{51}\\) factors gives
\\[
\begin{aligned}
z_0 &= \mathrm{lo}(x_0, y_0) \\\\
&+ \qquad 0 \\\\
z_1 &= \mathrm{lo}(x_1, y_0) + \mathrm{lo}(x_0, y_1) \\\\
&+ \qquad 2( \mathrm{hi}(x_0, y_0) )\\\\
z_2 &= \mathrm{lo}(x_2, y_0) + \mathrm{lo}(x_1, y_1) + \mathrm{lo}(x_0, y_2) \\\\
&+ \qquad 2( \mathrm{hi}(x_1, y_0) + \mathrm{hi}(x_0, y_1) )\\\\
z_3 &= \mathrm{lo}(x_3, y_0) + \mathrm{lo}(x_2, y_1) + \mathrm{lo}(x_1, y_2) + \mathrm{lo}(x_0, y_3) \\\\
&+ \qquad 2( \mathrm{hi}(x_2, y_0) + \mathrm{hi}(x_1, y_1) + \mathrm{hi}(x_0, y_2) )\\\\
z_4 &= \mathrm{lo}(x_4, y_0) + \mathrm{lo}(x_3, y_1) + \mathrm{lo}(x_2, y_2) + \mathrm{lo}(x_1, y_3) + \mathrm{lo}(x_0, y_4) \\\\
&+ \qquad 2( \mathrm{hi}(x_3, y_0) + \mathrm{hi}(x_2, y_1) + \mathrm{hi}(x_1, y_2) + \mathrm{hi}(x_0, y_3) )\\\\
z_5 &= \mathrm{lo}(x_4, y_1) + \mathrm{lo}(x_3, y_2) + \mathrm{lo}(x_2, y_3) + \mathrm{lo}(x_1, y_4) \\\\
&+ \qquad 2( \mathrm{hi}(x_4, y_0) + \mathrm{hi}(x_3, y_1) + \mathrm{hi}(x_2, y_2) + \mathrm{hi}(x_1, y_3) + \mathrm{hi}(x_0, y_4) )\\\\
z_6 &= \mathrm{lo}(x_4, y_2) + \mathrm{lo}(x_3, y_3) + \mathrm{lo}(x_2, y_4) \\\\
&+ \qquad 2( \mathrm{hi}(x_4, y_1) + \mathrm{hi}(x_3, y_2) + \mathrm{hi}(x_2, y_3) + \mathrm{hi}(x_1, y_4) )\\\\
z_7 &= \mathrm{lo}(x_4, y_3) + \mathrm{lo}(x_3, y_4) \\\\
&+ \qquad 2( \mathrm{hi}(x_4, y_2) + \mathrm{hi}(x_3, y_3) + \mathrm{hi}(x_2, y_4) )\\\\
z_8 &= \mathrm{lo}(x_4, y_4) \\\\
&+ \qquad 2( \mathrm{hi}(x_4, y_3) + \mathrm{hi}(x_3, y_4) )\\\\
z_9 &= 0 \\\\
&+ \qquad 2( \mathrm{hi}(x_4, y_4) )\\\\
\end{aligned}
\\]
As noted above, our strategy will be to multiply and accumulate the
terms with coefficient \\(2\\) separately from those with coefficient
\\(1\\), before combining them at the end. This can alternately be
thought of as accumulating product terms into a *doubly-redundant*
representation, with two limbs for each digit, before collapsing
the doubly-redundant representation by shifts and adds.
This computation requires 25 `vpmadd52luq` and 25 `vpmadd52huq`
operations. For 256-bit vectors, IFMA operations execute on an
i3-8121U with latency 4 cycles, throughput 0.5 cycles, so executing 50
instructions requires 25 cycles' worth of throughput. Accumulating
terms with coefficient \\(1\\) and \\(2\\) seperately means that the
longest dependency chain has length 5, so the critical path has length
20 cycles and the bottleneck is throughput.
# Reduction modulo \\(p\\)
The next question is how to handle the reduction modulo \\(p\\).
Because \\(p = 2^{255} - 19\\), \\(2^{255} = 19 \pmod p\\), so we can
alternately write
\\[
\begin{aligned}
z &= z_0 + z_1 2^{51} + z_2 2^{102} + z_3 2^{153} + z_4 2^{204} \\\\
&+ z_5 2^{255} + z_6 2^{306} + z_7 2^{357} + z_8 2^{408} + z_9 2^{459}
\end{aligned}
\\]
as
\\[
\begin{aligned}
z &= (z_0 + 19z_5) + (z_1 + 19z_6) 2^{51} + (z_2 + 19z_7) 2^{102} + (z_3 + 19z_8) 2^{153} + (z_4 + 19z_9) 2^{204}.
\end{aligned}
\\]
When using a \\(64 \times 64 \rightarrow 128\\)-bit multiplier, this
can be handled (as in [Ed25519][ed25519_paper]) by premultiplying
source terms by \\(19\\). Since \\(\lg(19) < 4.25\\), this increases
their size by less than \\(4.25\\) bits, and the rest of the
multiplication can be shown to work out.
Here, we have at most \\(1\\) bit of headroom. In order to allow
premultiplication, we would need to use radix \\(2^{47}\\), which
would require six limbs instead of five. Instead, we compute the high
terms \\(z_5, \ldots, z_9\\), each using two chains of IFMA
operations, then multiply by \\(19\\) and combine with the lower terms
\\(z_0, \ldots, z_4\\). There are two ways to perform the
multiplication by \\(19\\): using more IFMA operations, or using the
`vpmullq` instruction, which computes the low \\(64\\) bits of a \\(64
\times 64\\)-bit product. However, `vpmullq` has 15c/1.5c
latency/throughput, in contrast to the 4c/0.5c latency/throughput of
IFMA operations, so it seems like a worse choice.
The high terms \\(z_5, \ldots, z_9\\) are sums of \\(52\\)-bit terms,
so they are larger than \\(52\\) bits. Write these terms in radix \\(52\\) as
\\[
z_{5+i} = z_{5+i}' + z_{5+i}'' 2^{52}, \qquad z_{5+i}' < 2^{52}.
\\]
Then the contribution of \\(z_{5+i}\\), taken modulo \\(p\\), is
\\[
\begin{aligned}
z_{5+i} 2^{255} 2^{51 i}
&=
19 (z_{5+i}' + z_{5+i}'' 2^{52}) 2^{51 i}
\\\\
&=
19 z_{5+i}' 2^{51 i} + 2 \cdot 19 z_{5+i}'' 2^{51 (i+1)}
\\\\
\end{aligned}
\\]
The products \\(19 z_{5+i}', 19 z_{5+i}''\\) can be written in terms of IFMA operations as
\\[
\begin{aligned}
19 z_{5+i}' &= \mathrm{lo}(19, z_{5+i}') + 2 \mathrm{hi}(19, z_{5+i}') 2^{51}, \\\\
19 z_{5+i}'' &= \mathrm{lo}(19, z_{5+i}'') + 2 \mathrm{hi}(19, z_{5+i}'') 2^{51}. \\\\
\end{aligned}
\\]
Because \\(z_{5+i} < 2^{64}\\), \\(z_{5+i}'' < 2^{12} \\), so \\(19
z_{5+i}'' < 2^{17} < 2^{52} \\) and \\(\mathrm{hi}(19, z_{5+i}'') = 0\\).
Because IFMA operations ignore the high bits of their source
operands, we do not need to compute \\(z\_{5+i}'\\) explicitly:
the high bits will be ignored.
Combining these observations, we can write
\\[
\begin{aligned}
z_{5+i} 2^{255} 2^{51 i}
&=
19 z_{5+i}' 2^{51 i} + 2 \cdot 19 z_{5+i}'' 2^{51 (i+1)}
\\\\
&=
\mathrm{lo}(19, z_{5+i}) 2^{51 i}
\+ 2 \mathrm{hi}(19, z_{5+i}) 2^{51 (i+1)}
\+ 2 \mathrm{lo}(19, z_{5+i}/2^{52}) 2^{51 (i+1)}.
\end{aligned}
\\]
For \\(i = 0,1,2,3\\), this allows reducing \\(z_{5+i}\\) onto
\\(z_{i}, z_{i+1}\\), and if the low terms are computed using a
doubly-redundant representation, no additional shifts are needed to
handle the \\(2\\) coefficients. For \\(i = 4\\), there's a
complication: the contribution becomes
\\[
\begin{aligned}
z_{9} 2^{255} 2^{204}
&=
\mathrm{lo}(19, z_{9}) 2^{204}
\+ 2 \mathrm{hi}(19, z_{9}) 2^{255}
\+ 2 \mathrm{lo}(19, z_{9}/2^{52}) 2^{255}
\\\\
&=
\mathrm{lo}(19, z_{9}) 2^{204}
\+ 2 \mathrm{hi}(19, z_{9}) 19
\+ 2 \mathrm{lo}(19, z_{9}/2^{52}) 19
\\\\
&=
\mathrm{lo}(19, z_{9}) 2^{204}
\+ 2
\mathrm{lo}(19, \mathrm{hi}(19, z_{9}) + \mathrm{lo}(19, z_{9}/2^{52})).
\\\\
\end{aligned}
\\]
It would be possible to cut the number of multiplications from 3 to 2
by carrying the high part of each \\(z_i\\) onto \\(z_{i+1}\\). This
would eliminate 5 multiplications, clearing 2.5 cycles of port
pressure, at the cost of 5 additions, adding 1.66 cycles of port
pressure. But doing this would create a dependency between terms
(e.g., \\(z_{5}\\) must be computed before the reduction of
\\(z_{6}\\) can begin), whereas with the approach above, all
contributions to all terms are computed independently, to maximize ILP
and flexibility for the processor to schedule instructions.
This strategy performs 16 IFMA operations, adding two IFMA operations
to each of the \\(2\\)-coefficient terms and one to each of the
\\(1\\)-coefficient terms. Considering the multiplication and
reduction together, we use 66 IFMA operations, requiring 33 cycles'
throughput, while the longest chain of IFMA operations is in the
reduction of \\(z_5\\) onto \\(z_1\\), of length 7 (so 28 cycles, plus
2 cycles to combine the two parts of \\(z_5\\), and the bottleneck is
again throughput.
Once this is done, we have computed the product terms
\\[
z = z_0 + z_1 2^{51} + z_2 2^{102} + z_3 2^{153} + z_4 2^{204},
\\]
without reducing the \\(z_i\\) to fit in \\(52\\) bits. Because the
overall flow of operations alternates multiplications and additions or
subtractions, we would have to perform a reduction after an addition
but before the next multiplication anyways, so there's no benefit to
fully reducing the limbs at the end of a multiplication. Instead, we
leave them unreduced, and track the reduction state using the type
system to ensure that unreduced limbs are not accidentally used as an
input to a multiplication.
# Squaring
Squaring operates similarly to multiplication, but with the
possibility to combine identical terms.
As before, we write the input as
\\[
\begin{aligned}
x &= x_0 + x_1 2^{51} + x_2 2^{102} + x_3 2^{153} + x_4 2^{204}
\end{aligned}
\\]
with limbs in range \\([0,2^{52})\\).
Writing the product terms as
\\[
\begin{aligned}
z &= z_0 + z_1 2^{51} + z_2 2^{102} + z_3 2^{153} + z_4 2^{204} \\\\
&+ z_5 2^{255} + z_6 2^{306} + z_7 2^{357} + z_8 2^{408} + z_9 2^{459},
\end{aligned}
\\]
a schoolbook squaring in product scanning form takes the form
\\[
\begin{aligned}
z_0 &= x_0 x_0 \\\\
z_1 &= 2 x_1 x_0 \\\\
z_2 &= 2 x_2 x_0 + x_1 x_1 \\\\
z_3 &= 2 x_3 x_0 + 2 x_2 x_1 \\\\
z_4 &= 2 x_4 x_0 + 2 x_3 x_1 + x_2 x_2 \\\\
z_5 &= 2 x_4 x_1 + 2 x_3 x_2 \\\\
z_6 &= 2 x_4 x_2 + x_3 x_3 \\\\
z_7 &= 2 x_4 x_3 \\\\
z_8 &= x_4 x_4 \\\\
z_9 &= 0 \\\\
\end{aligned}
\\]
As before, we write \\(x_i x_j\\) as
\\[
x_i x_j = \mathrm{lo}(x_i,x_j) + 2\mathrm{hi}(x_i,x_j)2^{51},
\\]
and substitute to obtain
\\[
\begin{aligned}
z_0 &= \mathrm{lo}(x_0, x_0) + 0 \\\\
z_1 &= 2 \mathrm{lo}(x_1, x_0) + 2 \mathrm{hi}(x_0, x_0) \\\\
z_2 &= 2 \mathrm{lo}(x_2, x_0) + \mathrm{lo}(x_1, x_1) + 4 \mathrm{hi}(x_1, x_0) \\\\
z_3 &= 2 \mathrm{lo}(x_3, x_0) + 2 \mathrm{lo}(x_2, x_1) + 4 \mathrm{hi}(x_2, x_0) + 2 \mathrm{hi}(x_1, x_1) \\\\
z_4 &= 2 \mathrm{lo}(x_4, x_0) + 2 \mathrm{lo}(x_3, x_1) + \mathrm{lo}(x_2, x_2) + 4 \mathrm{hi}(x_3, x_0) + 4 \mathrm{hi}(x_2, x_1) \\\\
z_5 &= 2 \mathrm{lo}(x_4, x_1) + 2 \mathrm{lo}(x_3, x_2) + 4 \mathrm{hi}(x_4, x_0) + 4 \mathrm{hi}(x_3, x_1) + 2 \mathrm{hi}(x_2, x_2) \\\\
z_6 &= 2 \mathrm{lo}(x_4, x_2) + \mathrm{lo}(x_3, x_3) + 4 \mathrm{hi}(x_4, x_1) + 4 \mathrm{hi}(x_3, x_2) \\\\
z_7 &= 2 \mathrm{lo}(x_4, x_3) + 4 \mathrm{hi}(x_4, x_2) + 2 \mathrm{hi}(x_3, x_3) \\\\
z_8 &= \mathrm{lo}(x_4, x_4) + 4 \mathrm{hi}(x_4, x_3) \\\\
z_9 &= 0 + 2 \mathrm{hi}(x_4, x_4) \\\\
\end{aligned}
\\]
To implement these, we group terms by their coefficient, computing
those with coefficient \\(2\\) on set of IFMA chains, and on another
set of chains, we begin with coefficient-\\(4\\) terms, then shift
left before continuing with the coefficient-\\(1\\) terms.
The reduction strategy is the same as for multiplication.
# Future improvements
LLVM won't use blend operations on [256-bit vectors yet][llvm_blend],
so there's a bunch of blend instructions that could be omitted.
Although the multiplications and squarings are much faster, there's no
speedup to the additions and subtractions, so there are diminishing
returns. In fact, the complications in the doubling formulas mean
that doubling is actually slower than readdition. This also suggests
that moving to 512-bit vectors won't be much help for a strategy aimed
at parallelism within a group operation, so to extract performance
gains from 512-bit vectors it will probably be necessary to create a
parallel-friendly multiscalar multiplication algorithm. This could
also help with reducing shuffle pressure.
The squaring implementation could probably be optimized, but without
`perf` support on Cannonlake it's difficult to make actual
measurements.
Another improvement would be to implement vectorized square root
computations, which would allow creating an iterator adaptor for point
decompression that bunched decompression operations and executed them
in parallel. This would accelerate batch verification.
[2016_gueron_krasnov]: https://ieeexplore.ieee.org/document/7563269
[2018_drucker_gueron]: https://eprint.iacr.org/2018/335
[1999_walter]: https://pdfs.semanticscholar.org/0e6a/3e8f30b63b556679f5dff2cbfdfe9523f4fa.pdf
[ed25519_paper]: https://ed25519.cr.yp.to/ed25519-20110926.pdf
[llvm_blend]: https://bugs.llvm.org/show_bug.cgi?id=38343

333
docs/parallel-formulas.md Normal file
View file

@ -0,0 +1,333 @@
Vectorized implementations of field and point operations, using a
modification of the 4-way parallel formulas of Hisil, Wong, Carter,
and Dawson.
These notes explain the parallel formulas and our strategy for using
them with SIMD operations. There are two backend implementations: one
using AVX2, and the other using AVX512-IFMA.
# Overview
The 2008 paper [_Twisted Edwards Curves Revisited_][hwcd08] by Hisil,
Wong, Carter, and Dawson (HWCD) introduced the “extended coordinates”
and mixed-model representations which are used by most Edwards curve
implementations.
However, they also describe 4-way parallel formulas for point addition
and doubling: a unified addition algorithm taking an effective
\\(2\mathbf M + 1\mathbf D\\), a doubling algorithm taking an
effective \\(1\mathbf M + 1\mathbf S\\), and a dedicated (i.e., for
distinct points) addition algorithm taking an effective \\(2 \mathbf M
\\). They compare these formulas with a 2-way parallel variant of the
Montgomery ladder.
Unlike their serial formulas, which are used widely, their parallel
formulas do not seem to have been implemented in software before. The
2-way parallel Montgomery ladder was used in 2015 by Tung Chou's
`sandy2x` implementation. Curiously, however, although the [`sandy2x`
paper][sandy2x] also implements Edwards arithmetic, and cites HWCD08,
it doesn't mention their parallel Edwards formulas.
A 2015 paper by Hernández and López describes an AVX2 implementation
of X25519. Neither the paper nor the code are publicly available, but
it apparently gives only a [slight speedup][avx2trac], suggesting that
it uses a 4-way parallel Montgomery ladder rather than parallel
Edwards formulas.
The reason may be that HWCD08 describe their formulas as operating on
four independent processors, which would make a software
implementation impractical: all of the operations are too low-latency
to effectively synchronize. But a closer inspection reveals that the
(more expensive) multiplication and squaring steps are uniform, while
the instruction divergence occurs in the (much cheaper) addition and
subtraction steps. This means that a SIMD implementation can perform
the expensive steps uniformly, and handle divergence in the
inexpensive steps using masking.
These notes describe modifications to the original parallel formulas
to allow a SIMD implementation, and this module contains
implementations of the modified formulas targeting either AVX2 or
AVX512-IFMA.
# Parallel formulas in HWCD'08
The doubling formula is presented in the HWCD paper as follows:
| Cost | Processor 1 | Processor 2 | Processor 3 | Processor 4 |
|------------------|--------------------------------|--------------------------------|--------------------------------|--------------------------------|
| | idle | idle | idle | \\( R\_1 \gets X\_1 + Y\_1 \\) |
| \\(1\mathbf S\\) | \\( R\_2 \gets X\_1\^2 \\) | \\( R\_3 \gets Y\_1\^2 \\) | \\( R\_4 \gets Z\_1\^2 \\) | \\( R\_5 \gets R\_1\^2 \\) |
| | \\( R\_6 \gets R\_2 + R\_3 \\) | \\( R\_7 \gets R\_2 - R\_3 \\) | \\( R\_4 \gets 2 R\_4 \\) | idle |
| | idle | \\( R\_1 \gets R\_4 + R\_7 \\) | idle | \\( R\_2 \gets R\_6 - R\_5 \\) |
| \\(1\mathbf M\\) | \\( X\_3 \gets R\_1 R\_2 \\) | \\( Y\_3 \gets R\_6 R\_7 \\) | \\( T\_3 \gets R\_2 R\_6 \\) | \\( Z\_3 \gets R\_1 R\_7 \\) |
and the unified addition algorithm is presented as follows:
| Cost | Processor 1 | Processor 2 | Processor 3 | Processor 4 |
|------------------|--------------------------------|--------------------------------|--------------------------------|--------------------------------|
| | \\( R\_1 \gets Y\_1 - X\_1 \\) | \\( R\_2 \gets Y\_2 - X\_2 \\) | \\( R\_3 \gets Y\_1 + X\_1 \\) | \\( R\_4 \gets Y\_2 + X\_2 \\) |
| \\(1\mathbf M\\) | \\( R\_5 \gets R\_1 R\_2 \\) | \\( R\_6 \gets R\_3 R\_4 \\) | \\( R\_7 \gets T\_1 T\_2 \\) | \\( R\_8 \gets Z\_1 Z\_2 \\) |
| \\(1\mathbf D\\) | idle | idle | \\( R\_7 \gets k R\_7 \\) | \\( R\_8 \gets 2 R\_8 \\) |
| | \\( R\_1 \gets R\_6 - R\_5 \\) | \\( R\_2 \gets R\_8 - R\_7 \\) | \\( R\_3 \gets R\_8 + R\_7 \\) | \\( R\_4 \gets R\_6 + R\_5 \\) |
| \\(1\mathbf M\\) | \\( X\_3 \gets R\_1 R\_2 \\) | \\( Y\_3 \gets R\_3 R\_4 \\) | \\( T\_3 \gets R\_1 R\_4 \\) | \\( Z\_3 \gets R\_2 R\_3 \\) |
Here \\(\mathbf M\\) and \\(\mathbf S\\) represent the cost of
multiplication and squaring of generic field elements, \\(\mathbf D\\)
represents the cost of multiplication by a curve constant (in this
case \\( k = 2d \\)).
Notice that the \\(1\mathbf M\\) and \\(1\mathbf S\\) steps are
uniform. The non-uniform steps are all inexpensive additions or
subtractions, with the exception of the multiplication by the curve
constant \\(k = 2d\\):
$$
R\_7 \gets 2 d R\_7.
$$
HWCD suggest parallelising this step by breaking \\(k = 2d\\) into four
parts as \\(k = k_0 + 2\^n k_1 + 2\^{2n} k_2 + 2\^{3n} k_3 \\) and
computing \\(k_i R_7 \\) in parallel. This is quite awkward, but if
the curve constant is a ratio \\( d = d\_1/d\_2 \\), then projective
coordinates allow us to instead compute
$$
(R\_5, R\_6, R\_7, R\_8) \gets (d\_2 R\_5, d\_2 R\_6, 2d\_1 R\_7, d\_2 R\_8).
$$
This can be performed as a uniform multiplication by a vector of
constants, and if \\(d\_1, d\_2\\) are small, it is relatively
inexpensive. (This trick was suggested by Mike Hamburg).
In the Curve25519 case, we have
$$
d = \frac{d\_1}{d\_2} = \frac{-121665}{121666};
$$
Since \\(2 \cdot 121666 < 2\^{18}\\), all the constants above fit (up
to sign) in 32 bits, so this can be done in parallel as four
multiplications by small constants \\( (121666, 121666, 2\cdot 121665,
2\cdot 121666) \\), followed by a negation to compute \\( - 2\cdot 121665\\).
# Modified parallel formulas
Using the modifications sketched above, we can write SIMD-friendly
versions of the parallel formulas as follows. To avoid confusion with
the original formulas, temporary variables are named \\(S\\) instead
of \\(R\\) and are in static single-assignment form.
## Addition
To add points
\\(P_1 = (X_1 : Y_1 : Z_1 : T_1) \\)
and
\\(P_2 = (X_2 : Y_2 : Z_2 : T_2 ) \\),
we compute
$$
\begin{aligned}
(S\_0 &&,&& S\_1 &&,&& S\_2 &&,&& S\_3 )
&\gets
(Y\_1 - X\_1&&,&& Y\_1 + X\_1&&,&& Y\_2 - X\_2&&,&& Y\_2 + X\_2)
\\\\
(S\_4 &&,&& S\_5 &&,&& S\_6 &&,&& S\_7 )
&\gets
(S\_0 \cdot S\_2&&,&& S\_1 \cdot S\_3&&,&& Z\_1 \cdot Z\_2&&,&& T\_1 \cdot T\_2)
\\\\
(S\_8 &&,&& S\_9 &&,&& S\_{10} &&,&& S\_{11} )
&\gets
(d\_2 \cdot S\_4 &&,&& d\_2 \cdot S\_5 &&,&& 2 d\_2 \cdot S\_6 &&,&& 2 d\_1 \cdot S\_7 )
\\\\
(S\_{12} &&,&& S\_{13} &&,&& S\_{14} &&,&& S\_{15})
&\gets
(S\_9 - S\_8&&,&& S\_9 + S\_8&&,&& S\_{10} - S\_{11}&&,&& S\_{10} + S\_{11})
\\\\
(X\_3&&,&& Y\_3&&,&& Z\_3&&,&& T\_3)
&\gets
(S\_{12} \cdot S\_{14}&&,&& S\_{15} \cdot S\_{13}&&,&& S\_{15} \cdot S\_{14}&&,&& S\_{12} \cdot S\_{13})
\end{aligned}
$$
to obtain \\( P\_3 = (X\_3 : Y\_3 : Z\_3 : T\_3) = P\_1 + P\_2 \\).
This costs \\( 2\mathbf M + 1 \mathbf D\\).
## Readdition
If the point \\( P_2 = (X\_2 : Y\_2 : Z\_2 : T\_2) \\) is fixed, we
can cache the multiplication of the curve constants by computing
$$
\begin{aligned}
(S\_2' &&,&& S\_3' &&,&& Z\_2' &&,&& T\_2' )
&\gets
(d\_2 \cdot (Y\_2 - X\_2)&&,&& d\_2 \cdot (Y\_1 + X\_1)&&,&& 2d\_2 \cdot Z\_2 &&,&& 2d\_1 \cdot T\_2).
\end{aligned}
$$
This costs \\( 1\mathbf D\\); with \\( (S\_2', S\_3', Z\_2', T\_2')\\)
in hand, the addition formulas above become
$$
\begin{aligned}
(S\_0 &&,&& S\_1 &&,&& Z\_1 &&,&& T\_1 )
&\gets
(Y\_1 - X\_1&&,&& Y\_1 + X\_1&&,&& Z\_1 &&,&& T\_1)
\\\\
(S\_8 &&,&& S\_9 &&,&& S\_{10} &&,&& S\_{11} )
&\gets
(S\_0 \cdot S\_2' &&,&& S\_1 \cdot S\_3'&&,&& Z\_1 \cdot Z\_2' &&,&& T\_1 \cdot T\_2')
\\\\
(S\_{12} &&,&& S\_{13} &&,&& S\_{14} &&,&& S\_{15})
&\gets
(S\_9 - S\_8&&,&& S\_9 + S\_8&&,&& S\_{10} - S\_{11}&&,&& S\_{10} + S\_{11})
\\\\
(X\_3&&,&& Y\_3&&,&& Z\_3&&,&& T\_3)
&\gets
(S\_{12} \cdot S\_{14}&&,&& S\_{15} \cdot S\_{13}&&,&& S\_{15} \cdot S\_{14}&&,&& S\_{12} \cdot S\_{13})
\end{aligned}
$$
which costs only \\( 2\mathbf M \\). This precomputation is
essentially similar to the precomputation that HWCD suggest for their
serial formulas. Because the cost of precomputation and then
readdition is the same as addition, it's sufficient to only
implement caching and readdition.
## Doubling
The non-uniform portions of the (re)addition formulas have a fairly
regular structure. Unfortunately, this is not the case for the
doubling formulas, which are much less nice.
To double a point \\( P = (X\_1 : Y\_1 : Z\_1 : T\_1) \\), we compute
$$
\begin{aligned}
(X\_1 &&,&& Y\_1 &&,&& Z\_1 &&,&& S\_0)
&\gets
(X\_1 &&,&& Y\_1 &&,&& Z\_1 &&,&& X\_1 + Y\_1)
\\\\
(S\_1 &&,&& S\_2 &&,&& S\_3 &&,&& S\_4 )
&\gets
(X\_1\^2 &&,&& Y\_1\^2&&,&& Z\_1\^2 &&,&& S\_0\^2)
\\\\
(S\_5 &&,&& S\_6 &&,&& S\_8 &&,&& S\_9 )
&\gets
(S\_1 + S\_2 &&,&& S\_1 - S\_2 &&,&& S\_1 + 2S\_3 - S\_2 &&,&& S\_1 + S\_2 - S\_4)
\\\\
(X\_3 &&,&& Y\_3 &&,&& Z\_3 &&,&& T\_3 )
&\gets
(S\_8 \cdot S\_9 &&,&& S\_5 \cdot S\_6 &&,&& S\_8 \cdot S\_6 &&,&& S\_5 \cdot S\_9)
\end{aligned}
$$
to obtain \\( P\_3 = (X\_3 : Y\_3 : Z\_3 : T\_3) = [2]P\_1 \\).
The intermediate step between the squaring and multiplication requires
a long chain of additions. For the IFMA-based implementation, this is not a problem; for the AVX2-based implementation, it is, but with some care and finesse, it's possible to arrange the computation without requiring an intermediate reduction.
# Implementation
These formulas aren't specific to a particular representation of field
element vectors, whose optimum choice is determined by the details of
the instruction set. However, it's not possible to perfectly separate
the implementation of the field element vectors from the
implementation of the point operations. Instead, the [`avx2`] and
[`ifma`] backends provide `ExtendedPoint` and `CachedPoint` types, and
the [`scalar_mul`] code uses one of the backend types by a type alias.
# Comparison to non-vectorized formulas
In theory, the parallel Edwards formulas seem to allow a \\(4\\)-way
speedup from parallelism. However, an actual vectorized
implementation has several slowdowns that cut into this speedup.
First, the parallel formulas can only use the available vector
multiplier. For AVX2, this is a \\( 32 \times 32 \rightarrow 64
\\)-bit integer multiplier, so the speedup from vectorization must
overcome the disadvantage of losing the \\( 64 \times 64 \rightarrow
128\\)-bit (serial) integer multiplier. The effect of this slowdown
is microarchitecture-dependent, since it requires accounting for the
total number of multiplications and additions and their relative
costs. IFMA allows using a \\( 52 \times 52 \rightarrow 104 \\)-bit
multiplier, but the high and low halves need to be computed
separately, and the reduction requires extra work because it's not
possible to pre-multiply by \\(19\\).
Second, the parallel doubling formulas incur both a theoretical and
practical slowdown. The parallel formulas described above work on the
\\( \mathbb P\^3 \\) “extended” coordinates. The \\( \mathbb P\^2 \\)
model introduced earlier by [Bernstein, Birkner, Joye, Lange, and
Peters][bbjlp08] allows slightly faster doublings, so HWCD suggest
mixing coordinate systems while performing scalar multiplication
(attributing the idea to [a 1998 paper][cmo98] by Cohen, Miyagi, and
Ono). The \\( T \\) coordinate is not required for doublings, so when
doublings are followed by doublings, its computation can be skipped.
More details on this approach and the different coordinate systems can
be found in the [`curve_models` module documentation][curve_models].
Unfortunately, this optimization is not compatible with the parallel
formulas, which cannot save time by skipping a single variable, so the
parallel doubling formulas do slightly more work when counting the
total number of field multiplications and squarings.
In addition, the parallel doubling formulas have a less regular
pattern of additions and subtractions than the parallel addition
formulas, so the vectorization overhead is proportionately greater.
Both the parallel addition and parallel doubling formulas also require
some shuffling to rearrange data within the vectors, which places more
pressure on the shuffle unit than is desirable.
This means that the speedup from using a vectorized implementation of
parallel Edwards formulas is likely to be greatest in applications
that do fewer doublings and more additions (like a large multiscalar
multiplication) rather than applications that do fewer additions and
more doublings (like a double-base scalar multiplication).
Third, Amdahl's law says that the speedup is limited to the portion
which can be parallelized. Normally, the field multiplications
dominate the cost of point operations, but with the IFMA backend, the
multiplications are so fast that the non-parallel additions end up as
a significant portion of the total time.
Fourth, current Intel CPUs perform thermal throttling when using wide
vector instructions. A detailed description can be found in §15.26 of
[the Intel Optimization Manual][intel], but using wide vector
instructions prevents the core from operating at higher frequencies.
The core can return to the higher-frequency state after 2
milliseconds, but this timer is reset every time high-power
instructions are used.
Any speedup from vectorization therefore has to be weighed against a
slowdown for the next few million instructions. For a mixed workload,
where point operations are interspersed with other tasks, this can
reduce overall performance. This implementation is therefore probably
not suitable for basic applications, like signatures, but is
worthwhile for complex applications, like zero-knowledge proofs, which
do sustained work.
# Future work
There are several directions for future improvement:
* Using the vectorized field arithmetic code to parallelize across
point operations rather than within a single point operation. This
is less flexible, but would give a speedup both from allowing use of
the faster mixed-model arithmetic and from reducing shuffle
pressure. One approach in this direction would be to implement
batched scalar-point operations using vectors of points (AoSoA
layout). This less generally useful but would give a speedup for
Bulletproofs.
* Extending the IFMA implementation to use the full width of AVX512,
either handling the extra parallelism internally to a single point
operation (by using a 2-way parallel implementation of field
arithmetic instead of a wordsliced one), or externally,
parallelizing across point operations. Internal parallelism would
be preferable but might require too much shuffle pressure. For now,
the only available CPU which runs IFMA operations executes them at
256-bits wide anyways, so this isn't yet important.
* Generalizing the implementation to NEON instructions. The current
point arithmetic code is written in terms of field element vectors,
which are in turn implemented using platform SIMD vectors. It
should be possible to write an alternate implementation of the
`FieldElement2625x4` using NEON without changing the point
arithmetic. NEON has 128-bit vectors rather than 256-bit vectors,
but this may still be worthwhile compared to a serial
implementation.
[sandy2x]: https://eprint.iacr.org/2015/943.pdf
[avx2trac]: https://trac.torproject.org/projects/tor/ticket/8897#comment:28
[hwcd08]: https://www.iacr.org/archive/asiacrypt2008/53500329/53500329.pdf
[curve_models]: https://doc-internal.dalek.rs/curve25519_dalek/curve_models/index.html
[bbjlp08]: https://eprint.iacr.org/2008/013
[cmo98]: https://link.springer.com/content/pdf/10.1007%2F3-540-49649-1_6.pdf
[intel]: https://software.intel.com/sites/default/files/managed/9e/bc/64-ia-32-architectures-optimization-manual.pdf

View file

@ -10,29 +10,53 @@
//! Pluggable implementations for different architectures.
//!
//! The naming of the `u32` and `u64` modules is somewhat unfortunate,
//! since these are also the names of primitive types. Since types have
//! a different namespace than modules, this isn't a problem to the
//! compiler, but it could cause confusion.
//! The backend code is split into two parts: a serial backend,
//! and a vector backend.
//!
//! However, it's unlikely that the names of those modules would be
//! brought into scope directly, instead of used as
//! `backend::u32::field` or similar. Unfortunately we can't use
//! `32bit` since identifiers can't start with letters, and the backends
//! do use `u32`/`u64`, so this seems like a least-bad option.
//! The [`serial`] backend contains 32- and 64-bit implementations of
//! field arithmetic and scalar arithmetic, as well as implementations
//! of point operations using the mixed-model strategy (passing
//! between different curve models depending on the operation).
//!
//! The [`vector`] backend contains implementations of vectorized
//! field arithmetic, used to implement point operations using a novel
//! implementation strategy derived from parallel formulas of Hisil,
//! Wong, Carter, and Dawson.
//!
//! Because the two strategies give rise to different curve models,
//! it's not possible to reuse exactly the same scalar multiplication
//! code (or to write it generically), so both serial and vector
//! backends contain matching implementations of scalar multiplication
//! algorithms. These are intended to be selected by a `#[cfg]`-based
//! type alias.
//!
//! The [`vector`] backend is selected by the `simd_backend` cargo
//! feature; it uses the [`serial`] backend for non-vectorized operations.
#[cfg(not(any(feature = "u32_backend", feature = "u64_backend", feature = "avx2_backend")))]
#[cfg(not(any(
feature = "u32_backend",
feature = "u64_backend",
feature = "simd_backend",
)))]
compile_error!(
"no curve25519-dalek backend cargo feature enabled! \
please enable one of: u32_backend, u64_backend, avx2_backend"
please enable one of: u32_backend, u64_backend, simd_backend"
);
#[cfg(feature = "u32_backend")]
pub mod u32;
#[cfg(feature = "u64_backend")]
pub mod u64;
#[cfg(all(feature = "avx2_backend", target_feature = "avx2"))]
pub mod avx2;
pub mod serial;
#[cfg(any(
all(
feature = "simd_backend",
any(target_feature = "avx2", target_feature = "avx512ifma")
),
all(feature = "nightly", rustdoc)
))]
#[cfg_attr(
feature = "nightly",
doc(cfg(any(all(
feature = "simd_backend",
any(target_feature = "avx2", target_feature = "avx512ifma")
))))
)]
pub mod vector;

43
src/backend/serial/mod.rs Normal file
View file

@ -0,0 +1,43 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Isis Agora Lovecruft <isis@patternsinthevoid.net>
// - Henry de Valence <hdevalence@hdevalence.ca>
//! Serial implementations of field, scalar, point arithmetic.
//!
//! When the vector backend is disabled, the crate uses the
//! mixed-model strategy for implementing point operations and scalar
//! multiplication; see the [`curve_models`](self::curve_models) and
//! [`scalar_mul`](self::scalar_mul) documentation for more
//! information.
//!
//! When the vector backend is enabled, the field and scalar
//! implementations are still used for non-vectorized operations.
//!
//! Note: at this time the `u32` and `u64` backends cannot be built
//! together.
#[cfg(not(any(feature = "u32_backend", feature = "u64_backend")))]
compile_error!(
"no curve25519-dalek backend cargo feature enabled! \
please enable one of: u32_backend, u64_backend"
);
#[cfg(feature = "u32_backend")]
pub mod u32;
#[cfg(feature = "u64_backend")]
pub mod u64;
pub mod curve_models;
#[cfg(not(all(
feature = "simd_backend",
any(target_feature = "avx2", target_feature = "avx512ifma")
)))]
pub mod scalar_mul;

View file

@ -16,11 +16,13 @@
//! scalar multiplication implementations, since it only uses one
//! curve model.
pub mod window;
pub mod variable_base;
#[cfg(feature = "stage2_build")]
pub mod vartime_double_base;
#[cfg(feature = "alloc")]
pub mod straus;
#[cfg(feature = "alloc")]
pub mod precomputed_straus;

View file

@ -0,0 +1,114 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2019 Henry de Valence.
// See LICENSE for licensing information.
//
// Authors:
// - Henry de Valence <hdevalence@hdevalence.ca>
//! Precomputation for Straus's method.
#![allow(non_snake_case)]
use core::borrow::Borrow;
use backend::serial::curve_models::{
AffineNielsPoint, CompletedPoint, ProjectiveNielsPoint, ProjectivePoint,
};
use edwards::EdwardsPoint;
use scalar::Scalar;
use traits::Identity;
use traits::VartimePrecomputedMultiscalarMul;
use window::{NafLookupTable5, NafLookupTable8};
#[allow(unused_imports)]
use prelude::*;
pub struct VartimePrecomputedStraus {
static_lookup_tables: Vec<NafLookupTable8<AffineNielsPoint>>,
}
impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus {
type Point = EdwardsPoint;
fn new<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Self::Point>,
{
Self {
static_lookup_tables: static_points
.into_iter()
.map(|P| NafLookupTable8::<AffineNielsPoint>::from(P.borrow()))
.collect(),
}
}
fn optional_mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Option<Self::Point>
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator<Item = Option<Self::Point>>,
{
let static_nafs = static_scalars
.into_iter()
.map(|c| c.borrow().non_adjacent_form(5))
.collect::<Vec<_>>();
let dynamic_nafs: Vec<_> = dynamic_scalars
.into_iter()
.map(|c| c.borrow().non_adjacent_form(5))
.collect::<Vec<_>>();
let dynamic_lookup_tables = match dynamic_points
.into_iter()
.map(|P_opt| P_opt.map(|P| NafLookupTable5::<ProjectiveNielsPoint>::from(&P)))
.collect::<Option<Vec<_>>>()
{
Some(x) => x,
None => return None,
};
let sp = self.static_lookup_tables.len();
let dp = dynamic_lookup_tables.len();
assert_eq!(sp, static_nafs.len());
assert_eq!(dp, dynamic_nafs.len());
// We could save some doublings by looking for the highest
// nonzero NAF coefficient, but since we might have a lot of
// them to search, it's not clear it's worthwhile to check.
let mut S = ProjectivePoint::identity();
for j in (0..255).rev() {
let mut R: CompletedPoint = S.double();
for i in 0..dp {
let t_ij = dynamic_nafs[i][j];
if t_ij > 0 {
R = &R.to_extended() + &dynamic_lookup_tables[i].select(t_ij as usize);
} else if t_ij < 0 {
R = &R.to_extended() - &dynamic_lookup_tables[i].select(-t_ij as usize);
}
}
for i in 0..sp {
let t_ij = static_nafs[i][j];
if t_ij > 0 {
R = &R.to_extended() + &self.static_lookup_tables[i].select(t_ij as usize);
} else if t_ij < 0 {
R = &R.to_extended() - &self.static_lookup_tables[i].select(-t_ij as usize);
}
}
S = R.to_projective();
}
Some(S.to_extended())
}
}

View file

@ -12,16 +12,11 @@
#![allow(non_snake_case)]
#[cfg(any(feature = "alloc", feature = "std"))]
use core::borrow::Borrow;
#[cfg(any(feature = "alloc", feature = "std"))]
use edwards::EdwardsPoint;
#[cfg(any(feature = "alloc", feature = "std"))]
use scalar::Scalar;
#[cfg(any(feature = "alloc", feature = "std"))]
use traits::MultiscalarMul;
#[cfg(any(feature = "alloc", feature = "std"))]
use traits::VartimeMultiscalarMul;
#[allow(unused_imports)]
@ -48,10 +43,8 @@ use prelude::*;
///
/// [solution]: https://www.jstor.org/stable/2310929
/// [problem]: https://www.jstor.org/stable/2312273
#[cfg(any(feature = "alloc", feature = "std"))]
pub struct Straus {}
#[cfg(feature = "alloc")]
impl MultiscalarMul for Straus {
type Point = EdwardsPoint;
@ -115,8 +108,8 @@ impl MultiscalarMul for Straus {
{
use clear_on_drop::ClearOnDrop;
use curve_models::ProjectiveNielsPoint;
use scalar_mul::window::LookupTable;
use backend::serial::curve_models::ProjectiveNielsPoint;
use window::LookupTable;
use traits::Identity;
let lookup_tables: Vec<_> = points
@ -148,7 +141,6 @@ impl MultiscalarMul for Straus {
}
}
#[cfg(feature = "alloc")]
impl VartimeMultiscalarMul for Straus {
type Point = EdwardsPoint;
@ -167,8 +159,8 @@ impl VartimeMultiscalarMul for Straus {
I::Item: Borrow<Scalar>,
J: IntoIterator<Item = Option<EdwardsPoint>>,
{
use curve_models::{CompletedPoint, ProjectiveNielsPoint, ProjectivePoint};
use scalar_mul::window::NafLookupTable5;
use backend::serial::curve_models::{CompletedPoint, ProjectiveNielsPoint, ProjectivePoint};
use window::NafLookupTable5;
use traits::Identity;
let nafs: Vec<_> = scalars

View file

@ -3,8 +3,8 @@
use traits::Identity;
use scalar::Scalar;
use edwards::EdwardsPoint;
use curve_models::ProjectiveNielsPoint;
use scalar_mul::window::LookupTable;
use backend::serial::curve_models::ProjectiveNielsPoint;
use window::LookupTable;
/// Perform constant-time, variable-base scalar multiplication.
pub(crate) fn mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint {

View file

@ -13,8 +13,8 @@ use constants;
use traits::Identity;
use scalar::Scalar;
use edwards::EdwardsPoint;
use curve_models::{ProjectiveNielsPoint, ProjectivePoint};
use scalar_mul::window::NafLookupTable5;
use backend::serial::curve_models::{ProjectiveNielsPoint, ProjectivePoint};
use window::NafLookupTable5;
/// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the Ed25519 basepoint.
pub fn mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint {

View file

@ -0,0 +1,149 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Isis Agora Lovecruft <isis@patternsinthevoid.net>
// - Henry de Valence <hdevalence@hdevalence.ca>
//! This module contains various constants (such as curve parameters
//! and useful field elements like `sqrt(-1)`), as well as
//! lookup tables of pre-computed points.
use backend::serial::u32::field::FieldElement2625;
use backend::serial::u32::scalar::Scalar29;
use edwards::EdwardsPoint;
/// Edwards `d` value, equal to `-121665/121666 mod p`.
pub(crate) const EDWARDS_D: FieldElement2625 = FieldElement2625([
56195235, 13857412, 51736253, 6949390, 114729,
24766616, 60832955, 30306712, 48412415, 21499315,
]);
/// Edwards `2*d` value, equal to `2*(-121665/121666) mod p`.
pub(crate) const EDWARDS_D2: FieldElement2625 = FieldElement2625([
45281625, 27714825, 36363642, 13898781, 229458,
15978800, 54557047, 27058993, 29715967, 9444199,
]);
/// `= sqrt(a*d - 1)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters.
pub(crate) const SQRT_AD_MINUS_ONE: FieldElement2625 = FieldElement2625([
24849947, 33400850, 43495378, 6347714, 46036536,
32887293, 41837720, 18186727, 66238516, 14525638,
]);
/// `= 1/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters.
pub(crate) const INVSQRT_A_MINUS_D: FieldElement2625 = FieldElement2625([
6111466, 4156064, 39310137, 12243467, 41204824,
120896, 20826367, 26493656, 6093567, 31568420,
]);
/// Precomputed value of one of the square roots of -1 (mod p)
pub(crate) const SQRT_M1: FieldElement2625 = FieldElement2625([
34513072, 25610706, 9377949, 3500415, 12389472,
33281959, 41962654, 31548777, 326685, 11406482,
]);
/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within the Montgomery ladder.)
pub(crate) const APLUS2_OVER_FOUR: FieldElement2625 = FieldElement2625([
121666, 0, 0, 0, 0, 0, 0, 0, 0, 0
]);
/// `L` is the order of base point, i.e. 2^252 +
/// 27742317777372353535851937790883648493
pub(crate) const L: Scalar29 = Scalar29([ 0x1cf5d3ed, 0x009318d2, 0x1de73596, 0x1df3bd45,
0x0000014d, 0x00000000, 0x00000000, 0x00000000,
0x00100000 ]);
/// `L` * `LFACTOR` = -1 (mod 2^29)
pub(crate) const LFACTOR: u32 = 0x12547e1b;
/// `R` = R % L where R = 2^261
pub(crate) const R: Scalar29 = Scalar29([ 0x114df9ed, 0x1a617303, 0x0f7c098c, 0x16793167,
0x1ffd656e, 0x1fffffff, 0x1fffffff, 0x1fffffff,
0x000fffff ]);
/// `RR` = (R^2) % L where R = 2^261
pub(crate) const RR: Scalar29 = Scalar29([ 0x0b5f9d12, 0x1e141b17, 0x158d7f3d, 0x143f3757,
0x1972d781, 0x042feb7c, 0x1ceec73d, 0x1e184d1e,
0x0005046d ]);
/// The Ed25519 basepoint, as an `EdwardsPoint`.
///
/// This is called `_POINT` to distinguish it from
/// `ED25519_BASEPOINT_TABLE`, which should be used for scalar
/// multiplication (it's much faster).
pub const ED25519_BASEPOINT_POINT: EdwardsPoint = EdwardsPoint{
X: FieldElement2625([52811034, 25909283, 16144682, 17082669, 27570973, 30858332, 40966398, 8378388, 20764389, 8758491]),
Y: FieldElement2625([40265304, 26843545, 13421772, 20132659, 26843545, 6710886, 53687091, 13421772, 40265318, 26843545]),
Z: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement2625([28827043, 27438313, 39759291, 244362, 8635006, 11264893, 19351346, 13413597, 16611511, 27139452]),
};
/// The 8-torsion subgroup \\(\mathcal E [8]\\).
///
/// In the case of Curve25519, it is cyclic; the \\(i\\)-th element of
/// the array is \\([i]P\\), where \\(P\\) is a point of order \\(8\\)
/// generating \\(\mathcal E[8]\\).
///
/// Thus \\(\mathcal E[8]\\) is the points indexed by `0,2,4,6`, and
/// \\(\mathcal E[2]\\) is the points indexed by `0,4`.
/// The Ed25519 basepoint has y = 4/5. This is called `_POINT` to
/// distinguish it from `_TABLE`, which should be used for scalar
/// multiplication (it's much faster).
pub const EIGHT_TORSION: [EdwardsPoint; 8] = EIGHT_TORSION_INNER_DOC_HIDDEN;
/// Inner item used to hide limb constants from cargo doc output.
#[doc(hidden)]
pub const EIGHT_TORSION_INNER_DOC_HIDDEN: [EdwardsPoint; 8] = [
EdwardsPoint{
X: FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Y: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Z: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
},
EdwardsPoint{
X: FieldElement2625([21352778, 5345713, 4660180, 25206575, 24143089, 14568123, 30185756, 21306662, 33579924, 8345318]),
Y: FieldElement2625([6952903, 1265500, 60246523, 7057497, 4037696, 5447722, 35427965, 15325401, 19365852, 31985330]),
Z: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement2625([41846657, 21581751, 11716001, 27684820, 48915701, 16297738, 20670665, 24995334, 3541542, 28543251])
},
EdwardsPoint{
X: FieldElement2625([32595773, 7943725, 57730914, 30054016, 54719391, 272472, 25146209, 2005654, 66782178, 22147949]),
Y: FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Z: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
},
EdwardsPoint{
X: FieldElement2625([21352778, 5345713, 4660180, 25206575, 24143089, 14568123, 30185756, 21306662, 33579924, 8345318]),
Y: FieldElement2625([60155942, 32288931, 6862340, 26496934, 63071167, 28106709, 31680898, 18229030, 47743011, 1569101]),
Z: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement2625([25262188, 11972680, 55392862, 5869611, 18193162, 17256693, 46438198, 8559097, 63567321, 5011180])
},
EdwardsPoint{
X: FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Y: FieldElement2625([67108844, 33554431, 67108863, 33554431, 67108863, 33554431, 67108863, 33554431, 67108863, 33554431]),
Z: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
},
EdwardsPoint{
X: FieldElement2625([45756067, 28208718, 62448683, 8347856, 42965774, 18986308, 36923107, 12247769, 33528939, 25209113]),
Y: FieldElement2625([60155942, 32288931, 6862340, 26496934, 63071167, 28106709, 31680898, 18229030, 47743011, 1569101]),
Z: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement2625([41846657, 21581751, 11716001, 27684820, 48915701, 16297738, 20670665, 24995334, 3541542, 28543251])
},
EdwardsPoint{
X: FieldElement2625([34513072, 25610706, 9377949, 3500415, 12389472, 33281959, 41962654, 31548777, 326685, 11406482]),
Y: FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Z: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement2625([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
},
EdwardsPoint{
X: FieldElement2625([45756067, 28208718, 62448683, 8347856, 42965774, 18986308, 36923107, 12247769, 33528939, 25209113]),
Y: FieldElement2625([6952903, 1265500, 60246523, 7057497, 4037696, 5447722, 35427965, 15325401, 19365852, 31985330]),
Z: FieldElement2625([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement2625([25262188, 11972680, 55392862, 5869611, 18193162, 17256693, 46438198, 8559097, 63567321, 5011180])
},
];

View file

@ -24,7 +24,7 @@ use core::ops::{Sub, SubAssign};
use subtle::Choice;
use subtle::ConditionallySelectable;
/// A `FieldElement32` represents an element of the field
/// A `FieldElement2625` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// In the 32-bit implementation, a `FieldElement` is represented in
@ -41,44 +41,44 @@ use subtle::ConditionallySelectable;
/// # Note
///
/// The `curve25519_dalek::field` module provides a type alias
/// `curve25519_dalek::field::FieldElement` to either `FieldElement64`
/// or `FieldElement32`.
/// `curve25519_dalek::field::FieldElement` to either `FieldElement51`
/// or `FieldElement2625`.
///
/// The backend-specific type `FieldElement32` should not be used
/// The backend-specific type `FieldElement2625` should not be used
/// outside of the `curve25519_dalek::field` module.
#[derive(Copy, Clone)]
pub struct FieldElement32(pub (crate) [u32; 10]);
pub struct FieldElement2625(pub (crate) [u32; 10]);
impl Debug for FieldElement32 {
impl Debug for FieldElement2625 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "FieldElement32({:?})", &self.0[..])
write!(f, "FieldElement2625({:?})", &self.0[..])
}
}
impl<'b> AddAssign<&'b FieldElement32> for FieldElement32 {
fn add_assign(&mut self, _rhs: &'b FieldElement32) {
impl<'b> AddAssign<&'b FieldElement2625> for FieldElement2625 {
fn add_assign(&mut self, _rhs: &'b FieldElement2625) {
for i in 0..10 {
self.0[i] += _rhs.0[i];
}
}
}
impl<'a, 'b> Add<&'b FieldElement32> for &'a FieldElement32 {
type Output = FieldElement32;
fn add(self, _rhs: &'b FieldElement32) -> FieldElement32 {
impl<'a, 'b> Add<&'b FieldElement2625> for &'a FieldElement2625 {
type Output = FieldElement2625;
fn add(self, _rhs: &'b FieldElement2625) -> FieldElement2625 {
let mut output = *self;
output += _rhs;
output
}
}
impl<'b> SubAssign<&'b FieldElement32> for FieldElement32 {
fn sub_assign(&mut self, _rhs: &'b FieldElement32) {
// See comment in FieldElement64::Sub
impl<'b> SubAssign<&'b FieldElement2625> for FieldElement2625 {
fn sub_assign(&mut self, _rhs: &'b FieldElement2625) {
// See comment in FieldElement51::Sub
//
// Compute a - b as ((a + 2^4 * p) - b) to avoid underflow.
let b = &_rhs.0;
self.0 = FieldElement32::reduce([
self.0 = FieldElement2625::reduce([
((self.0[0] + (0x3ffffed << 4)) - b[0]) as u64,
((self.0[1] + (0x1ffffff << 4)) - b[1]) as u64,
((self.0[2] + (0x3ffffff << 4)) - b[2]) as u64,
@ -93,25 +93,25 @@ impl<'b> SubAssign<&'b FieldElement32> for FieldElement32 {
}
}
impl<'a, 'b> Sub<&'b FieldElement32> for &'a FieldElement32 {
type Output = FieldElement32;
fn sub(self, _rhs: &'b FieldElement32) -> FieldElement32 {
impl<'a, 'b> Sub<&'b FieldElement2625> for &'a FieldElement2625 {
type Output = FieldElement2625;
fn sub(self, _rhs: &'b FieldElement2625) -> FieldElement2625 {
let mut output = *self;
output -= _rhs;
output
}
}
impl<'b> MulAssign<&'b FieldElement32> for FieldElement32 {
fn mul_assign(&mut self, _rhs: &'b FieldElement32) {
let result = (self as &FieldElement32) * _rhs;
impl<'b> MulAssign<&'b FieldElement2625> for FieldElement2625 {
fn mul_assign(&mut self, _rhs: &'b FieldElement2625) {
let result = (self as &FieldElement2625) * _rhs;
self.0 = result.0;
}
}
impl<'a, 'b> Mul<&'b FieldElement32> for &'a FieldElement32 {
type Output = FieldElement32;
fn mul(self, _rhs: &'b FieldElement32) -> FieldElement32 {
impl<'a, 'b> Mul<&'b FieldElement2625> for &'a FieldElement2625 {
type Output = FieldElement2625;
fn mul(self, _rhs: &'b FieldElement2625) -> FieldElement2625 {
/// Helper function to multiply two 32-bit integers with 64 bits
/// of output.
#[inline(always)]
@ -206,26 +206,26 @@ impl<'a, 'b> Mul<&'b FieldElement32> for &'a FieldElement32 {
//
// So z[0] fits into a u64 if 51 + 2*b + lg(249) < 64
// if b < 2.5.
FieldElement32::reduce([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9])
FieldElement2625::reduce([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9])
}
}
impl<'a> Neg for &'a FieldElement32 {
type Output = FieldElement32;
fn neg(self) -> FieldElement32 {
impl<'a> Neg for &'a FieldElement2625 {
type Output = FieldElement2625;
fn neg(self) -> FieldElement2625 {
let mut output = *self;
output.negate();
output
}
}
impl ConditionallySelectable for FieldElement32 {
impl ConditionallySelectable for FieldElement2625 {
fn conditional_select(
a: &FieldElement32,
b: &FieldElement32,
a: &FieldElement2625,
b: &FieldElement2625,
choice: Choice,
) -> FieldElement32 {
FieldElement32([
) -> FieldElement2625 {
FieldElement2625([
u32::conditional_select(&a.0[0], &b.0[0], choice),
u32::conditional_select(&a.0[1], &b.0[1], choice),
u32::conditional_select(&a.0[2], &b.0[2], choice),
@ -239,7 +239,7 @@ impl ConditionallySelectable for FieldElement32 {
])
}
fn conditional_assign(&mut self, other: &FieldElement32, choice: Choice) {
fn conditional_assign(&mut self, other: &FieldElement2625, choice: Choice) {
self.0[0].conditional_assign(&other.0[0], choice);
self.0[1].conditional_assign(&other.0[1], choice);
self.0[2].conditional_assign(&other.0[2], choice);
@ -252,7 +252,7 @@ impl ConditionallySelectable for FieldElement32 {
self.0[9].conditional_assign(&other.0[9], choice);
}
fn conditional_swap(a: &mut FieldElement32, b: &mut FieldElement32, choice: Choice) {
fn conditional_swap(a: &mut FieldElement2625, b: &mut FieldElement2625, choice: Choice) {
u32::conditional_swap(&mut a.0[0], &mut b.0[0], choice);
u32::conditional_swap(&mut a.0[1], &mut b.0[1], choice);
u32::conditional_swap(&mut a.0[2], &mut b.0[2], choice);
@ -266,11 +266,11 @@ impl ConditionallySelectable for FieldElement32 {
}
}
impl FieldElement32 {
impl FieldElement2625 {
/// Invert the sign of this field element
pub fn negate(&mut self) {
// Compute -b as ((2^4 * p) - b) to avoid underflow.
let neg = FieldElement32::reduce([
let neg = FieldElement2625::reduce([
((0x3ffffed << 4) - self.0[0]) as u64,
((0x1ffffff << 4) - self.0[1]) as u64,
((0x3ffffff << 4) - self.0[2]) as u64,
@ -286,25 +286,25 @@ impl FieldElement32 {
}
/// Construct zero.
pub fn zero() -> FieldElement32 {
FieldElement32([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ])
pub fn zero() -> FieldElement2625 {
FieldElement2625([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ])
}
/// Construct one.
pub fn one() -> FieldElement32 {
FieldElement32([ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ])
pub fn one() -> FieldElement2625 {
FieldElement2625([ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ])
}
/// Construct -1.
pub fn minus_one() -> FieldElement32 {
FieldElement32([
pub fn minus_one() -> FieldElement2625 {
FieldElement2625([
0x3ffffec, 0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff,
0x1ffffff, 0x3ffffff, 0x1ffffff, 0x3ffffff, 0x1ffffff,
])
}
/// Given `k > 0`, return `self^(2^k)`.
pub fn pow2k(&self, k: u32) -> FieldElement32 {
pub fn pow2k(&self, k: u32) -> FieldElement2625 {
debug_assert!( k > 0 );
let mut z = self.square();
for _ in 1..k {
@ -314,12 +314,12 @@ impl FieldElement32 {
}
/// Given unreduced coefficients `z[0], ..., z[9]` of any size,
/// carry and reduce them mod p to obtain a `FieldElement32`
/// carry and reduce them mod p to obtain a `FieldElement2625`
/// whose coefficients have excess `b < 0.007`.
///
/// In other words, each coefficient of the result is bounded by
/// either `2^(25 + 0.007)` or `2^(26 + 0.007)`, as appropriate.
fn reduce(mut z: [u64; 10]) -> FieldElement32 {
fn reduce(mut z: [u64; 10]) -> FieldElement2625 {
const LOW_25_BITS: u64 = (1 << 25) - 1;
const LOW_26_BITS: u64 = (1 << 26) - 1;
@ -361,13 +361,13 @@ impl FieldElement32 {
// < 2^25.007 (good enough)
// and we're done.
FieldElement32([
FieldElement2625([
z[0] as u32, z[1] as u32, z[2] as u32, z[3] as u32, z[4] as u32,
z[5] as u32, z[6] as u32, z[7] as u32, z[8] as u32, z[9] as u32,
])
}
/// Load a `FieldElement64` from the low 255 bits of a 256-bit
/// Load a `FieldElement51` from the low 255 bits of a 256-bit
/// input.
///
/// # Warning
@ -378,7 +378,7 @@ impl FieldElement32 {
/// encoding of every field element should decode, re-encode to
/// the canonical encoding, and check that the input was
/// canonical.
pub fn from_bytes(data: &[u8; 32]) -> FieldElement32 { //FeFromBytes
pub fn from_bytes(data: &[u8; 32]) -> FieldElement2625 { //FeFromBytes
#[inline]
fn load3(b: &[u8]) -> u64 {
(b[0] as u64) | ((b[1] as u64) << 8) | ((b[2] as u64) << 16)
@ -402,16 +402,16 @@ impl FieldElement32 {
h[8] = load3(&data[26..]) << 4;
h[9] = (load3(&data[29..]) & LOW_23_BITS) << 2;
FieldElement32::reduce(h)
FieldElement2625::reduce(h)
}
/// Serialize this `FieldElement64` to a 32-byte array. The
/// Serialize this `FieldElement51` to a 32-byte array. The
/// encoding is canonical.
pub fn to_bytes(&self) -> [u8; 32] {
let inp = &self.0;
// Reduce the value represented by `in` to the range [0,2*p)
let mut h: [u32; 10] = FieldElement32::reduce([
let mut h: [u32; 10] = FieldElement2625::reduce([
// XXX this cast is annoying
inp[0] as u64, inp[1] as u64, inp[2] as u64, inp[3] as u64, inp[4] as u64,
inp[5] as u64, inp[6] as u64, inp[7] as u64, inp[8] as u64, inp[9] as u64,
@ -554,16 +554,16 @@ impl FieldElement32 {
}
/// Compute `self^2`.
pub fn square(&self) -> FieldElement32 {
FieldElement32::reduce(self.square_inner())
pub fn square(&self) -> FieldElement2625 {
FieldElement2625::reduce(self.square_inner())
}
/// Compute `2*self^2`.
pub fn square2(&self) -> FieldElement32 {
pub fn square2(&self) -> FieldElement2625 {
let mut coeffs = self.square_inner();
for i in 0..self.0.len() {
coeffs[i] += coeffs[i];
}
FieldElement32::reduce(coeffs)
FieldElement2625::reduce(coeffs)
}
}

View file

@ -15,24 +15,24 @@ use core::ops::{Index, IndexMut};
use constants;
/// The `Scalar32` struct represents an element in /l as 9 29-bit limbs
/// The `Scalar29` struct represents an element in /l as 9 29-bit limbs
#[derive(Copy,Clone)]
pub struct Scalar32(pub [u32; 9]);
pub struct Scalar29(pub [u32; 9]);
impl Debug for Scalar32 {
impl Debug for Scalar29 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "Scalar32: {:?}", &self.0[..])
write!(f, "Scalar29: {:?}", &self.0[..])
}
}
impl Index<usize> for Scalar32 {
impl Index<usize> for Scalar29 {
type Output = u32;
fn index(&self, _index: usize) -> &u32 {
&(self.0[_index])
}
}
impl IndexMut<usize> for Scalar32 {
impl IndexMut<usize> for Scalar29 {
fn index_mut(&mut self, _index: usize) -> &mut u32 {
&mut (self.0[_index])
}
@ -44,14 +44,14 @@ fn m(x: u32, y: u32) -> u64 {
(x as u64) * (y as u64)
}
impl Scalar32 {
impl Scalar29 {
/// Return the zero scalar.
pub fn zero() -> Scalar32 {
Scalar32([0,0,0,0,0,0,0,0,0])
pub fn zero() -> Scalar29 {
Scalar29([0,0,0,0,0,0,0,0,0])
}
/// Unpack a 32 byte / 256 bit scalar into 9 29-bit limbs.
pub fn from_bytes(bytes: &[u8; 32]) -> Scalar32 {
pub fn from_bytes(bytes: &[u8; 32]) -> Scalar29 {
let mut words = [0u32; 8];
for i in 0..8 {
for j in 0..4 {
@ -61,7 +61,7 @@ impl Scalar32 {
let mask = (1u32 << 29) - 1;
let top_mask = (1u32 << 24) - 1;
let mut s = Scalar32::zero();
let mut s = Scalar29::zero();
s[ 0] = words[0] & mask;
s[ 1] = ((words[0] >> 29) | (words[1] << 3)) & mask;
@ -77,7 +77,7 @@ impl Scalar32 {
}
/// Reduce a 64 byte / 512 bit scalar mod l.
pub fn from_bytes_wide(bytes: &[u8; 64]) -> Scalar32 {
pub fn from_bytes_wide(bytes: &[u8; 64]) -> Scalar29 {
let mut words = [0u32; 16];
for i in 0..16 {
for j in 0..4 {
@ -86,8 +86,8 @@ impl Scalar32 {
}
let mask = (1u32 << 29) - 1;
let mut lo = Scalar32::zero();
let mut hi = Scalar32::zero();
let mut lo = Scalar29::zero();
let mut hi = Scalar29::zero();
lo[0] = words[ 0] & mask;
lo[1] = ((words[ 0] >> 29) | (words[ 1] << 3)) & mask;
@ -108,13 +108,13 @@ impl Scalar32 {
hi[7] = ((words[14] >> 16) | (words[15] << 16)) & mask;
hi[8] = words[15] >> 13 ;
lo = Scalar32::montgomery_mul(&lo, &constants::R); // (lo * R) / R = lo
hi = Scalar32::montgomery_mul(&hi, &constants::RR); // (hi * R^2) / R = hi * R
lo = Scalar29::montgomery_mul(&lo, &constants::R); // (lo * R) / R = lo
hi = Scalar29::montgomery_mul(&hi, &constants::RR); // (hi * R^2) / R = hi * R
Scalar32::add(&hi, &lo) // (hi * R) + lo
Scalar29::add(&hi, &lo) // (hi * R) + lo
}
/// Pack the limbs of this `Scalar32` into 32 bytes.
/// Pack the limbs of this `Scalar29` into 32 bytes.
pub fn to_bytes(&self) -> [u8; 32] {
let mut s = [0u8; 32];
@ -155,8 +155,8 @@ impl Scalar32 {
}
/// Compute `a + b` (mod l).
pub fn add(a: &Scalar32, b: &Scalar32) -> Scalar32 {
let mut sum = Scalar32::zero();
pub fn add(a: &Scalar29, b: &Scalar29) -> Scalar29 {
let mut sum = Scalar29::zero();
let mask = (1u32 << 29) - 1;
// a + b
@ -167,12 +167,12 @@ impl Scalar32 {
}
// subtract l if the sum is >= l
Scalar32::sub(&sum, &constants::L)
Scalar29::sub(&sum, &constants::L)
}
/// Compute `a - b` (mod l).
pub fn sub(a: &Scalar32, b: &Scalar32) -> Scalar32 {
let mut difference = Scalar32::zero();
pub fn sub(a: &Scalar29, b: &Scalar29) -> Scalar29 {
let mut difference = Scalar29::zero();
let mask = (1u32 << 29) - 1;
// a - b
@ -197,7 +197,7 @@ impl Scalar32 {
///
/// This is implemented with a one-level refined Karatsuba decomposition
#[inline(always)]
pub (crate) fn mul_internal(a: &Scalar32, b: &Scalar32) -> [u64; 17] {
pub (crate) fn mul_internal(a: &Scalar29, b: &Scalar29) -> [u64; 17] {
let mut z = [0u64; 17];
z[0] = m(a[0],b[0]); // c00
@ -254,7 +254,7 @@ impl Scalar32 {
/// Compute `a^2`.
#[inline(always)]
fn square_internal(a: &Scalar32) -> [u64; 17] {
fn square_internal(a: &Scalar29) -> [u64; 17] {
let aa = [
a[0]*2,
a[1]*2,
@ -289,7 +289,7 @@ impl Scalar32 {
/// Compute `limbs/R` (mod l), where R is the Montgomery modulus 2^261
#[inline(always)]
pub (crate) fn montgomery_reduce(limbs: &[u64; 17]) -> Scalar32 {
pub (crate) fn montgomery_reduce(limbs: &[u64; 17]) -> Scalar29 {
#[inline(always)]
fn part1(sum: u64) -> (u64, u32) {
@ -329,49 +329,49 @@ impl Scalar32 {
let r8 = carry as u32;
// result may be >= l, so attempt to subtract l
Scalar32::sub(&Scalar32([r0,r1,r2,r3,r4,r5,r6,r7,r8]), l)
Scalar29::sub(&Scalar29([r0,r1,r2,r3,r4,r5,r6,r7,r8]), l)
}
/// Compute `a * b` (mod l).
#[inline(never)]
pub fn mul(a: &Scalar32, b: &Scalar32) -> Scalar32 {
let ab = Scalar32::montgomery_reduce(&Scalar32::mul_internal(a, b));
Scalar32::montgomery_reduce(&Scalar32::mul_internal(&ab, &constants::RR))
pub fn mul(a: &Scalar29, b: &Scalar29) -> Scalar29 {
let ab = Scalar29::montgomery_reduce(&Scalar29::mul_internal(a, b));
Scalar29::montgomery_reduce(&Scalar29::mul_internal(&ab, &constants::RR))
}
/// Compute `a^2` (mod l).
#[inline(never)]
#[allow(dead_code)] // XXX we don't expose square() via the Scalar API
pub fn square(&self) -> Scalar32 {
let aa = Scalar32::montgomery_reduce(&Scalar32::square_internal(self));
Scalar32::montgomery_reduce(&Scalar32::mul_internal(&aa, &constants::RR))
pub fn square(&self) -> Scalar29 {
let aa = Scalar29::montgomery_reduce(&Scalar29::square_internal(self));
Scalar29::montgomery_reduce(&Scalar29::mul_internal(&aa, &constants::RR))
}
/// Compute `(a * b) / R` (mod l), where R is the Montgomery modulus 2^261
#[inline(never)]
pub fn montgomery_mul(a: &Scalar32, b: &Scalar32) -> Scalar32 {
Scalar32::montgomery_reduce(&Scalar32::mul_internal(a, b))
pub fn montgomery_mul(a: &Scalar29, b: &Scalar29) -> Scalar29 {
Scalar29::montgomery_reduce(&Scalar29::mul_internal(a, b))
}
/// Compute `(a^2) / R` (mod l) in Montgomery form, where R is the Montgomery modulus 2^261
#[inline(never)]
pub fn montgomery_square(&self) -> Scalar32 {
Scalar32::montgomery_reduce(&Scalar32::square_internal(self))
pub fn montgomery_square(&self) -> Scalar29 {
Scalar29::montgomery_reduce(&Scalar29::square_internal(self))
}
/// Puts a Scalar32 in to Montgomery form, i.e. computes `a*R (mod l)`
/// Puts a Scalar29 in to Montgomery form, i.e. computes `a*R (mod l)`
#[inline(never)]
pub fn to_montgomery(&self) -> Scalar32 {
Scalar32::montgomery_mul(self, &constants::RR)
pub fn to_montgomery(&self) -> Scalar29 {
Scalar29::montgomery_mul(self, &constants::RR)
}
/// Takes a Scalar32 out of Montgomery form, i.e. computes `a/R (mod l)`
pub fn from_montgomery(&self) -> Scalar32 {
/// Takes a Scalar29 out of Montgomery form, i.e. computes `a/R (mod l)`
pub fn from_montgomery(&self) -> Scalar29 {
let mut limbs = [0u64; 17];
for i in 0..9 {
limbs[i] = self[i] as u64;
}
Scalar32::montgomery_reduce(&limbs)
Scalar29::montgomery_reduce(&limbs)
}
}
@ -385,69 +385,69 @@ mod test {
/// x = 2^253-1 = 14474011154664524427946373126085988481658748083205070504932198000989141204991
/// x = 7237005577332262213973186563042994240801631723825162898930247062703686954002 mod l
/// x = 5147078182513738803124273553712992179887200054963030844803268920753008712037*R mod l in Montgomery form
pub static X: Scalar32 = Scalar32(
pub static X: Scalar29 = Scalar29(
[0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff,
0x1fffffff, 0x1fffffff, 0x1fffffff, 0x1fffffff,
0x001fffff]);
/// x^2 = 3078544782642840487852506753550082162405942681916160040940637093560259278169 mod l
pub static XX: Scalar32 = Scalar32(
pub static XX: Scalar29 = Scalar29(
[0x00217559, 0x000b3401, 0x103ff43b, 0x1462a62c,
0x1d6f9f38, 0x18e7a42f, 0x09a3dcee, 0x008dbe18,
0x0006ce65]);
/// x^2 = 2912514428060642753613814151688322857484807845836623976981729207238463947987*R mod l in Montgomery form
pub static XX_MONT: Scalar32 = Scalar32(
pub static XX_MONT: Scalar29 = Scalar29(
[0x152b4d2e, 0x0571d53b, 0x1da6d964, 0x188663b6,
0x1d1b5f92, 0x19d50e3f, 0x12306c29, 0x0c6f26fe,
0x00030edb]);
/// y = 6145104759870991071742105800796537629880401874866217824609283457819451087098
pub static Y: Scalar32 = Scalar32(
pub static Y: Scalar29 = Scalar29(
[0x1e1458fa, 0x165ba838, 0x1d787b36, 0x0e577f3a,
0x1d2baf06, 0x1d689a19, 0x1fff3047, 0x117704ab,
0x000d9601]);
/// x*y = 36752150652102274958925982391442301741
pub static XY: Scalar32 = Scalar32(
pub static XY: Scalar29 = Scalar29(
[0x0ba7632d, 0x017736bb, 0x15c76138, 0x0c69daa1,
0x000001ba, 0x00000000, 0x00000000, 0x00000000,
0x00000000]);
/// x*y = 3783114862749659543382438697751927473898937741870308063443170013240655651591*R mod l in Montgomery form
pub static XY_MONT: Scalar32 = Scalar32(
pub static XY_MONT: Scalar29 = Scalar29(
[0x077b51e1, 0x1c64e119, 0x02a19ef5, 0x18d2129e,
0x00de0430, 0x045a7bc8, 0x04cfc7c9, 0x1c002681,
0x000bdc1c]);
/// a = 2351415481556538453565687241199399922945659411799870114962672658845158063753
pub static A: Scalar32 = Scalar32(
pub static A: Scalar29 = Scalar29(
[0x07b3be89, 0x02291b60, 0x14a99f03, 0x07dc3787,
0x0a782aae, 0x16262525, 0x0cfdb93f, 0x13f5718d,
0x000532da]);
/// b = 4885590095775723760407499321843594317911456947580037491039278279440296187236
pub static B: Scalar32 = Scalar32(
pub static B: Scalar29 = Scalar29(
[0x15421564, 0x1e69fd72, 0x093d9692, 0x161785be,
0x1587d69f, 0x09d9dada, 0x130246c0, 0x0c0a8e72,
0x000acd25]);
/// a+b = 0
/// a-b = 4702830963113076907131374482398799845891318823599740229925345317690316127506
pub static AB: Scalar32 = Scalar32(
pub static AB: Scalar29 = Scalar29(
[0x0f677d12, 0x045236c0, 0x09533e06, 0x0fb86f0f,
0x14f0555c, 0x0c4c4a4a, 0x19fb727f, 0x07eae31a,
0x000a65b5]);
// c = (2^512 - 1) % l = 1627715501170711445284395025044413883736156588369414752970002579683115011840
pub static C: Scalar32 = Scalar32(
pub static C: Scalar29 = Scalar29(
[0x049c0f00, 0x00308f1a, 0x0164d1e9, 0x1c374ed1,
0x1be65d00, 0x19e90bfa, 0x08f73bb1, 0x036f8613,
0x00039941]);
#[test]
fn mul_max() {
let res = Scalar32::mul(&X, &X);
let res = Scalar29::mul(&X, &X);
for i in 0..9 {
assert!(res[i] == XX[i]);
}
@ -463,7 +463,7 @@ mod test {
#[test]
fn montgomery_mul_max() {
let res = Scalar32::montgomery_mul(&X, &X);
let res = Scalar29::montgomery_mul(&X, &X);
for i in 0..9 {
assert!(res[i] == XX_MONT[i]);
}
@ -479,7 +479,7 @@ mod test {
#[test]
fn mul() {
let res = Scalar32::mul(&X, &Y);
let res = Scalar29::mul(&X, &Y);
for i in 0..9 {
assert!(res[i] == XY[i]);
}
@ -487,7 +487,7 @@ mod test {
#[test]
fn montgomery_mul() {
let res = Scalar32::montgomery_mul(&X, &Y);
let res = Scalar29::montgomery_mul(&X, &Y);
for i in 0..9 {
assert!(res[i] == XY_MONT[i]);
}
@ -495,8 +495,8 @@ mod test {
#[test]
fn add() {
let res = Scalar32::add(&A, &B);
let zero = Scalar32::zero();
let res = Scalar29::add(&A, &B);
let zero = Scalar29::zero();
for i in 0..9 {
assert!(res[i] == zero[i]);
}
@ -504,7 +504,7 @@ mod test {
#[test]
fn sub() {
let res = Scalar32::sub(&A, &B);
let res = Scalar29::sub(&A, &B);
for i in 0..9 {
assert!(res[i] == AB[i]);
}
@ -513,7 +513,7 @@ mod test {
#[test]
fn from_bytes_wide() {
let bignum = [255u8; 64]; // 2^512 - 1
let reduced = Scalar32::from_bytes_wide(&bignum);
let reduced = Scalar29::from_bytes_wide(&bignum);
for i in 0..9 {
assert!(reduced[i] == C[i]);
}

View file

@ -10,43 +10,43 @@
//! This module contains backend-specific constant values, such as the 64-bit limbs of curve constants.
use backend::u64::field::FieldElement64;
use backend::u64::scalar::Scalar64;
use backend::serial::u64::field::FieldElement51;
use backend::serial::u64::scalar::Scalar52;
use edwards::EdwardsPoint;
/// Edwards `d` value, equal to `-121665/121666 mod p`.
pub(crate) const EDWARDS_D: FieldElement64 = FieldElement64([929955233495203, 466365720129213, 1662059464998953, 2033849074728123, 1442794654840575]);
pub(crate) const EDWARDS_D: FieldElement51 = FieldElement51([929955233495203, 466365720129213, 1662059464998953, 2033849074728123, 1442794654840575]);
/// Edwards `2*d` value, equal to `2*(-121665/121666) mod p`.
pub(crate) const EDWARDS_D2: FieldElement64 = FieldElement64([1859910466990425, 932731440258426, 1072319116312658, 1815898335770999, 633789495995903]);
pub(crate) const EDWARDS_D2: FieldElement51 = FieldElement51([1859910466990425, 932731440258426, 1072319116312658, 1815898335770999, 633789495995903]);
/// `= sqrt(a*d - 1)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters.
pub(crate) const SQRT_AD_MINUS_ONE: FieldElement64 = FieldElement64([
pub(crate) const SQRT_AD_MINUS_ONE: FieldElement51 = FieldElement51([
2241493124984347, 425987919032274, 2207028919301688, 1220490630685848, 974799131293748
]);
/// `= 1/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters.
pub(crate) const INVSQRT_A_MINUS_D: FieldElement64 = FieldElement64([
pub(crate) const INVSQRT_A_MINUS_D: FieldElement51 = FieldElement51([
278908739862762, 821645201101625, 8113234426968, 1777959178193151, 2118520810568447
]);
/// Precomputed value of one of the square roots of -1 (mod p)
pub(crate) const SQRT_M1: FieldElement64 = FieldElement64([1718705420411056, 234908883556509, 2233514472574048, 2117202627021982, 765476049583133]);
pub(crate) const SQRT_M1: FieldElement51 = FieldElement51([1718705420411056, 234908883556509, 2233514472574048, 2117202627021982, 765476049583133]);
/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within the Montgomery ladder.)
pub(crate) const APLUS2_OVER_FOUR: FieldElement64 = FieldElement64([121666, 0, 0, 0, 0]);
pub(crate) const APLUS2_OVER_FOUR: FieldElement51 = FieldElement51([121666, 0, 0, 0, 0]);
/// `L` is the order of base point, i.e. 2^252 + 27742317777372353535851937790883648493
pub(crate) const L: Scalar64 = Scalar64([ 0x0002631a5cf5d3ed, 0x000dea2f79cd6581, 0x000000000014def9, 0x0000000000000000, 0x0000100000000000 ]);
pub(crate) const L: Scalar52 = Scalar52([ 0x0002631a5cf5d3ed, 0x000dea2f79cd6581, 0x000000000014def9, 0x0000000000000000, 0x0000100000000000 ]);
/// `L` * `LFACTOR` = -1 (mod 2^52)
pub(crate) const LFACTOR: u64 = 0x51da312547e1b;
/// `R` = R % L where R = 2^260
pub(crate) const R: Scalar64 = Scalar64([ 0x000f48bd6721e6ed, 0x0003bab5ac67e45a, 0x000fffffeb35e51b, 0x000fffffffffffff, 0x00000fffffffffff ]);
pub(crate) const R: Scalar52 = Scalar52([ 0x000f48bd6721e6ed, 0x0003bab5ac67e45a, 0x000fffffeb35e51b, 0x000fffffffffffff, 0x00000fffffffffff ]);
/// `RR` = (R^2) % L where R = 2^260
pub(crate) const RR: Scalar64 = Scalar64([ 0x0009d265e952d13b, 0x000d63c715bea69f, 0x0005be65cb687604, 0x0003dceec73d217f, 0x000009411b7c309a ]);
pub(crate) const RR: Scalar52 = Scalar52([ 0x0009d265e952d13b, 0x000d63c715bea69f, 0x0005be65cb687604, 0x0003dceec73d217f, 0x000009411b7c309a ]);
/// The Ed25519 basepoint, as an `EdwardsPoint`.
///
@ -54,10 +54,10 @@ pub(crate) const RR: Scalar64 = Scalar64([ 0x0009d265e952d13b, 0x000d63c715bea69
/// `ED25519_BASEPOINT_TABLE`, which should be used for scalar
/// multiplication (it's much faster).
pub const ED25519_BASEPOINT_POINT: EdwardsPoint = EdwardsPoint{
X: FieldElement64([1738742601995546, 1146398526822698, 2070867633025821, 562264141797630, 587772402128613]),
Y: FieldElement64([1801439850948184, 1351079888211148, 450359962737049, 900719925474099, 1801439850948198]),
Z: FieldElement64([1, 0, 0, 0, 0]),
T: FieldElement64([1841354044333475, 16398895984059, 755974180946558, 900171276175154, 1821297809914039]),
X: FieldElement51([1738742601995546, 1146398526822698, 2070867633025821, 562264141797630, 587772402128613]),
Y: FieldElement51([1801439850948184, 1351079888211148, 450359962737049, 900719925474099, 1801439850948198]),
Z: FieldElement51([1, 0, 0, 0, 0]),
T: FieldElement51([1841354044333475, 16398895984059, 755974180946558, 900171276175154, 1821297809914039]),
};
/// The 8-torsion subgroup \\(\mathcal E [8]\\).
@ -74,58 +74,58 @@ pub const EIGHT_TORSION: [EdwardsPoint; 8] = EIGHT_TORSION_INNER_DOC_HIDDEN;
#[doc(hidden)]
pub const EIGHT_TORSION_INNER_DOC_HIDDEN: [EdwardsPoint; 8] = [
EdwardsPoint {
X: FieldElement64([0, 0, 0, 0, 0]),
Y: FieldElement64([1, 0, 0, 0, 0]),
Z: FieldElement64([1, 0, 0, 0, 0]),
T: FieldElement64([0, 0, 0, 0, 0]),
X: FieldElement51([0, 0, 0, 0, 0]),
Y: FieldElement51([1, 0, 0, 0, 0]),
Z: FieldElement51([1, 0, 0, 0, 0]),
T: FieldElement51([0, 0, 0, 0, 0]),
}
,
EdwardsPoint {
X: FieldElement64([358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676]),
Y: FieldElement64([84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972]),
Z: FieldElement64([1, 0, 0, 0, 0]),
T: FieldElement64([1448326834587521, 1857896831960481, 1093722731865333, 1677408490711241, 1915505153018406]),
X: FieldElement51([358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676]),
Y: FieldElement51([84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972]),
Z: FieldElement51([1, 0, 0, 0, 0]),
T: FieldElement51([1448326834587521, 1857896831960481, 1093722731865333, 1677408490711241, 1915505153018406]),
}
,
EdwardsPoint {
X: FieldElement64([533094393274173, 2016890930128738, 18285341111199, 134597186663265, 1486323764102114]),
Y: FieldElement64([0, 0, 0, 0, 0]),
Z: FieldElement64([1, 0, 0, 0, 0]),
T: FieldElement64([0, 0, 0, 0, 0]),
X: FieldElement51([533094393274173, 2016890930128738, 18285341111199, 134597186663265, 1486323764102114]),
Y: FieldElement51([0, 0, 0, 0, 0]),
Z: FieldElement51([1, 0, 0, 0, 0]),
T: FieldElement51([0, 0, 0, 0, 0]),
}
,
EdwardsPoint {
X: FieldElement64([358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676]),
Y: FieldElement64([2166873539340326, 1778179147085316, 1886209374839743, 1223329526802818, 105300633354275]),
Z: FieldElement64([1, 0, 0, 0, 0]),
T: FieldElement64([803472979097708, 393902981724766, 1158077081819914, 574391322974006, 336294660666841]),
X: FieldElement51([358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676]),
Y: FieldElement51([2166873539340326, 1778179147085316, 1886209374839743, 1223329526802818, 105300633354275]),
Z: FieldElement51([1, 0, 0, 0, 0]),
T: FieldElement51([803472979097708, 393902981724766, 1158077081819914, 574391322974006, 336294660666841]),
}
,
EdwardsPoint {
X: FieldElement64([0, 0, 0, 0, 0]),
Y: FieldElement64([2251799813685228, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247]),
Z: FieldElement64([1, 0, 0, 0, 0]),
T: FieldElement64([0, 0, 0, 0, 0]),
X: FieldElement51([0, 0, 0, 0, 0]),
Y: FieldElement51([2251799813685228, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247]),
Z: FieldElement51([1, 0, 0, 0, 0]),
T: FieldElement51([0, 0, 0, 0, 0]),
}
,
EdwardsPoint {
X: FieldElement64([1893055065632419, 560215195444267, 1274149604399886, 821933901047523, 1691754969406571]),
Y: FieldElement64([2166873539340326, 1778179147085316, 1886209374839743, 1223329526802818, 105300633354275]),
Z: FieldElement64([1, 0, 0, 0, 0]),
T: FieldElement64([1448326834587521, 1857896831960481, 1093722731865333, 1677408490711241, 1915505153018406]),
X: FieldElement51([1893055065632419, 560215195444267, 1274149604399886, 821933901047523, 1691754969406571]),
Y: FieldElement51([2166873539340326, 1778179147085316, 1886209374839743, 1223329526802818, 105300633354275]),
Z: FieldElement51([1, 0, 0, 0, 0]),
T: FieldElement51([1448326834587521, 1857896831960481, 1093722731865333, 1677408490711241, 1915505153018406]),
}
,
EdwardsPoint {
X: FieldElement64([1718705420411056, 234908883556509, 2233514472574048, 2117202627021982, 765476049583133]),
Y: FieldElement64([0, 0, 0, 0, 0]),
Z: FieldElement64([1, 0, 0, 0, 0]),
T: FieldElement64([0, 0, 0, 0, 0]),
X: FieldElement51([1718705420411056, 234908883556509, 2233514472574048, 2117202627021982, 765476049583133]),
Y: FieldElement51([0, 0, 0, 0, 0]),
Z: FieldElement51([1, 0, 0, 0, 0]),
T: FieldElement51([0, 0, 0, 0, 0]),
}
,
EdwardsPoint {
X: FieldElement64([1893055065632419, 560215195444267, 1274149604399886, 821933901047523, 1691754969406571]),
Y: FieldElement64([84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972]),
Z: FieldElement64([1, 0, 0, 0, 0]),
T: FieldElement64([803472979097708, 393902981724766, 1158077081819914, 574391322974006, 336294660666841]),
X: FieldElement51([1893055065632419, 560215195444267, 1274149604399886, 821933901047523, 1691754969406571]),
Y: FieldElement51([84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972]),
Z: FieldElement51([1, 0, 0, 0, 0]),
T: FieldElement51([803472979097708, 393902981724766, 1158077081819914, 574391322974006, 336294660666841]),
}
];

View file

@ -20,7 +20,7 @@ use core::ops::{Sub, SubAssign};
use subtle::Choice;
use subtle::ConditionallySelectable;
/// A `FieldElement64` represents an element of the field
/// A `FieldElement51` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// In the 64-bit implementation, a `FieldElement` is represented in
@ -30,57 +30,57 @@ use subtle::ConditionallySelectable;
/// # Note
///
/// The `curve25519_dalek::field` module provides a type alias
/// `curve25519_dalek::field::FieldElement` to either `FieldElement64`
/// or `FieldElement32`.
/// `curve25519_dalek::field::FieldElement` to either `FieldElement51`
/// or `FieldElement2625`.
///
/// The backend-specific type `FieldElement64` should not be used
/// The backend-specific type `FieldElement51` should not be used
/// outside of the `curve25519_dalek::field` module.
#[derive(Copy, Clone)]
pub struct FieldElement64(pub (crate) [u64; 5]);
pub struct FieldElement51(pub (crate) [u64; 5]);
impl Debug for FieldElement64 {
impl Debug for FieldElement51 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "FieldElement64({:?})", &self.0[..])
write!(f, "FieldElement51({:?})", &self.0[..])
}
}
impl<'b> AddAssign<&'b FieldElement64> for FieldElement64 {
fn add_assign(&mut self, _rhs: &'b FieldElement64) {
impl<'b> AddAssign<&'b FieldElement51> for FieldElement51 {
fn add_assign(&mut self, _rhs: &'b FieldElement51) {
for i in 0..5 {
self.0[i] += _rhs.0[i];
}
}
}
impl<'a, 'b> Add<&'b FieldElement64> for &'a FieldElement64 {
type Output = FieldElement64;
fn add(self, _rhs: &'b FieldElement64) -> FieldElement64 {
impl<'a, 'b> Add<&'b FieldElement51> for &'a FieldElement51 {
type Output = FieldElement51;
fn add(self, _rhs: &'b FieldElement51) -> FieldElement51 {
let mut output = *self;
output += _rhs;
output
}
}
impl<'b> SubAssign<&'b FieldElement64> for FieldElement64 {
fn sub_assign(&mut self, _rhs: &'b FieldElement64) {
let result = (self as &FieldElement64) - _rhs;
impl<'b> SubAssign<&'b FieldElement51> for FieldElement51 {
fn sub_assign(&mut self, _rhs: &'b FieldElement51) {
let result = (self as &FieldElement51) - _rhs;
self.0 = result.0;
}
}
impl<'a, 'b> Sub<&'b FieldElement64> for &'a FieldElement64 {
type Output = FieldElement64;
fn sub(self, _rhs: &'b FieldElement64) -> FieldElement64 {
impl<'a, 'b> Sub<&'b FieldElement51> for &'a FieldElement51 {
type Output = FieldElement51;
fn sub(self, _rhs: &'b FieldElement51) -> FieldElement51 {
// To avoid underflow, first add a multiple of p.
// Choose 16*p = p << 4 to be larger than 54-bit _rhs.
//
// If we could statically track the bitlengths of the limbs
// of every FieldElement64, we could choose a multiple of p
// of every FieldElement51, we could choose a multiple of p
// just bigger than _rhs and avoid having to do a reduction.
//
// Since we don't yet have type-level integers to do this, we
// have to add an explicit reduction call here.
FieldElement64::reduce([
FieldElement51::reduce([
(self.0[0] + 36028797018963664u64) - _rhs.0[0],
(self.0[1] + 36028797018963952u64) - _rhs.0[1],
(self.0[2] + 36028797018963952u64) - _rhs.0[2],
@ -90,16 +90,16 @@ impl<'a, 'b> Sub<&'b FieldElement64> for &'a FieldElement64 {
}
}
impl<'b> MulAssign<&'b FieldElement64> for FieldElement64 {
fn mul_assign(&mut self, _rhs: &'b FieldElement64) {
let result = (self as &FieldElement64) * _rhs;
impl<'b> MulAssign<&'b FieldElement51> for FieldElement51 {
fn mul_assign(&mut self, _rhs: &'b FieldElement51) {
let result = (self as &FieldElement51) * _rhs;
self.0 = result.0;
}
}
impl<'a, 'b> Mul<&'b FieldElement64> for &'a FieldElement64 {
type Output = FieldElement64;
fn mul(self, _rhs: &'b FieldElement64) -> FieldElement64 {
impl<'a, 'b> Mul<&'b FieldElement51> for &'a FieldElement51 {
type Output = FieldElement51;
fn mul(self, _rhs: &'b FieldElement51) -> FieldElement51 {
/// Helper function to multiply two 64-bit integers with 128
/// bits of output.
#[inline(always)]
@ -196,26 +196,26 @@ impl<'a, 'b> Mul<&'b FieldElement64> for &'a FieldElement64 {
out[0] &= LOW_51_BIT_MASK;
// Now out[i] < 2^(51 + epsilon) for all i.
FieldElement64(out)
FieldElement51(out)
}
}
impl<'a> Neg for &'a FieldElement64 {
type Output = FieldElement64;
fn neg(self) -> FieldElement64 {
impl<'a> Neg for &'a FieldElement51 {
type Output = FieldElement51;
fn neg(self) -> FieldElement51 {
let mut output = *self;
output.negate();
output
}
}
impl ConditionallySelectable for FieldElement64 {
impl ConditionallySelectable for FieldElement51 {
fn conditional_select(
a: &FieldElement64,
b: &FieldElement64,
a: &FieldElement51,
b: &FieldElement51,
choice: Choice,
) -> FieldElement64 {
FieldElement64([
) -> FieldElement51 {
FieldElement51([
u64::conditional_select(&a.0[0], &b.0[0], choice),
u64::conditional_select(&a.0[1], &b.0[1], choice),
u64::conditional_select(&a.0[2], &b.0[2], choice),
@ -224,7 +224,7 @@ impl ConditionallySelectable for FieldElement64 {
])
}
fn conditional_swap(a: &mut FieldElement64, b: &mut FieldElement64, choice: Choice) {
fn conditional_swap(a: &mut FieldElement51, b: &mut FieldElement51, choice: Choice) {
u64::conditional_swap(&mut a.0[0], &mut b.0[0], choice);
u64::conditional_swap(&mut a.0[1], &mut b.0[1], choice);
u64::conditional_swap(&mut a.0[2], &mut b.0[2], choice);
@ -232,7 +232,7 @@ impl ConditionallySelectable for FieldElement64 {
u64::conditional_swap(&mut a.0[4], &mut b.0[4], choice);
}
fn conditional_assign(&mut self, other: &FieldElement64, choice: Choice) {
fn conditional_assign(&mut self, other: &FieldElement51, choice: Choice) {
self.0[0].conditional_assign(&other.0[0], choice);
self.0[1].conditional_assign(&other.0[1], choice);
self.0[2].conditional_assign(&other.0[2], choice);
@ -241,11 +241,11 @@ impl ConditionallySelectable for FieldElement64 {
}
}
impl FieldElement64 {
impl FieldElement51 {
/// Invert the sign of this field element
pub fn negate(&mut self) {
// See commentary in the Sub impl
let neg = FieldElement64::reduce([
let neg = FieldElement51::reduce([
36028797018963664u64 - self.0[0],
36028797018963952u64 - self.0[1],
36028797018963952u64 - self.0[2],
@ -256,23 +256,23 @@ impl FieldElement64 {
}
/// Construct zero.
pub fn zero() -> FieldElement64 {
FieldElement64([ 0, 0, 0, 0, 0 ])
pub fn zero() -> FieldElement51 {
FieldElement51([ 0, 0, 0, 0, 0 ])
}
/// Construct one.
pub fn one() -> FieldElement64 {
FieldElement64([ 1, 0, 0, 0, 0 ])
pub fn one() -> FieldElement51 {
FieldElement51([ 1, 0, 0, 0, 0 ])
}
/// Construct -1.
pub fn minus_one() -> FieldElement64 {
FieldElement64([2251799813685228, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247])
pub fn minus_one() -> FieldElement51 {
FieldElement51([2251799813685228, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247])
}
/// Given 64-bit input limbs, reduce to enforce the bound 2^(51 + epsilon).
#[inline(always)]
fn reduce(mut limbs: [u64; 5]) -> FieldElement64 {
fn reduce(mut limbs: [u64; 5]) -> FieldElement51 {
const LOW_51_BIT_MASK: u64 = (1u64 << 51) - 1;
// Since the input limbs are bounded by 2^64, the biggest
@ -304,10 +304,10 @@ impl FieldElement64 {
limbs[3] += c2;
limbs[4] += c3;
FieldElement64(limbs)
FieldElement51(limbs)
}
/// Load a `FieldElement64` from the low 255 bits of a 256-bit
/// Load a `FieldElement51` from the low 255 bits of a 256-bit
/// input.
///
/// # Warning
@ -319,7 +319,7 @@ impl FieldElement64 {
/// the canonical encoding, and check that the input was
/// canonical.
///
pub fn from_bytes(bytes: &[u8; 32]) -> FieldElement64 {
pub fn from_bytes(bytes: &[u8; 32]) -> FieldElement51 {
let load8 = |input: &[u8]| -> u64 {
(input[0] as u64)
| ((input[1] as u64) << 8)
@ -332,7 +332,7 @@ impl FieldElement64 {
};
let low_51_bit_mask = (1u64 << 51) - 1;
FieldElement64(
FieldElement51(
// load bits [ 0, 64), no shift
[ load8(&bytes[ 0..]) & low_51_bit_mask
// load bits [ 48,112), shift to [ 51,112)
@ -346,7 +346,7 @@ impl FieldElement64 {
])
}
/// Serialize this `FieldElement64` to a 32-byte array. The
/// Serialize this `FieldElement51` to a 32-byte array. The
/// encoding is canonical.
pub fn to_bytes(&self) -> [u8; 32] {
// Let h = limbs[0] + limbs[1]*2^51 + ... + limbs[4]*2^204.
@ -365,7 +365,7 @@ impl FieldElement64 {
// Therefore q can be computed as the carry bit of h + 19.
// First, reduce the limbs to ensure h < 2*p.
let mut limbs = FieldElement64::reduce(self.0).0;
let mut limbs = FieldElement51::reduce(self.0).0;
let mut q = (limbs[0] + 19) >> 51;
q = (limbs[1] + q) >> 51;
@ -433,7 +433,7 @@ impl FieldElement64 {
}
/// Given `k > 0`, return `self^(2^k)`.
pub fn pow2k(&self, mut k: u32) -> FieldElement64 {
pub fn pow2k(&self, mut k: u32) -> FieldElement51 {
debug_assert!( k > 0 );
@ -535,16 +535,16 @@ impl FieldElement64 {
}
}
FieldElement64(a)
FieldElement51(a)
}
/// Returns the square of this field element.
pub fn square(&self) -> FieldElement64 {
pub fn square(&self) -> FieldElement51 {
self.pow2k(1)
}
/// Returns 2 times the square of this field element.
pub fn square2(&self) -> FieldElement64 {
pub fn square2(&self) -> FieldElement51 {
let mut square = self.pow2k(1);
for i in 0..5 {
square.0[i] *= 2;

View file

@ -16,25 +16,25 @@ use core::ops::{Index, IndexMut};
use constants;
/// The `Scalar64` struct represents an element in
/// The `Scalar52` struct represents an element in
/// \\(\mathbb Z / \ell \mathbb Z\\) as 5 \\(52\\)-bit limbs.
#[derive(Copy,Clone)]
pub struct Scalar64(pub [u64; 5]);
pub struct Scalar52(pub [u64; 5]);
impl Debug for Scalar64 {
impl Debug for Scalar52 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "Scalar64: {:?}", &self.0[..])
write!(f, "Scalar52: {:?}", &self.0[..])
}
}
impl Index<usize> for Scalar64 {
impl Index<usize> for Scalar52 {
type Output = u64;
fn index(&self, _index: usize) -> &u64 {
&(self.0[_index])
}
}
impl IndexMut<usize> for Scalar64 {
impl IndexMut<usize> for Scalar52 {
fn index_mut(&mut self, _index: usize) -> &mut u64 {
&mut (self.0[_index])
}
@ -46,14 +46,14 @@ fn m(x: u64, y: u64) -> u128 {
(x as u128) * (y as u128)
}
impl Scalar64 {
impl Scalar52 {
/// Return the zero scalar
pub fn zero() -> Scalar64 {
Scalar64([0,0,0,0,0])
pub fn zero() -> Scalar52 {
Scalar52([0,0,0,0,0])
}
/// Unpack a 32 byte / 256 bit scalar into 5 52-bit limbs.
pub fn from_bytes(bytes: &[u8; 32]) -> Scalar64 {
pub fn from_bytes(bytes: &[u8; 32]) -> Scalar52 {
let mut words = [0u64; 4];
for i in 0..4 {
for j in 0..8 {
@ -63,7 +63,7 @@ impl Scalar64 {
let mask = (1u64 << 52) - 1;
let top_mask = (1u64 << 48) - 1;
let mut s = Scalar64::zero();
let mut s = Scalar52::zero();
s[ 0] = words[0] & mask;
s[ 1] = ((words[0] >> 52) | (words[1] << 12)) & mask;
@ -75,7 +75,7 @@ impl Scalar64 {
}
/// Reduce a 64 byte / 512 bit scalar mod l
pub fn from_bytes_wide(bytes: &[u8; 64]) -> Scalar64 {
pub fn from_bytes_wide(bytes: &[u8; 64]) -> Scalar52 {
let mut words = [0u64; 8];
for i in 0..8 {
for j in 0..8 {
@ -84,8 +84,8 @@ impl Scalar64 {
}
let mask = (1u64 << 52) - 1;
let mut lo = Scalar64::zero();
let mut hi = Scalar64::zero();
let mut lo = Scalar52::zero();
let mut hi = Scalar52::zero();
lo[0] = words[ 0] & mask;
lo[1] = ((words[ 0] >> 52) | (words[ 1] << 12)) & mask;
@ -98,13 +98,13 @@ impl Scalar64 {
hi[3] = ((words[ 6] >> 32) | (words[ 7] << 32)) & mask;
hi[4] = words[ 7] >> 20 ;
lo = Scalar64::montgomery_mul(&lo, &constants::R); // (lo * R) / R = lo
hi = Scalar64::montgomery_mul(&hi, &constants::RR); // (hi * R^2) / R = hi * R
lo = Scalar52::montgomery_mul(&lo, &constants::R); // (lo * R) / R = lo
hi = Scalar52::montgomery_mul(&hi, &constants::RR); // (hi * R^2) / R = hi * R
Scalar64::add(&hi, &lo)
Scalar52::add(&hi, &lo)
}
/// Pack the limbs of this `Scalar64` into 32 bytes
/// Pack the limbs of this `Scalar52` into 32 bytes
pub fn to_bytes(&self) -> [u8; 32] {
let mut s = [0u8; 32];
@ -145,8 +145,8 @@ impl Scalar64 {
}
/// Compute `a + b` (mod l)
pub fn add(a: &Scalar64, b: &Scalar64) -> Scalar64 {
let mut sum = Scalar64::zero();
pub fn add(a: &Scalar52, b: &Scalar52) -> Scalar52 {
let mut sum = Scalar52::zero();
let mask = (1u64 << 52) - 1;
// a + b
@ -157,12 +157,12 @@ impl Scalar64 {
}
// subtract l if the sum is >= l
Scalar64::sub(&sum, &constants::L)
Scalar52::sub(&sum, &constants::L)
}
/// Compute `a - b` (mod l)
pub fn sub(a: &Scalar64, b: &Scalar64) -> Scalar64 {
let mut difference = Scalar64::zero();
pub fn sub(a: &Scalar52, b: &Scalar52) -> Scalar52 {
let mut difference = Scalar52::zero();
let mask = (1u64 << 52) - 1;
// a - b
@ -185,7 +185,7 @@ impl Scalar64 {
/// Compute `a * b`
#[inline(always)]
pub (crate) fn mul_internal(a: &Scalar64, b: &Scalar64) -> [u128; 9] {
pub (crate) fn mul_internal(a: &Scalar52, b: &Scalar52) -> [u128; 9] {
let mut z = [0u128; 9];
z[0] = m(a[0],b[0]);
@ -203,7 +203,7 @@ impl Scalar64 {
/// Compute `a^2`
#[inline(always)]
fn square_internal(a: &Scalar64) -> [u128; 9] {
fn square_internal(a: &Scalar52) -> [u128; 9] {
let aa = [
a[0]*2,
a[1]*2,
@ -226,7 +226,7 @@ impl Scalar64 {
/// Compute `limbs/R` (mod l), where R is the Montgomery modulus 2^260
#[inline(always)]
pub (crate) fn montgomery_reduce(limbs: &[u128; 9]) -> Scalar64 {
pub (crate) fn montgomery_reduce(limbs: &[u128; 9]) -> Scalar52 {
#[inline(always)]
fn part1(sum: u128) -> (u128, u64) {
@ -258,50 +258,50 @@ impl Scalar64 {
let r4 = carry as u64;
// result may be >= l, so attempt to subtract l
Scalar64::sub(&Scalar64([r0,r1,r2,r3,r4]), l)
Scalar52::sub(&Scalar52([r0,r1,r2,r3,r4]), l)
}
/// Compute `a * b` (mod l)
#[inline(never)]
pub fn mul(a: &Scalar64, b: &Scalar64) -> Scalar64 {
let ab = Scalar64::montgomery_reduce(&Scalar64::mul_internal(a, b));
Scalar64::montgomery_reduce(&Scalar64::mul_internal(&ab, &constants::RR))
pub fn mul(a: &Scalar52, b: &Scalar52) -> Scalar52 {
let ab = Scalar52::montgomery_reduce(&Scalar52::mul_internal(a, b));
Scalar52::montgomery_reduce(&Scalar52::mul_internal(&ab, &constants::RR))
}
/// Compute `a^2` (mod l)
#[inline(never)]
#[allow(dead_code)] // XXX we don't expose square() via the Scalar API
pub fn square(&self) -> Scalar64 {
let aa = Scalar64::montgomery_reduce(&Scalar64::square_internal(self));
Scalar64::montgomery_reduce(&Scalar64::mul_internal(&aa, &constants::RR))
pub fn square(&self) -> Scalar52 {
let aa = Scalar52::montgomery_reduce(&Scalar52::square_internal(self));
Scalar52::montgomery_reduce(&Scalar52::mul_internal(&aa, &constants::RR))
}
/// Compute `(a * b) / R` (mod l), where R is the Montgomery modulus 2^260
#[inline(never)]
pub fn montgomery_mul(a: &Scalar64, b: &Scalar64) -> Scalar64 {
Scalar64::montgomery_reduce(&Scalar64::mul_internal(a, b))
pub fn montgomery_mul(a: &Scalar52, b: &Scalar52) -> Scalar52 {
Scalar52::montgomery_reduce(&Scalar52::mul_internal(a, b))
}
/// Compute `(a^2) / R` (mod l) in Montgomery form, where R is the Montgomery modulus 2^260
#[inline(never)]
pub fn montgomery_square(&self) -> Scalar64 {
Scalar64::montgomery_reduce(&Scalar64::square_internal(self))
pub fn montgomery_square(&self) -> Scalar52 {
Scalar52::montgomery_reduce(&Scalar52::square_internal(self))
}
/// Puts a Scalar64 in to Montgomery form, i.e. computes `a*R (mod l)`
/// Puts a Scalar52 in to Montgomery form, i.e. computes `a*R (mod l)`
#[inline(never)]
pub fn to_montgomery(&self) -> Scalar64 {
Scalar64::montgomery_mul(self, &constants::RR)
pub fn to_montgomery(&self) -> Scalar52 {
Scalar52::montgomery_mul(self, &constants::RR)
}
/// Takes a Scalar64 out of Montgomery form, i.e. computes `a/R (mod l)`
/// Takes a Scalar52 out of Montgomery form, i.e. computes `a/R (mod l)`
#[inline(never)]
pub fn from_montgomery(&self) -> Scalar64 {
pub fn from_montgomery(&self) -> Scalar52 {
let mut limbs = [0u128; 9];
for i in 0..5 {
limbs[i] = self[i] as u128;
}
Scalar64::montgomery_reduce(&limbs)
Scalar52::montgomery_reduce(&limbs)
}
}
@ -316,59 +316,59 @@ mod test {
/// x = 14474011154664524427946373126085988481658748083205070504932198000989141204991
/// x = 7237005577332262213973186563042994240801631723825162898930247062703686954002 mod l
/// x = 3057150787695215392275360544382990118917283750546154083604586903220563173085*R mod l in Montgomery form
pub static X: Scalar64 = Scalar64(
pub static X: Scalar52 = Scalar52(
[0x000fffffffffffff, 0x000fffffffffffff, 0x000fffffffffffff, 0x000fffffffffffff,
0x00001fffffffffff]);
/// x^2 = 3078544782642840487852506753550082162405942681916160040940637093560259278169 mod l
pub static XX: Scalar64 = Scalar64(
pub static XX: Scalar52 = Scalar52(
[0x0001668020217559, 0x000531640ffd0ec0, 0x00085fd6f9f38a31, 0x000c268f73bb1cf4,
0x000006ce65046df0]);
/// x^2 = 4413052134910308800482070043710297189082115023966588301924965890668401540959*R mod l in Montgomery form
pub static XX_MONT: Scalar64 = Scalar64(
pub static XX_MONT: Scalar52 = Scalar52(
[0x000c754eea569a5c, 0x00063b6ed36cb215, 0x0008ffa36bf25886, 0x000e9183614e7543,
0x0000061db6c6f26f]);
/// y = 6145104759870991071742105800796537629880401874866217824609283457819451087098
pub static Y: Scalar64 = Scalar64(
pub static Y: Scalar52 = Scalar52(
[0x000b75071e1458fa, 0x000bf9d75e1ecdac, 0x000433d2baf0672b, 0x0005fffcc11fad13,
0x00000d96018bb825]);
/// x*y = 36752150652102274958925982391442301741 mod l
pub static XY: Scalar64 = Scalar64(
pub static XY: Scalar52 = Scalar52(
[0x000ee6d76ba7632d, 0x000ed50d71d84e02, 0x00000000001ba634, 0x0000000000000000,
0x0000000000000000]);
/// x*y = 658448296334113745583381664921721413881518248721417041768778176391714104386*R mod l in Montgomery form
pub static XY_MONT: Scalar64 = Scalar64(
pub static XY_MONT: Scalar52 = Scalar52(
[0x0006d52bf200cfd5, 0x00033fb1d7021570, 0x000f201bc07139d8, 0x0001267e3e49169e,
0x000007b839c00268]);
/// a = 2351415481556538453565687241199399922945659411799870114962672658845158063753
pub static A: Scalar64 = Scalar64(
pub static A: Scalar52 = Scalar52(
[0x0005236c07b3be89, 0x0001bc3d2a67c0c4, 0x000a4aa782aae3ee, 0x0006b3f6e4fec4c4,
0x00000532da9fab8c]);
/// b = 4885590095775723760407499321843594317911456947580037491039278279440296187236
pub static B: Scalar64 = Scalar64(
pub static B: Scalar52 = Scalar52(
[0x000d3fae55421564, 0x000c2df24f65a4bc, 0x0005b5587d69fb0b, 0x00094c091b013b3b,
0x00000acd25605473]);
/// a+b = 0
/// a-b = 4702830963113076907131374482398799845891318823599740229925345317690316127506
pub static AB: Scalar64 = Scalar64(
pub static AB: Scalar52 = Scalar52(
[0x000a46d80f677d12, 0x0003787a54cf8188, 0x0004954f0555c7dc, 0x000d67edc9fd8989,
0x00000a65b53f5718]);
// c = (2^512 - 1) % l = 1627715501170711445284395025044413883736156588369414752970002579683115011840
pub static C: Scalar64 = Scalar64(
pub static C: Scalar52 = Scalar52(
[0x000611e3449c0f00, 0x000a768859347a40, 0x0007f5be65d00e1b, 0x0009a3dceec73d21,
0x00000399411b7c30]);
#[test]
fn mul_max() {
let res = Scalar64::mul(&X, &X);
let res = Scalar52::mul(&X, &X);
for i in 0..5 {
assert!(res[i] == XX[i]);
}
@ -384,7 +384,7 @@ mod test {
#[test]
fn montgomery_mul_max() {
let res = Scalar64::montgomery_mul(&X, &X);
let res = Scalar52::montgomery_mul(&X, &X);
for i in 0..5 {
assert!(res[i] == XX_MONT[i]);
}
@ -400,7 +400,7 @@ mod test {
#[test]
fn mul() {
let res = Scalar64::mul(&X, &Y);
let res = Scalar52::mul(&X, &Y);
for i in 0..5 {
assert!(res[i] == XY[i]);
}
@ -408,7 +408,7 @@ mod test {
#[test]
fn montgomery_mul() {
let res = Scalar64::montgomery_mul(&X, &Y);
let res = Scalar52::montgomery_mul(&X, &Y);
for i in 0..5 {
assert!(res[i] == XY_MONT[i]);
}
@ -416,8 +416,8 @@ mod test {
#[test]
fn add() {
let res = Scalar64::add(&A, &B);
let zero = Scalar64::zero();
let res = Scalar52::add(&A, &B);
let zero = Scalar52::zero();
for i in 0..5 {
assert!(res[i] == zero[i]);
}
@ -425,7 +425,7 @@ mod test {
#[test]
fn sub() {
let res = Scalar64::sub(&A, &B);
let res = Scalar52::sub(&A, &B);
for i in 0..5 {
assert!(res[i] == AB[i]);
}
@ -434,7 +434,7 @@ mod test {
#[test]
fn from_bytes_wide() {
let bignum = [255u8; 64]; // 2^512 - 1
let reduced = Scalar64::from_bytes_wide(&bignum);
let reduced = Scalar52::from_bytes_wide(&bignum);
println!("{:?}", reduced);
for i in 0..5 {
assert!(reduced[i] == C[i]);

View file

@ -1,149 +0,0 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Isis Agora Lovecruft <isis@patternsinthevoid.net>
// - Henry de Valence <hdevalence@hdevalence.ca>
//! This module contains various constants (such as curve parameters
//! and useful field elements like `sqrt(-1)`), as well as
//! lookup tables of pre-computed points.
use backend::u32::field::FieldElement32;
use backend::u32::scalar::Scalar32;
use edwards::EdwardsPoint;
/// Edwards `d` value, equal to `-121665/121666 mod p`.
pub(crate) const EDWARDS_D: FieldElement32 = FieldElement32([
56195235, 13857412, 51736253, 6949390, 114729,
24766616, 60832955, 30306712, 48412415, 21499315,
]);
/// Edwards `2*d` value, equal to `2*(-121665/121666) mod p`.
pub(crate) const EDWARDS_D2: FieldElement32 = FieldElement32([
45281625, 27714825, 36363642, 13898781, 229458,
15978800, 54557047, 27058993, 29715967, 9444199,
]);
/// `= sqrt(a*d - 1)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters.
pub(crate) const SQRT_AD_MINUS_ONE: FieldElement32 = FieldElement32([
24849947, 33400850, 43495378, 6347714, 46036536,
32887293, 41837720, 18186727, 66238516, 14525638,
]);
/// `= 1/sqrt(a-d)`, where `a = -1 (mod p)`, `d` are the Edwards curve parameters.
pub(crate) const INVSQRT_A_MINUS_D: FieldElement32 = FieldElement32([
6111466, 4156064, 39310137, 12243467, 41204824,
120896, 20826367, 26493656, 6093567, 31568420,
]);
/// Precomputed value of one of the square roots of -1 (mod p)
pub(crate) const SQRT_M1: FieldElement32 = FieldElement32([
34513072, 25610706, 9377949, 3500415, 12389472,
33281959, 41962654, 31548777, 326685, 11406482,
]);
/// `APLUS2_OVER_FOUR` is (A+2)/4. (This is used internally within the Montgomery ladder.)
pub(crate) const APLUS2_OVER_FOUR: FieldElement32 = FieldElement32([
121666, 0, 0, 0, 0, 0, 0, 0, 0, 0
]);
/// `L` is the order of base point, i.e. 2^252 +
/// 27742317777372353535851937790883648493
pub(crate) const L: Scalar32 = Scalar32([ 0x1cf5d3ed, 0x009318d2, 0x1de73596, 0x1df3bd45,
0x0000014d, 0x00000000, 0x00000000, 0x00000000,
0x00100000 ]);
/// `L` * `LFACTOR` = -1 (mod 2^29)
pub(crate) const LFACTOR: u32 = 0x12547e1b;
/// `R` = R % L where R = 2^261
pub(crate) const R: Scalar32 = Scalar32([ 0x114df9ed, 0x1a617303, 0x0f7c098c, 0x16793167,
0x1ffd656e, 0x1fffffff, 0x1fffffff, 0x1fffffff,
0x000fffff ]);
/// `RR` = (R^2) % L where R = 2^261
pub(crate) const RR: Scalar32 = Scalar32([ 0x0b5f9d12, 0x1e141b17, 0x158d7f3d, 0x143f3757,
0x1972d781, 0x042feb7c, 0x1ceec73d, 0x1e184d1e,
0x0005046d ]);
/// The Ed25519 basepoint, as an `EdwardsPoint`.
///
/// This is called `_POINT` to distinguish it from
/// `ED25519_BASEPOINT_TABLE`, which should be used for scalar
/// multiplication (it's much faster).
pub const ED25519_BASEPOINT_POINT: EdwardsPoint = EdwardsPoint{
X: FieldElement32([52811034, 25909283, 16144682, 17082669, 27570973, 30858332, 40966398, 8378388, 20764389, 8758491]),
Y: FieldElement32([40265304, 26843545, 13421772, 20132659, 26843545, 6710886, 53687091, 13421772, 40265318, 26843545]),
Z: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement32([28827043, 27438313, 39759291, 244362, 8635006, 11264893, 19351346, 13413597, 16611511, 27139452]),
};
/// The 8-torsion subgroup \\(\mathcal E [8]\\).
///
/// In the case of Curve25519, it is cyclic; the \\(i\\)-th element of
/// the array is \\([i]P\\), where \\(P\\) is a point of order \\(8\\)
/// generating \\(\mathcal E[8]\\).
///
/// Thus \\(\mathcal E[8]\\) is the points indexed by `0,2,4,6`, and
/// \\(\mathcal E[2]\\) is the points indexed by `0,4`.
/// The Ed25519 basepoint has y = 4/5. This is called `_POINT` to
/// distinguish it from `_TABLE`, which should be used for scalar
/// multiplication (it's much faster).
pub const EIGHT_TORSION: [EdwardsPoint; 8] = EIGHT_TORSION_INNER_DOC_HIDDEN;
/// Inner item used to hide limb constants from cargo doc output.
#[doc(hidden)]
pub const EIGHT_TORSION_INNER_DOC_HIDDEN: [EdwardsPoint; 8] = [
EdwardsPoint{
X: FieldElement32([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Y: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Z: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement32([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
},
EdwardsPoint{
X: FieldElement32([21352778, 5345713, 4660180, 25206575, 24143089, 14568123, 30185756, 21306662, 33579924, 8345318]),
Y: FieldElement32([6952903, 1265500, 60246523, 7057497, 4037696, 5447722, 35427965, 15325401, 19365852, 31985330]),
Z: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement32([41846657, 21581751, 11716001, 27684820, 48915701, 16297738, 20670665, 24995334, 3541542, 28543251])
},
EdwardsPoint{
X: FieldElement32([32595773, 7943725, 57730914, 30054016, 54719391, 272472, 25146209, 2005654, 66782178, 22147949]),
Y: FieldElement32([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Z: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement32([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
},
EdwardsPoint{
X: FieldElement32([21352778, 5345713, 4660180, 25206575, 24143089, 14568123, 30185756, 21306662, 33579924, 8345318]),
Y: FieldElement32([60155942, 32288931, 6862340, 26496934, 63071167, 28106709, 31680898, 18229030, 47743011, 1569101]),
Z: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement32([25262188, 11972680, 55392862, 5869611, 18193162, 17256693, 46438198, 8559097, 63567321, 5011180])
},
EdwardsPoint{
X: FieldElement32([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Y: FieldElement32([67108844, 33554431, 67108863, 33554431, 67108863, 33554431, 67108863, 33554431, 67108863, 33554431]),
Z: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement32([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
},
EdwardsPoint{
X: FieldElement32([45756067, 28208718, 62448683, 8347856, 42965774, 18986308, 36923107, 12247769, 33528939, 25209113]),
Y: FieldElement32([60155942, 32288931, 6862340, 26496934, 63071167, 28106709, 31680898, 18229030, 47743011, 1569101]),
Z: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement32([41846657, 21581751, 11716001, 27684820, 48915701, 16297738, 20670665, 24995334, 3541542, 28543251])
},
EdwardsPoint{
X: FieldElement32([34513072, 25610706, 9377949, 3500415, 12389472, 33281959, 41962654, 31548777, 326685, 11406482]),
Y: FieldElement32([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
Z: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement32([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
},
EdwardsPoint{
X: FieldElement32([45756067, 28208718, 62448683, 8347856, 42965774, 18986308, 36923107, 12247769, 33528939, 25209113]),
Y: FieldElement32([6952903, 1265500, 60246523, 7057497, 4037696, 5447722, 35427965, 15325401, 19365852, 31985330]),
Z: FieldElement32([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
T: FieldElement32([25262188, 11972680, 55392862, 5869611, 18193162, 17256693, 46438198, 8559097, 63567321, 5011180])
},
];

View file

@ -12,12 +12,12 @@
use packed_simd::u32x8;
use backend::avx2::edwards::{CachedPoint, ExtendedPoint};
use backend::avx2::field::FieldElement32x4;
use scalar_mul::window::NafLookupTable8;
use backend::vector::avx2::edwards::{CachedPoint, ExtendedPoint};
use backend::vector::avx2::field::FieldElement2625x4;
use window::NafLookupTable8;
/// The identity element as an `ExtendedPoint`.
pub(crate) static EXTENDEDPOINT_IDENTITY: ExtendedPoint = ExtendedPoint(FieldElement32x4([
pub(crate) static EXTENDEDPOINT_IDENTITY: ExtendedPoint = ExtendedPoint(FieldElement2625x4([
u32x8::new(0, 1, 0, 0, 1, 0, 0, 0),
u32x8::splat(0),
u32x8::splat(0),
@ -26,7 +26,7 @@ pub(crate) static EXTENDEDPOINT_IDENTITY: ExtendedPoint = ExtendedPoint(FieldEle
]));
/// The identity element as a `CachedPoint`.
pub(crate) static CACHEDPOINT_IDENTITY: CachedPoint = CachedPoint(FieldElement32x4([
pub(crate) static CACHEDPOINT_IDENTITY: CachedPoint = CachedPoint(FieldElement2625x4([
u32x8::new(121647, 121666, 0, 0, 243332, 67108845, 0, 33554431),
u32x8::new(67108864, 0, 33554431, 0, 0, 67108863, 0, 33554431),
u32x8::new(67108863, 0, 33554431, 0, 0, 67108863, 0, 33554431),
@ -35,7 +35,7 @@ pub(crate) static CACHEDPOINT_IDENTITY: CachedPoint = CachedPoint(FieldElement32
]));
/// The low limbs of (2p, 2p, 2p, 2p), so that
/// ```no_run
/// ```ascii,no_run
/// (2p, 2p, 2p, 2p) = [P_TIMES_2_LO, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI]
/// ```
pub(crate) static P_TIMES_2_LO: u32x8 = u32x8::new(
@ -50,7 +50,7 @@ pub(crate) static P_TIMES_2_LO: u32x8 = u32x8::new(
);
/// The high limbs of (2p, 2p, 2p, 2p), so that
/// ```no_run
/// ```ascii,no_run
/// (2p, 2p, 2p, 2p) = [P_TIMES_2_LO, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI, P_TIMES_2_HI]
/// ```
pub(crate) static P_TIMES_2_HI: u32x8 = u32x8::new(
@ -65,7 +65,7 @@ pub(crate) static P_TIMES_2_HI: u32x8 = u32x8::new(
);
/// The low limbs of (16p, 16p, 16p, 16p), so that
/// ```no_run
/// ```ascii,no_run
/// (16p, 16p, 16p, 16p) = [P_TIMES_16_LO, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI]
/// ```
pub(crate) static P_TIMES_16_LO: u32x8 = u32x8::new(
@ -80,7 +80,7 @@ pub(crate) static P_TIMES_16_LO: u32x8 = u32x8::new(
);
/// The high limbs of (16p, 16p, 16p, 16p), so that
/// ```no_run
/// ```ascii,no_run
/// (16p, 16p, 16p, 16p) = [P_TIMES_16_LO, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI, P_TIMES_16_HI]
/// ```
pub(crate) static P_TIMES_16_HI: u32x8 = u32x8::new(
@ -96,7 +96,7 @@ pub(crate) static P_TIMES_16_HI: u32x8 = u32x8::new(
/// Odd multiples of the Ed25519 basepoint:
pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = NafLookupTable8([
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
3571425,
10045002,
@ -148,7 +148,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
4846528,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
47099681,
31447946,
@ -200,7 +200,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
31366585,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
18147205,
62587998,
@ -252,7 +252,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
31948344,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
10625852,
15193821,
@ -304,7 +304,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
11531760,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
22176662,
3984313,
@ -356,7 +356,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
9686767,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
21157200,
39156966,
@ -408,7 +408,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
17395963,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
63153652,
32195955,
@ -460,7 +460,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
10289708,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
1401265,
58846825,
@ -512,7 +512,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
11976112,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
39849808,
44781685,
@ -564,7 +564,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
27313245,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
58237774,
15917425,
@ -616,7 +616,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
22487864,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
12671134,
56419053,
@ -668,7 +668,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
2606889,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
55082775,
45300503,
@ -720,7 +720,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
5956424,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
8211442,
8014184,
@ -772,7 +772,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
1824195,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
59402443,
17056879,
@ -824,7 +824,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
11687259,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
12521903,
26014045,
@ -876,7 +876,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
32973409,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
30654745,
51286025,
@ -928,7 +928,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
32228854,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
49518649,
59119280,
@ -980,7 +980,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
27003505,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
28286608,
10767548,
@ -1032,7 +1032,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
18292949,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
13869851,
31448904,
@ -1084,7 +1084,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
17277037,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
43287109,
27900723,
@ -1136,7 +1136,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
30748046,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
16441817,
36111849,
@ -1188,7 +1188,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
10930179,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
28319289,
40734650,
@ -1240,7 +1240,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
10938429,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
8373273,
49064494,
@ -1292,7 +1292,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
15812027,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
30741269,
38648744,
@ -1344,7 +1344,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
10839820,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
18672548,
57660959,
@ -1396,7 +1396,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
30432268,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
12179834,
41005450,
@ -1448,7 +1448,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
24862543,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
59523541,
62195428,
@ -1500,7 +1500,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
27449522,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
19770733,
26478685,
@ -1552,7 +1552,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
1776722,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
36719806,
20827965,
@ -1604,7 +1604,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
29334408,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
42307254,
57217102,
@ -1656,7 +1656,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
11292096,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
7071115,
46444090,
@ -1708,7 +1708,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
26285185,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
963440,
63742255,
@ -1760,7 +1760,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
4928058,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
63655588,
17883670,
@ -1812,7 +1812,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
32462955,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
39158670,
15322548,
@ -1864,7 +1864,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
20307815,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
2863373,
40876242,
@ -1916,7 +1916,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
21388876,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
59276548,
49972346,
@ -1968,7 +1968,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
27257051,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
52951491,
66542164,
@ -2020,7 +2020,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
26001714,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
66783087,
5234346,
@ -2072,7 +2072,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
10065424,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
42822326,
57678669,
@ -2124,7 +2124,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
4170709,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
26535281,
60238317,
@ -2176,7 +2176,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
33286062,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
54863941,
67016431,
@ -2228,7 +2228,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
347423,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
41391822,
34336880,
@ -2280,7 +2280,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
4942942,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
31956192,
59570132,
@ -2332,7 +2332,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
32932252,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
29885826,
51028067,
@ -2384,7 +2384,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
9101885,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
43331297,
18431341,
@ -2436,7 +2436,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
201203,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
20548943,
14334571,
@ -2488,7 +2488,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
25177079,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
41186817,
46681702,
@ -2540,7 +2540,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
7976478,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
46509314,
55327128,
@ -2592,7 +2592,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
22687008,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
15091184,
32550863,
@ -2644,7 +2644,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
542137,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
62776018,
32835413,
@ -2696,7 +2696,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
21024049,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
60835961,
48209103,
@ -2748,7 +2748,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
20924342,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
18135013,
20358426,
@ -2800,7 +2800,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
14572399,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
10785787,
46564798,
@ -2852,7 +2852,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
24110612,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
39907267,
45940262,
@ -2904,7 +2904,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
29853825,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
66303987,
36060363,
@ -2956,7 +2956,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
23261841,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
51218008,
5070126,
@ -3008,7 +3008,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
18326047,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
64176557,
3912400,
@ -3060,7 +3060,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
12655016,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
38372660,
57101970,
@ -3112,7 +3112,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
13421882,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
22917795,
22088359,
@ -3164,7 +3164,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
13175986,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
16185025,
61537525,
@ -3216,7 +3216,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
21409233,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
59411973,
57437124,
@ -3268,7 +3268,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
4211851,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
6757410,
65455566,
@ -3320,7 +3320,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
10524446,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
1497507,
33054449,
@ -3372,7 +3372,7 @@ pub(crate) static BASEPOINT_ODD_LOOKUP_TABLE: NafLookupTable8<CachedPoint> = Naf
29687002,
),
])),
CachedPoint(FieldElement32x4([
CachedPoint(FieldElement2625x4([
u32x8::new(
35889734,
23047226,

View file

@ -41,12 +41,12 @@ use subtle::Choice;
use subtle::ConditionallySelectable;
use edwards;
use scalar_mul::window::{LookupTable, NafLookupTable5, NafLookupTable8};
use window::{LookupTable, NafLookupTable5, NafLookupTable8};
use traits::Identity;
use backend::avx2::field::{FieldElement32x4, Lanes, Shuffle};
use backend::avx2::constants;
use super::constants;
use super::field::{FieldElement2625x4, Lanes, Shuffle};
/// A point on Curve25519, using parallel Edwards formulas for curve
/// operations.
@ -56,11 +56,11 @@ use backend::avx2::constants;
/// The coefficients of an `ExtendedPoint` are bounded with
/// \\( b < 0.007 \\).
#[derive(Copy, Clone, Debug)]
pub struct ExtendedPoint(pub(super) FieldElement32x4);
pub struct ExtendedPoint(pub(super) FieldElement2625x4);
impl From<edwards::EdwardsPoint> for ExtendedPoint {
fn from(P: edwards::EdwardsPoint) -> ExtendedPoint {
ExtendedPoint(FieldElement32x4::new(&P.X, &P.Y, &P.Z, &P.T))
ExtendedPoint(FieldElement2625x4::new(&P.X, &P.Y, &P.Z, &P.T))
}
}
@ -78,7 +78,7 @@ impl From<ExtendedPoint> for edwards::EdwardsPoint {
impl ConditionallySelectable for ExtendedPoint {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
ExtendedPoint(FieldElement32x4::conditional_select(&a.0, &b.0, choice))
ExtendedPoint(FieldElement2625x4::conditional_select(&a.0, &b.0, choice))
}
fn conditional_assign(&mut self, other: &Self, choice: Choice) {
@ -133,7 +133,7 @@ impl ExtendedPoint {
// =======================
// S5 S6 S8 S9
let zero = FieldElement32x4::zero();
let zero = FieldElement2625x4::zero();
let S_1 = tmp1.shuffle(Shuffle::AAAA);
let S_2 = tmp1.shuffle(Shuffle::BBBB);
@ -181,7 +181,7 @@ impl ExtendedPoint {
/// As long as the `CachedPoint` is not repeatedly negated, its
/// coefficients will be bounded with \\( b < 1.0 \\).
#[derive(Copy, Clone, Debug)]
pub struct CachedPoint(pub(super) FieldElement32x4);
pub struct CachedPoint(pub(super) FieldElement2625x4);
impl From<ExtendedPoint> for CachedPoint {
fn from(P: ExtendedPoint) -> CachedPoint {
@ -190,7 +190,7 @@ impl From<ExtendedPoint> for CachedPoint {
x = x.blend(x.diff_sum(), Lanes::AB);
// x = (X1 - Y1, X2 + Y2, Z2, T2) = (S2 S3 Z2 T2)
x = x * (121666, 121666, 2*121666, 2*121665);
x = x * (121666, 121666, 2 * 121666, 2 * 121665);
// x = (121666*S2 121666*S3 2*121666*Z2 2*121665*T2)
x = x.blend(-x, Lanes::D);
@ -215,7 +215,7 @@ impl Identity for CachedPoint {
impl ConditionallySelectable for CachedPoint {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
CachedPoint(FieldElement32x4::conditional_select(&a.0, &b.0, choice))
CachedPoint(FieldElement2625x4::conditional_select(&a.0, &b.0, choice))
}
fn conditional_assign(&mut self, other: &Self, choice: Choice) {
@ -247,7 +247,7 @@ impl<'a, 'b> Add<&'b CachedPoint> for &'a ExtendedPoint {
// coefficients grow by one bit. So on input, `self` is
// bounded with `b < 0.007` and `other` is bounded with
// `b < 1.0`.
let mut tmp = self.0;
tmp = tmp.blend(tmp.diff_sum(), Lanes::AB);
@ -329,7 +329,7 @@ mod test {
use super::*;
fn serial_add(P: edwards::EdwardsPoint, Q: edwards::EdwardsPoint) -> edwards::EdwardsPoint {
use backend::u64::field::FieldElement64;
use backend::serial::u64::field::FieldElement51;
let (X1, Y1, Z1, T1) = (P.X, P.Y, P.Z, P.T);
let (X2, Y2, Z2, T2) = (Q.X, Q.Y, Q.Z, Q.T);
@ -360,10 +360,10 @@ mod test {
print_var!(S7);
println!("");
let S8 = &S4 * &FieldElement64([ 121666,0,0,0,0]); // R5
let S9 = &S5 * &FieldElement64([ 121666,0,0,0,0]); // R6
let S10 = &S6 * &FieldElement64([2*121666,0,0,0,0]); // R8
let S11 = &S7 * &(-&FieldElement64([2*121665,0,0,0,0])); // R7
let S8 = &S4 * &FieldElement51([ 121666,0,0,0,0]); // R5
let S9 = &S5 * &FieldElement51([ 121666,0,0,0,0]); // R6
let S10 = &S6 * &FieldElement51([2*121666,0,0,0,0]); // R8
let S11 = &S7 * &(-&FieldElement51([2*121665,0,0,0,0])); // R7
print_var!(S8);
print_var!(S9);
print_var!(S10);

View file

@ -11,14 +11,14 @@
//! An implementation of 4-way vectorized 32bit field arithmetic using
//! AVX2.
//!
//! The `FieldElement32x4` struct provides a vector of four field
//! The `FieldElement2625x4` struct provides a vector of four field
//! elements, implemented using AVX2 operations. Its API is designed
//! to abstract away the platform-dependent details, so that point
//! arithmetic can be implemented only in terms of a vector of field
//! elements.
//!
//! At this level, the API is optimized for speed and not safety. The
//! `FieldElement32x4` does not always perform reductions. The pre-
//! `FieldElement2625x4` does not always perform reductions. The pre-
//! and post-conditions on the bounds of the coefficients are
//! documented for each method, but it is the caller's responsibility
//! to ensure that there are no overflows.
@ -42,15 +42,15 @@ const D_LANES64: u8 = 0b11_00_00_00;
use core::ops::{Add, Mul, Neg};
use packed_simd::{i32x8, u32x8, u64x4, IntoBits};
use backend::avx2::constants::{P_TIMES_16_HI, P_TIMES_16_LO, P_TIMES_2_HI, P_TIMES_2_LO};
use backend::u64::field::FieldElement64;
use backend::vector::avx2::constants::{P_TIMES_16_HI, P_TIMES_16_LO, P_TIMES_2_HI, P_TIMES_2_LO};
use backend::serial::u64::field::FieldElement51;
/// Unpack 32-bit lanes into 64-bit lanes:
/// ```
/// ```ascii,no_run
/// (a0, b0, a1, b1, c0, d0, c1, d1)
/// ```
/// into
/// ```
/// ```ascii,no_run
/// (a0, 0, b0, 0, c0, 0, d0, 0)
/// (a1, 0, b1, 0, c1, 0, d1, 0)
/// ```
@ -69,12 +69,12 @@ fn unpack_pair(src: u32x8) -> (u32x8, u32x8) {
}
/// Repack 64-bit lanes into 32-bit lanes:
/// ```
/// ```ascii,no_run
/// (a0, 0, b0, 0, c0, 0, d0, 0)
/// (a1, 0, b1, 0, c1, 0, d1, 0)
/// ```
/// into
/// ```
/// ```ascii,no_run
/// (a0, b0, a1, b1, c0, d0, c1, d1)
/// ```
#[inline(always)]
@ -97,11 +97,11 @@ fn repack_pair(x: u32x8, y: u32x8) -> u32x8 {
}
/// The `Lanes` enum represents a subset of the lanes `A,B,C,D` of a
/// `FieldElement32x4`.
/// `FieldElement2625x4`.
///
/// It's used to specify blend operations without
/// having to know details about the data layout of the
/// `FieldElement32x4`.
/// `FieldElement2625x4`.
#[derive(Copy, Clone, Debug)]
pub enum Lanes {
C,
@ -114,7 +114,7 @@ pub enum Lanes {
ABCD,
}
/// The `Shuffle` enum represents a shuffle of a `FieldElement32x4`.
/// The `Shuffle` enum represents a shuffle of a `FieldElement2625x4`.
///
/// The enum variants are named by what they do to a vector \\(
/// (A,B,C,D) \\); for instance, `Shuffle::BADC` turns \\( (A, B, C,
@ -135,26 +135,26 @@ pub enum Shuffle {
/// A vector of four field elements.
///
/// Each operation on a `FieldElement32x4` has documented effects on
/// Each operation on a `FieldElement2625x4` has documented effects on
/// the bounds of the coefficients. This API is designed for speed
/// and not safety; it is the caller's responsibility to ensure that
/// the post-conditions of one operation are compatible with the
/// pre-conditions of the next.
#[derive(Clone, Copy, Debug)]
pub struct FieldElement32x4(pub(crate) [u32x8; 5]);
pub struct FieldElement2625x4(pub(crate) [u32x8; 5]);
use subtle::Choice;
use subtle::ConditionallySelectable;
impl ConditionallySelectable for FieldElement32x4 {
impl ConditionallySelectable for FieldElement2625x4 {
fn conditional_select(
a: &FieldElement32x4,
b: &FieldElement32x4,
a: &FieldElement2625x4,
b: &FieldElement2625x4,
choice: Choice,
) -> FieldElement32x4 {
) -> FieldElement2625x4 {
let mask = (-(choice.unwrap_u8() as i32)) as u32;
let mask_vec = u32x8::splat(mask);
FieldElement32x4([
FieldElement2625x4([
a.0[0] ^ (mask_vec & (a.0[0] ^ b.0[0])),
a.0[1] ^ (mask_vec & (a.0[1] ^ b.0[1])),
a.0[2] ^ (mask_vec & (a.0[2] ^ b.0[2])),
@ -165,7 +165,7 @@ impl ConditionallySelectable for FieldElement32x4 {
fn conditional_assign(
&mut self,
other: &FieldElement32x4,
other: &FieldElement2625x4,
choice: Choice,
) {
let mask = (-(choice.unwrap_u8() as i32)) as u32;
@ -178,11 +178,11 @@ impl ConditionallySelectable for FieldElement32x4 {
}
}
impl FieldElement32x4 {
impl FieldElement2625x4 {
/// Split this vector into an array of four (serial) field
/// elements.
pub fn split(&self) -> [FieldElement64; 4] {
let mut out = [FieldElement64::zero(); 4];
pub fn split(&self) -> [FieldElement51; 4] {
let mut out = [FieldElement51::zero(); 4];
for i in 0..5 {
let a_2i = self.0[i].extract(0) as u64; //
let b_2i = self.0[i].extract(1) as u64; //
@ -208,7 +208,7 @@ impl FieldElement32x4 {
/// that when this function is inlined, LLVM is able to lower the
/// shuffle using an immediate.
#[inline]
pub fn shuffle(&self, control: Shuffle) -> FieldElement32x4 {
pub fn shuffle(&self, control: Shuffle) -> FieldElement2625x4 {
#[inline(always)]
fn shuffle_lanes(x: u32x8, control: Shuffle) -> u32x8 {
unsafe {
@ -233,7 +233,7 @@ impl FieldElement32x4 {
}
}
FieldElement32x4([
FieldElement2625x4([
shuffle_lanes(self.0[0], control),
shuffle_lanes(self.0[1], control),
shuffle_lanes(self.0[2], control),
@ -248,7 +248,7 @@ impl FieldElement32x4 {
/// that this function can be inlined and LLVM can lower it to a
/// blend instruction using an immediate.
#[inline]
pub fn blend(&self, other: FieldElement32x4, control: Lanes) -> FieldElement32x4 {
pub fn blend(&self, other: FieldElement2625x4, control: Lanes) -> FieldElement2625x4 {
#[inline(always)]
fn blend_lanes(x: u32x8, y: u32x8, control: Lanes) -> u32x8 {
unsafe {
@ -310,7 +310,7 @@ impl FieldElement32x4 {
}
}
FieldElement32x4([
FieldElement2625x4([
blend_lanes(self.0[0], other.0[0], control),
blend_lanes(self.0[1], other.0[1], control),
blend_lanes(self.0[2], other.0[2], control),
@ -320,26 +320,26 @@ impl FieldElement32x4 {
}
/// Construct a vector of zeros.
pub fn zero() -> FieldElement32x4 {
FieldElement32x4([u32x8::splat(0); 5])
pub fn zero() -> FieldElement2625x4 {
FieldElement2625x4([u32x8::splat(0); 5])
}
/// Convenience wrapper around `new(x,x,x,x)`.
pub fn splat(x: &FieldElement64) -> FieldElement32x4 {
FieldElement32x4::new(x, x, x, x)
pub fn splat(x: &FieldElement51) -> FieldElement2625x4 {
FieldElement2625x4::new(x, x, x, x)
}
/// Create a `FieldElement32x4` from four `FieldElement64`s.
/// Create a `FieldElement2625x4` from four `FieldElement51`s.
///
/// # Postconditions
///
/// The resulting `FieldElement32x4` is bounded with \\( b < 0.0002 \\).
/// The resulting `FieldElement2625x4` is bounded with \\( b < 0.0002 \\).
pub fn new(
x0: &FieldElement64,
x1: &FieldElement64,
x2: &FieldElement64,
x3: &FieldElement64,
) -> FieldElement32x4 {
x0: &FieldElement51,
x1: &FieldElement51,
x2: &FieldElement51,
x3: &FieldElement51,
) -> FieldElement2625x4 {
let mut buf = [u32x8::splat(0); 5];
let low_26_bits = (1 << 26) - 1;
for i in 0..5 {
@ -355,10 +355,10 @@ impl FieldElement32x4 {
buf[i] = u32x8::new(a_2i, b_2i, a_2i_1, b_2i_1, c_2i, d_2i, c_2i_1, d_2i_1);
}
// We don't know that the original `FieldElement64`s were
// We don't know that the original `FieldElement51`s were
// fully reduced, so the odd limbs may exceed 2^25.
// Reduce them to be sure.
FieldElement32x4(buf).reduce()
FieldElement2625x4(buf).reduce()
}
/// Given \\((A,B,C,D)\\), compute \\((-A,-B,-C,-D)\\), without
@ -372,11 +372,11 @@ impl FieldElement32x4 {
///
/// The coefficients of the result are bounded with \\( b < 1 \\).
#[inline]
pub fn negate_lazy(&self) -> FieldElement32x4 {
pub fn negate_lazy(&self) -> FieldElement2625x4 {
// The limbs of self are bounded with b < 0.999, while the
// smallest limb of 2*p is 67108845 > 2^{26+0.9999}, so
// underflows are not possible.
FieldElement32x4([
FieldElement2625x4([
P_TIMES_2_LO - self.0[0],
P_TIMES_2_HI - self.0[1],
P_TIMES_2_HI - self.0[2],
@ -395,7 +395,7 @@ impl FieldElement32x4 {
///
/// The coefficients of the result are bounded with \\( b < 1.6 \\).
#[inline]
pub fn diff_sum(&self) -> FieldElement32x4 {
pub fn diff_sum(&self) -> FieldElement2625x4 {
// tmp1 = (B, A, D, C)
let tmp1 = self.shuffle(Shuffle::BADC);
// tmp2 = (-A, B, -C, D)
@ -410,7 +410,7 @@ impl FieldElement32x4 {
///
/// The coefficients of the result are bounded with \\( b < 0.0002 \\).
#[inline]
pub fn reduce(&self) -> FieldElement32x4 {
pub fn reduce(&self) -> FieldElement2625x4 {
let shifts = i32x8::new(26, 26, 25, 25, 26, 26, 25, 25);
let masks = u32x8::new(
(1 << 26) - 1,
@ -509,16 +509,16 @@ impl FieldElement32x4 {
// c_odd < 2^25 + 2^11.25 < 25.0001 < 2^{25+b}
//
// where b = 0.0002.
FieldElement32x4(v)
FieldElement2625x4(v)
}
/// Given an array of wide coefficients, reduce them to a `FieldElement32x4`.
/// Given an array of wide coefficients, reduce them to a `FieldElement2625x4`.
///
/// # Postconditions
///
/// The coefficients of the result are bounded with \\( b < 0.007 \\).
#[inline]
fn reduce64(mut z: [u64x4; 10]) -> FieldElement32x4 {
fn reduce64(mut z: [u64x4; 10]) -> FieldElement2625x4 {
// These aren't const because splat isn't a const fn
let LOW_25_BITS: u64x4 = u64x4::splat((1 << 25) - 1);
let LOW_26_BITS: u64x4 = u64x4::splat((1 << 26) - 1);
@ -578,7 +578,7 @@ impl FieldElement32x4 {
// b = 0 for other z[i].
//
// So the packed result is bounded with b = 0.007.
FieldElement32x4([
FieldElement2625x4([
repack_pair(z[0].into_bits(), z[1].into_bits()),
repack_pair(z[2].into_bits(), z[3].into_bits()),
repack_pair(z[4].into_bits(), z[5].into_bits()),
@ -596,7 +596,7 @@ impl FieldElement32x4 {
/// # Postconditions
///
/// The coefficients of the result are bounded with \\( b < 0.007 \\).
pub fn square_and_negate_D(&self) -> FieldElement32x4 {
pub fn square_and_negate_D(&self) -> FieldElement2625x4 {
#[inline(always)]
fn m(x: u32x8, y: u32x8) -> u64x4 {
use core::arch::x86_64::_mm256_mul_epu32;
@ -681,12 +681,12 @@ impl FieldElement32x4 {
z8 = negate_D(z8, even_p37);
z9 = negate_D(z9, odd__p37);
FieldElement32x4::reduce64([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9])
FieldElement2625x4::reduce64([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9])
}
}
impl Neg for FieldElement32x4 {
type Output = FieldElement32x4;
impl Neg for FieldElement2625x4 {
type Output = FieldElement2625x4;
/// Negate this field element, performing a reduction.
///
@ -701,8 +701,8 @@ impl Neg for FieldElement32x4 {
///
/// The coefficients of the result are bounded with \\( b < 0.0002 \\).
#[inline]
fn neg(self) -> FieldElement32x4 {
FieldElement32x4([
fn neg(self) -> FieldElement2625x4 {
FieldElement2625x4([
P_TIMES_16_LO - self.0[0],
P_TIMES_16_HI - self.0[1],
P_TIMES_16_HI - self.0[2],
@ -712,12 +712,12 @@ impl Neg for FieldElement32x4 {
}
}
impl Add<FieldElement32x4> for FieldElement32x4 {
type Output = FieldElement32x4;
/// Add two `FieldElement32x4`s, without performing a reduction.
impl Add<FieldElement2625x4> for FieldElement2625x4 {
type Output = FieldElement2625x4;
/// Add two `FieldElement2625x4`s, without performing a reduction.
#[inline]
fn add(self, rhs: FieldElement32x4) -> FieldElement32x4 {
FieldElement32x4([
fn add(self, rhs: FieldElement2625x4) -> FieldElement2625x4 {
FieldElement2625x4([
self.0[0] + rhs.0[0],
self.0[1] + rhs.0[1],
self.0[2] + rhs.0[2],
@ -727,15 +727,15 @@ impl Add<FieldElement32x4> for FieldElement32x4 {
}
}
impl Mul<(u32, u32, u32, u32)> for FieldElement32x4 {
type Output = FieldElement32x4;
impl Mul<(u32, u32, u32, u32)> for FieldElement2625x4 {
type Output = FieldElement2625x4;
/// Perform a multiplication by a vector of small constants.
///
/// # Postconditions
///
/// The coefficients of the result are bounded with \\( b < 0.007 \\).
#[inline]
fn mul(self, scalars: (u32, u32, u32, u32)) -> FieldElement32x4 {
fn mul(self, scalars: (u32, u32, u32, u32)) -> FieldElement2625x4 {
unsafe {
use core::arch::x86_64::_mm256_mul_epu32;
@ -747,7 +747,7 @@ impl Mul<(u32, u32, u32, u32)> for FieldElement32x4 {
let (b6, b7) = unpack_pair(self.0[3]);
let (b8, b9) = unpack_pair(self.0[4]);
FieldElement32x4::reduce64([
FieldElement2625x4::reduce64([
_mm256_mul_epu32(b0.into_bits(), consts.into_bits()).into_bits(),
_mm256_mul_epu32(b1.into_bits(), consts.into_bits()).into_bits(),
_mm256_mul_epu32(b2.into_bits(), consts.into_bits()).into_bits(),
@ -763,8 +763,8 @@ impl Mul<(u32, u32, u32, u32)> for FieldElement32x4 {
}
}
impl<'a, 'b> Mul<&'b FieldElement32x4> for &'a FieldElement32x4 {
type Output = FieldElement32x4;
impl<'a, 'b> Mul<&'b FieldElement2625x4> for &'a FieldElement2625x4 {
type Output = FieldElement2625x4;
/// Multiply `self` by `rhs`.
///
/// # Preconditions
@ -777,7 +777,7 @@ impl<'a, 'b> Mul<&'b FieldElement32x4> for &'a FieldElement32x4 {
///
/// The coefficients of the result are bounded with \\( b < 0.007 \\).
///
fn mul(self, rhs: &'b FieldElement32x4) -> FieldElement32x4 {
fn mul(self, rhs: &'b FieldElement2625x4) -> FieldElement2625x4 {
#[inline(always)]
fn m(x: u32x8, y: u32x8) -> u64x4 {
use core::arch::x86_64::_mm256_mul_epu32;
@ -869,7 +869,7 @@ impl<'a, 'b> Mul<&'b FieldElement32x4> for &'a FieldElement32x4 {
// multiplications by 19 into a u32. The tighter bound on b_y
// means we could get a tighter bound on the outputs, or a
// looser bound on b_x.
FieldElement32x4::reduce64([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9])
FieldElement2625x4::reduce64([z0, z1, z2, z3, z4, z5, z6, z7, z8, z9])
}
}
@ -880,25 +880,25 @@ mod test {
#[test]
fn scale_by_curve_constants() {
let mut x = FieldElement32x4::splat(&FieldElement64::one());
let mut x = FieldElement2625x4::splat(&FieldElement51::one());
x = x * (121666, 121666, 2*121666, 2*121665);
let xs = x.split();
assert_eq!(xs[0], FieldElement64([121666, 0, 0, 0, 0]));
assert_eq!(xs[1], FieldElement64([121666, 0, 0, 0, 0]));
assert_eq!(xs[2], FieldElement64([2 * 121666, 0, 0, 0, 0]));
assert_eq!(xs[3], FieldElement64([2 * 121665, 0, 0, 0, 0]));
assert_eq!(xs[0], FieldElement51([121666, 0, 0, 0, 0]));
assert_eq!(xs[1], FieldElement51([121666, 0, 0, 0, 0]));
assert_eq!(xs[2], FieldElement51([2 * 121666, 0, 0, 0, 0]));
assert_eq!(xs[3], FieldElement51([2 * 121665, 0, 0, 0, 0]));
}
#[test]
fn diff_sum_vs_serial() {
let x0 = FieldElement64([10000, 10001, 10002, 10003, 10004]);
let x1 = FieldElement64([10100, 10101, 10102, 10103, 10104]);
let x2 = FieldElement64([10200, 10201, 10202, 10203, 10204]);
let x3 = FieldElement64([10300, 10301, 10302, 10303, 10304]);
let x0 = FieldElement51([10000, 10001, 10002, 10003, 10004]);
let x1 = FieldElement51([10100, 10101, 10102, 10103, 10104]);
let x2 = FieldElement51([10200, 10201, 10202, 10203, 10204]);
let x3 = FieldElement51([10300, 10301, 10302, 10303, 10304]);
let vec = FieldElement32x4::new(&x0, &x1, &x2, &x3).diff_sum();
let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3).diff_sum();
let result = vec.split();
@ -910,12 +910,12 @@ mod test {
#[test]
fn square_vs_serial() {
let x0 = FieldElement64([10000, 10001, 10002, 10003, 10004]);
let x1 = FieldElement64([10100, 10101, 10102, 10103, 10104]);
let x2 = FieldElement64([10200, 10201, 10202, 10203, 10204]);
let x3 = FieldElement64([10300, 10301, 10302, 10303, 10304]);
let x0 = FieldElement51([10000, 10001, 10002, 10003, 10004]);
let x1 = FieldElement51([10100, 10101, 10102, 10103, 10104]);
let x2 = FieldElement51([10200, 10201, 10202, 10203, 10204]);
let x3 = FieldElement51([10300, 10301, 10302, 10303, 10304]);
let vec = FieldElement32x4::new(&x0, &x1, &x2, &x3);
let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3);
let result = vec.square_and_negate_D().split();
@ -927,12 +927,12 @@ mod test {
#[test]
fn multiply_vs_serial() {
let x0 = FieldElement64([10000, 10001, 10002, 10003, 10004]);
let x1 = FieldElement64([10100, 10101, 10102, 10103, 10104]);
let x2 = FieldElement64([10200, 10201, 10202, 10203, 10204]);
let x3 = FieldElement64([10300, 10301, 10302, 10303, 10304]);
let x0 = FieldElement51([10000, 10001, 10002, 10003, 10004]);
let x1 = FieldElement51([10100, 10101, 10102, 10103, 10104]);
let x2 = FieldElement51([10200, 10201, 10202, 10203, 10204]);
let x3 = FieldElement51([10300, 10301, 10302, 10303, 10304]);
let vec = FieldElement32x4::new(&x0, &x1, &x2, &x3);
let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3);
let vecprime = vec.clone();
let result = (&vec * &vecprime).split();
@ -945,12 +945,12 @@ mod test {
#[test]
fn test_unpack_repack_pair() {
let x0 = FieldElement64([10000 + (10001 << 26), 0, 0, 0, 0]);
let x1 = FieldElement64([10100 + (10101 << 26), 0, 0, 0, 0]);
let x2 = FieldElement64([10200 + (10201 << 26), 0, 0, 0, 0]);
let x3 = FieldElement64([10300 + (10301 << 26), 0, 0, 0, 0]);
let x0 = FieldElement51([10000 + (10001 << 26), 0, 0, 0, 0]);
let x1 = FieldElement51([10100 + (10101 << 26), 0, 0, 0, 0]);
let x2 = FieldElement51([10200 + (10201 << 26), 0, 0, 0, 0]);
let x3 = FieldElement51([10300 + (10301 << 26), 0, 0, 0, 0]);
let vec = FieldElement32x4::new(&x0, &x1, &x2, &x3);
let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3);
let src = vec.0[0];
@ -969,12 +969,12 @@ mod test {
#[test]
fn new_split_roundtrips() {
let x0 = FieldElement64::from_bytes(&[0x10; 32]);
let x1 = FieldElement64::from_bytes(&[0x11; 32]);
let x2 = FieldElement64::from_bytes(&[0x12; 32]);
let x3 = FieldElement64::from_bytes(&[0x13; 32]);
let x0 = FieldElement51::from_bytes(&[0x10; 32]);
let x1 = FieldElement51::from_bytes(&[0x11; 32]);
let x2 = FieldElement51::from_bytes(&[0x12; 32]);
let x3 = FieldElement51::from_bytes(&[0x13; 32]);
let vec = FieldElement32x4::new(&x0, &x1, &x2, &x3);
let vec = FieldElement2625x4::new(&x0, &x1, &x2, &x3);
let splits = vec.split();

View file

@ -18,7 +18,8 @@
// location of build.rs, not lib.rs, so the markdown file appears
// missing).
#![cfg_attr(
all(feature = "nightly", feature = "stage2_build"), doc(include = "../docs/avx2-notes.md")
all(feature = "nightly", feature = "stage2_build"),
doc(include = "../docs/avx2-notes.md")
)]
pub(crate) mod field;
@ -26,5 +27,3 @@ pub(crate) mod field;
pub(crate) mod edwards;
pub(crate) mod constants;
pub(crate) mod scalar_mul;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,315 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2018 Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Henry de Valence <hdevalence@hdevalence.ca>
#![allow(non_snake_case)]
use traits::Identity;
use std::ops::{Add, Neg, Sub};
use subtle::Choice;
use subtle::ConditionallySelectable;
use edwards;
use window::{LookupTable, NafLookupTable5, NafLookupTable8};
use super::constants;
use super::field::{F51x4Reduced, F51x4Unreduced, Lanes, Shuffle};
#[derive(Copy, Clone, Debug)]
pub struct ExtendedPoint(pub(super) F51x4Unreduced);
#[derive(Copy, Clone, Debug)]
pub struct CachedPoint(pub(super) F51x4Reduced);
impl From<edwards::EdwardsPoint> for ExtendedPoint {
fn from(P: edwards::EdwardsPoint) -> ExtendedPoint {
ExtendedPoint(F51x4Unreduced::new(&P.X, &P.Y, &P.Z, &P.T))
}
}
impl From<ExtendedPoint> for edwards::EdwardsPoint {
fn from(P: ExtendedPoint) -> edwards::EdwardsPoint {
let reduced = F51x4Reduced::from(P.0);
let tmp = F51x4Unreduced::from(reduced).split();
edwards::EdwardsPoint {
X: tmp[0],
Y: tmp[1],
Z: tmp[2],
T: tmp[3],
}
}
}
impl From<ExtendedPoint> for CachedPoint {
fn from(P: ExtendedPoint) -> CachedPoint {
let mut x = P.0;
x = x.blend(&x.diff_sum(), Lanes::AB);
x = &F51x4Reduced::from(x) * (121666, 121666, 2 * 121666, 2 * 121665);
x = x.blend(&x.negate_lazy(), Lanes::D);
CachedPoint(F51x4Reduced::from(x))
}
}
impl Default for ExtendedPoint {
fn default() -> ExtendedPoint {
ExtendedPoint::identity()
}
}
impl Identity for ExtendedPoint {
fn identity() -> ExtendedPoint {
constants::EXTENDEDPOINT_IDENTITY
}
}
impl ExtendedPoint {
pub fn double(&self) -> ExtendedPoint {
// (Y1 X1 T1 Z1) -- uses vpshufd (1c latency @ 1/c)
let mut tmp0 = self.0.shuffle(Shuffle::BADC);
// (X1+Y1 X1+Y1 X1+Y1 X1+Y1) -- can use vpinserti128
let mut tmp1 = (self.0 + tmp0).shuffle(Shuffle::ABAB);
// (X1 Y1 Z1 X1+Y1)
tmp0 = self.0.blend(&tmp1, Lanes::D);
tmp1 = F51x4Reduced::from(tmp0).square();
// Now tmp1 = (S1 S2 S3 S4)
// We want to compute
//
// + | S1 | S1 | S1 | S1 |
// + | S2 | | | S2 |
// + | | | S3 | |
// + | | | S3 | |
// + | |16p |16p |16p |
// - | | S2 | S2 | |
// - | | | | S4 |
// =======================
// S5 S6 S8 S9
let zero = F51x4Unreduced::zero();
let S1_S1_S1_S1 = tmp1.shuffle(Shuffle::AAAA);
let S2_S2_S2_S2 = tmp1.shuffle(Shuffle::BBBB);
let S2_S2_S2_S4 = S2_S2_S2_S2.blend(&tmp1, Lanes::D).negate_lazy();
tmp0 = S1_S1_S1_S1 + zero.blend(&(tmp1 + tmp1), Lanes::C);
tmp0 = tmp0 + zero.blend(&S2_S2_S2_S2, Lanes::AD);
tmp0 = tmp0 + zero.blend(&S2_S2_S2_S4, Lanes::BCD);
let tmp2 = F51x4Reduced::from(tmp0);
ExtendedPoint(&tmp2.shuffle(Shuffle::DBBD) * &tmp2.shuffle(Shuffle::CACA))
}
pub fn mul_by_pow_2(&self, k: u32) -> ExtendedPoint {
let mut tmp: ExtendedPoint = *self;
for _ in 0..k {
tmp = tmp.double();
}
tmp
}
}
impl<'a, 'b> Add<&'b CachedPoint> for &'a ExtendedPoint {
type Output = ExtendedPoint;
/// Add an `ExtendedPoint` and a `CachedPoint`.
fn add(self, other: &'b CachedPoint) -> ExtendedPoint {
let mut tmp = self.0;
tmp = tmp.blend(&tmp.diff_sum(), Lanes::AB);
// tmp = (Y1-X1 Y1+X1 Z1 T1) = (S0 S1 Z1 T1)
tmp = &F51x4Reduced::from(tmp) * &other.0;
// tmp = (S0*S2' S1*S3' Z1*Z2' T1*T2') = (S8 S9 S10 S11)
tmp = tmp.shuffle(Shuffle::ABDC);
// tmp = (S8 S9 S11 S10)
let tmp = F51x4Reduced::from(tmp.diff_sum());
// tmp = (S9-S8 S9+S8 S10-S11 S10+S11) = (S12 S13 S14 S15)
let t0 = tmp.shuffle(Shuffle::ADDA);
// t0 = (S12 S15 S15 S12)
let t1 = tmp.shuffle(Shuffle::CBCB);
// t1 = (S14 S13 S14 S13)
// Return (S12*S14 S15*S13 S15*S14 S12*S13) = (X3 Y3 Z3 T3)
ExtendedPoint(&t0 * &t1)
}
}
impl Default for CachedPoint {
fn default() -> CachedPoint {
CachedPoint::identity()
}
}
impl Identity for CachedPoint {
fn identity() -> CachedPoint {
constants::CACHEDPOINT_IDENTITY
}
}
impl ConditionallySelectable for CachedPoint {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
CachedPoint(F51x4Reduced::conditional_select(&a.0, &b.0, choice))
}
fn conditional_assign(&mut self, other: &Self, choice: Choice) {
self.0.conditional_assign(&other.0, choice);
}
}
impl<'a> Neg for &'a CachedPoint {
type Output = CachedPoint;
fn neg(self) -> CachedPoint {
let swapped = self.0.shuffle(Shuffle::BACD);
CachedPoint(swapped.blend(&(-self.0), Lanes::D))
}
}
impl<'a, 'b> Sub<&'b CachedPoint> for &'a ExtendedPoint {
type Output = ExtendedPoint;
/// Implement subtraction by negating the point and adding.
fn sub(self, other: &'b CachedPoint) -> ExtendedPoint {
self + &(-other)
}
}
impl<'a> From<&'a edwards::EdwardsPoint> for LookupTable<CachedPoint> {
fn from(point: &'a edwards::EdwardsPoint) -> Self {
let P = ExtendedPoint::from(*point);
let mut points = [CachedPoint::from(P); 8];
for i in 0..7 {
points[i + 1] = (&P + &points[i]).into();
}
LookupTable(points)
}
}
impl<'a> From<&'a edwards::EdwardsPoint> for NafLookupTable5<CachedPoint> {
fn from(point: &'a edwards::EdwardsPoint) -> Self {
let A = ExtendedPoint::from(*point);
let mut Ai = [CachedPoint::from(A); 8];
let A2 = A.double();
for i in 0..7 {
Ai[i + 1] = (&A2 + &Ai[i]).into();
}
// Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A]
NafLookupTable5(Ai)
}
}
impl<'a> From<&'a edwards::EdwardsPoint> for NafLookupTable8<CachedPoint> {
fn from(point: &'a edwards::EdwardsPoint) -> Self {
let A = ExtendedPoint::from(*point);
let mut Ai = [CachedPoint::from(A); 64];
let A2 = A.double();
for i in 0..63 {
Ai[i + 1] = (&A2 + &Ai[i]).into();
}
// Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A, ..., 127A]
NafLookupTable8(Ai)
}
}
#[cfg(test)]
mod test {
use super::*;
fn addition_test_helper(P: edwards::EdwardsPoint, Q: edwards::EdwardsPoint) {
// Test the serial implementation of the parallel addition formulas
//let R_serial: edwards::EdwardsPoint = serial_add(P.into(), Q.into()).into();
// Test the vector implementation of the parallel readdition formulas
let cached_Q = CachedPoint::from(ExtendedPoint::from(Q));
let R_vector: edwards::EdwardsPoint = (&ExtendedPoint::from(P) + &cached_Q).into();
let S_vector: edwards::EdwardsPoint = (&ExtendedPoint::from(P) - &cached_Q).into();
println!("Testing point addition:");
println!("P = {:?}", P);
println!("Q = {:?}", Q);
println!("cached Q = {:?}", cached_Q);
println!("R = P + Q = {:?}", &P + &Q);
//println!("R_serial = {:?}", R_serial);
println!("R_vector = {:?}", R_vector);
println!("S = P - Q = {:?}", &P - &Q);
println!("S_vector = {:?}", S_vector);
//assert_eq!(R_serial.compress(), (&P + &Q).compress());
assert_eq!(R_vector.compress(), (&P + &Q).compress());
assert_eq!(S_vector.compress(), (&P - &Q).compress());
println!("OK!\n");
}
#[test]
fn vector_addition_vs_serial_addition_vs_edwards_extendedpoint() {
use constants;
use scalar::Scalar;
println!("Testing id +- id");
let P = edwards::EdwardsPoint::identity();
let Q = edwards::EdwardsPoint::identity();
addition_test_helper(P, Q);
println!("Testing id +- B");
let P = edwards::EdwardsPoint::identity();
let Q = constants::ED25519_BASEPOINT_POINT;
addition_test_helper(P, Q);
println!("Testing B +- B");
let P = constants::ED25519_BASEPOINT_POINT;
let Q = constants::ED25519_BASEPOINT_POINT;
addition_test_helper(P, Q);
println!("Testing B +- kB");
let P = constants::ED25519_BASEPOINT_POINT;
let Q = &constants::ED25519_BASEPOINT_TABLE * &Scalar::from(8475983829u64);
addition_test_helper(P, Q);
}
fn doubling_test_helper(P: edwards::EdwardsPoint) {
//let R1: edwards::EdwardsPoint = serial_double(P.into()).into();
let R2: edwards::EdwardsPoint = ExtendedPoint::from(P).double().into();
println!("Testing point doubling:");
println!("P = {:?}", P);
//println!("(serial) R1 = {:?}", R1);
println!("(vector) R2 = {:?}", R2);
println!("P + P = {:?}", &P + &P);
//assert_eq!(R1.compress(), (&P + &P).compress());
assert_eq!(R2.compress(), (&P + &P).compress());
println!("OK!\n");
}
#[test]
fn vector_doubling_vs_serial_doubling_vs_edwards_extendedpoint() {
use constants;
use scalar::Scalar;
println!("Testing [2]id");
let P = edwards::EdwardsPoint::identity();
doubling_test_helper(P);
println!("Testing [2]B");
let P = constants::ED25519_BASEPOINT_POINT;
doubling_test_helper(P);
println!("Testing [2]([k]B)");
let P = &constants::ED25519_BASEPOINT_TABLE * &Scalar::from(8475983829u64);
doubling_test_helper(P);
}
}

View file

@ -0,0 +1,822 @@
// -*- mode: rust; coding: utf-8; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2018 Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Henry de Valence <hdevalence@hdevalence.ca>
#![allow(non_snake_case)]
use core::ops::{Add, Mul, Neg};
use packed_simd::{u64x4, IntoBits};
use backend::serial::u64::field::FieldElement51;
#[allow(improper_ctypes)]
extern "C" {
#[link_name = "llvm.x86.avx512.vpmadd52l.uq.256"]
fn madd52lo(z: u64x4, x: u64x4, y: u64x4) -> u64x4;
#[link_name = "llvm.x86.avx512.vpmadd52h.uq.256"]
fn madd52hi(z: u64x4, x: u64x4, y: u64x4) -> u64x4;
}
/// A vector of four field elements in radix 2^51, with unreduced coefficients.
#[derive(Copy, Clone, Debug)]
pub struct F51x4Unreduced(pub(crate) [u64x4; 5]);
/// A vector of four field elements in radix 2^51, with reduced coefficients.
#[derive(Copy, Clone, Debug)]
pub struct F51x4Reduced(pub(crate) [u64x4; 5]);
#[derive(Copy, Clone)]
pub enum Shuffle {
AAAA,
BBBB,
BADC,
BACD,
ADDA,
CBCB,
ABDC,
ABAB,
DBBD,
CACA,
}
#[inline(always)]
fn shuffle_lanes(x: u64x4, control: Shuffle) -> u64x4 {
unsafe {
use core::arch::x86_64::_mm256_permute4x64_epi64 as perm;
match control {
Shuffle::AAAA => perm(x.into_bits(), 0b00_00_00_00).into_bits(),
Shuffle::BBBB => perm(x.into_bits(), 0b01_01_01_01).into_bits(),
Shuffle::BADC => perm(x.into_bits(), 0b10_11_00_01).into_bits(),
Shuffle::BACD => perm(x.into_bits(), 0b11_10_00_01).into_bits(),
Shuffle::ADDA => perm(x.into_bits(), 0b00_11_11_00).into_bits(),
Shuffle::CBCB => perm(x.into_bits(), 0b01_10_01_10).into_bits(),
Shuffle::ABDC => perm(x.into_bits(), 0b10_11_01_00).into_bits(),
Shuffle::ABAB => perm(x.into_bits(), 0b01_00_01_00).into_bits(),
Shuffle::DBBD => perm(x.into_bits(), 0b11_01_01_11).into_bits(),
Shuffle::CACA => perm(x.into_bits(), 0b00_10_00_10).into_bits(),
}
}
}
#[derive(Copy, Clone)]
pub enum Lanes {
D,
C,
AB,
AC,
AD,
BCD,
}
#[inline]
fn blend_lanes(x: u64x4, y: u64x4, control: Lanes) -> u64x4 {
unsafe {
use core::arch::x86_64::_mm256_blend_epi32 as blend;
match control {
Lanes::D => blend(x.into_bits(), y.into_bits(), 0b11_00_00_00).into_bits(),
Lanes::C => blend(x.into_bits(), y.into_bits(), 0b00_11_00_00).into_bits(),
Lanes::AB => blend(x.into_bits(), y.into_bits(), 0b00_00_11_11).into_bits(),
Lanes::AC => blend(x.into_bits(), y.into_bits(), 0b00_11_00_11).into_bits(),
Lanes::AD => blend(x.into_bits(), y.into_bits(), 0b11_00_00_11).into_bits(),
Lanes::BCD => blend(x.into_bits(), y.into_bits(), 0b11_11_11_00).into_bits(),
}
}
}
impl F51x4Unreduced {
pub fn zero() -> F51x4Unreduced {
F51x4Unreduced([u64x4::splat(0); 5])
}
pub fn new(
x0: &FieldElement51,
x1: &FieldElement51,
x2: &FieldElement51,
x3: &FieldElement51,
) -> F51x4Unreduced {
F51x4Unreduced([
u64x4::new(x0.0[0], x1.0[0], x2.0[0], x3.0[0]),
u64x4::new(x0.0[1], x1.0[1], x2.0[1], x3.0[1]),
u64x4::new(x0.0[2], x1.0[2], x2.0[2], x3.0[2]),
u64x4::new(x0.0[3], x1.0[3], x2.0[3], x3.0[3]),
u64x4::new(x0.0[4], x1.0[4], x2.0[4], x3.0[4]),
])
}
pub fn split(&self) -> [FieldElement51; 4] {
let x = &self.0;
[
FieldElement51([
x[0].extract(0),
x[1].extract(0),
x[2].extract(0),
x[3].extract(0),
x[4].extract(0),
]),
FieldElement51([
x[0].extract(1),
x[1].extract(1),
x[2].extract(1),
x[3].extract(1),
x[4].extract(1),
]),
FieldElement51([
x[0].extract(2),
x[1].extract(2),
x[2].extract(2),
x[3].extract(2),
x[4].extract(2),
]),
FieldElement51([
x[0].extract(3),
x[1].extract(3),
x[2].extract(3),
x[3].extract(3),
x[4].extract(3),
]),
]
}
#[inline]
pub fn diff_sum(&self) -> F51x4Unreduced {
// tmp1 = (B, A, D, C)
let tmp1 = self.shuffle(Shuffle::BADC);
// tmp2 = (-A, B, -C, D)
let tmp2 = self.blend(&self.negate_lazy(), Lanes::AC);
// (B - A, B + A, D - C, D + C)
tmp1 + tmp2
}
#[inline]
pub fn negate_lazy(&self) -> F51x4Unreduced {
let lo = u64x4::splat(36028797018963664u64);
let hi = u64x4::splat(36028797018963952u64);
F51x4Unreduced([
lo - self.0[0],
hi - self.0[1],
hi - self.0[2],
hi - self.0[3],
hi - self.0[4],
])
}
#[inline]
pub fn shuffle(&self, control: Shuffle) -> F51x4Unreduced {
F51x4Unreduced([
shuffle_lanes(self.0[0], control),
shuffle_lanes(self.0[1], control),
shuffle_lanes(self.0[2], control),
shuffle_lanes(self.0[3], control),
shuffle_lanes(self.0[4], control),
])
}
#[inline]
pub fn blend(&self, other: &F51x4Unreduced, control: Lanes) -> F51x4Unreduced {
F51x4Unreduced([
blend_lanes(self.0[0], other.0[0], control),
blend_lanes(self.0[1], other.0[1], control),
blend_lanes(self.0[2], other.0[2], control),
blend_lanes(self.0[3], other.0[3], control),
blend_lanes(self.0[4], other.0[4], control),
])
}
}
impl Neg for F51x4Reduced {
type Output = F51x4Reduced;
fn neg(self) -> F51x4Reduced {
F51x4Unreduced::from(self).negate_lazy().into()
}
}
use subtle::Choice;
use subtle::ConditionallySelectable;
impl ConditionallySelectable for F51x4Reduced {
#[inline]
fn conditional_select(
a: &F51x4Reduced,
b: &F51x4Reduced,
choice: Choice,
) -> F51x4Reduced {
let mask = (-(choice.unwrap_u8() as i64)) as u64;
let mask_vec = u64x4::splat(mask);
F51x4Reduced([
a.0[0] ^ (mask_vec & (a.0[0] ^ b.0[0])),
a.0[1] ^ (mask_vec & (a.0[1] ^ b.0[1])),
a.0[2] ^ (mask_vec & (a.0[2] ^ b.0[2])),
a.0[3] ^ (mask_vec & (a.0[3] ^ b.0[3])),
a.0[4] ^ (mask_vec & (a.0[4] ^ b.0[4])),
])
}
#[inline]
fn conditional_assign(&mut self, other: &F51x4Reduced, choice: Choice) {
let mask = (-(choice.unwrap_u8() as i64)) as u64;
let mask_vec = u64x4::splat(mask);
self.0[0] ^= mask_vec & (self.0[0] ^ other.0[0]);
self.0[1] ^= mask_vec & (self.0[1] ^ other.0[1]);
self.0[2] ^= mask_vec & (self.0[2] ^ other.0[2]);
self.0[3] ^= mask_vec & (self.0[3] ^ other.0[3]);
self.0[4] ^= mask_vec & (self.0[4] ^ other.0[4]);
}
}
impl F51x4Reduced {
#[inline]
pub fn shuffle(&self, control: Shuffle) -> F51x4Reduced {
F51x4Reduced([
shuffle_lanes(self.0[0], control),
shuffle_lanes(self.0[1], control),
shuffle_lanes(self.0[2], control),
shuffle_lanes(self.0[3], control),
shuffle_lanes(self.0[4], control),
])
}
#[inline]
pub fn blend(&self, other: &F51x4Reduced, control: Lanes) -> F51x4Reduced {
F51x4Reduced([
blend_lanes(self.0[0], other.0[0], control),
blend_lanes(self.0[1], other.0[1], control),
blend_lanes(self.0[2], other.0[2], control),
blend_lanes(self.0[3], other.0[3], control),
blend_lanes(self.0[4], other.0[4], control),
])
}
#[inline]
pub fn square(&self) -> F51x4Unreduced {
unsafe {
let x = &self.0;
// Represent values with coeff. 2
let mut z0_2 = u64x4::splat(0);
let mut z1_2 = u64x4::splat(0);
let mut z2_2 = u64x4::splat(0);
let mut z3_2 = u64x4::splat(0);
let mut z4_2 = u64x4::splat(0);
let mut z5_2 = u64x4::splat(0);
let mut z6_2 = u64x4::splat(0);
let mut z7_2 = u64x4::splat(0);
let mut z9_2 = u64x4::splat(0);
// Represent values with coeff. 4
let mut z2_4 = u64x4::splat(0);
let mut z3_4 = u64x4::splat(0);
let mut z4_4 = u64x4::splat(0);
let mut z5_4 = u64x4::splat(0);
let mut z6_4 = u64x4::splat(0);
let mut z7_4 = u64x4::splat(0);
let mut z8_4 = u64x4::splat(0);
let mut z0_1 = u64x4::splat(0);
z0_1 = madd52lo(z0_1, x[0], x[0]);
let mut z1_1 = u64x4::splat(0);
z1_2 = madd52lo(z1_2, x[0], x[1]);
z1_2 = madd52hi(z1_2, x[0], x[0]);
z2_4 = madd52hi(z2_4, x[0], x[1]);
let mut z2_1 = z2_4 << 2;
z2_2 = madd52lo(z2_2, x[0], x[2]);
z2_1 = madd52lo(z2_1, x[1], x[1]);
z3_4 = madd52hi(z3_4, x[0], x[2]);
let mut z3_1 = z3_4 << 2;
z3_2 = madd52lo(z3_2, x[1], x[2]);
z3_2 = madd52lo(z3_2, x[0], x[3]);
z3_2 = madd52hi(z3_2, x[1], x[1]);
z4_4 = madd52hi(z4_4, x[1], x[2]);
z4_4 = madd52hi(z4_4, x[0], x[3]);
let mut z4_1 = z4_4 << 2;
z4_2 = madd52lo(z4_2, x[1], x[3]);
z4_2 = madd52lo(z4_2, x[0], x[4]);
z4_1 = madd52lo(z4_1, x[2], x[2]);
z5_4 = madd52hi(z5_4, x[1], x[3]);
z5_4 = madd52hi(z5_4, x[0], x[4]);
let mut z5_1 = z5_4 << 2;
z5_2 = madd52lo(z5_2, x[2], x[3]);
z5_2 = madd52lo(z5_2, x[1], x[4]);
z5_2 = madd52hi(z5_2, x[2], x[2]);
z6_4 = madd52hi(z6_4, x[2], x[3]);
z6_4 = madd52hi(z6_4, x[1], x[4]);
let mut z6_1 = z6_4 << 2;
z6_2 = madd52lo(z6_2, x[2], x[4]);
z6_1 = madd52lo(z6_1, x[3], x[3]);
z7_4 = madd52hi(z7_4, x[2], x[4]);
let mut z7_1 = z7_4 << 2;
z7_2 = madd52lo(z7_2, x[3], x[4]);
z7_2 = madd52hi(z7_2, x[3], x[3]);
z8_4 = madd52hi(z8_4, x[3], x[4]);
let mut z8_1 = z8_4 << 2;
z8_1 = madd52lo(z8_1, x[4], x[4]);
let mut z9_1 = u64x4::splat(0);
z9_2 = madd52hi(z9_2, x[4], x[4]);
z5_1 += z5_2 << 1;
z6_1 += z6_2 << 1;
z7_1 += z7_2 << 1;
z9_1 += z9_2 << 1;
let mut t0 = u64x4::splat(0);
let mut t1 = u64x4::splat(0);
let r19 = u64x4::splat(19);
t0 = madd52hi(t0, r19, z9_1);
t1 = madd52lo(t1, r19, z9_1 >> 52);
z4_2 = madd52lo(z4_2, r19, z8_1 >> 52);
z3_2 = madd52lo(z3_2, r19, z7_1 >> 52);
z2_2 = madd52lo(z2_2, r19, z6_1 >> 52);
z1_2 = madd52lo(z1_2, r19, z5_1 >> 52);
z0_2 = madd52lo(z0_2, r19, t0 + t1);
z1_2 = madd52hi(z1_2, r19, z5_1);
z2_2 = madd52hi(z2_2, r19, z6_1);
z3_2 = madd52hi(z3_2, r19, z7_1);
z4_2 = madd52hi(z4_2, r19, z8_1);
z0_1 = madd52lo(z0_1, r19, z5_1);
z1_1 = madd52lo(z1_1, r19, z6_1);
z2_1 = madd52lo(z2_1, r19, z7_1);
z3_1 = madd52lo(z3_1, r19, z8_1);
z4_1 = madd52lo(z4_1, r19, z9_1);
F51x4Unreduced([
z0_1 + z0_2 + z0_2,
z1_1 + z1_2 + z1_2,
z2_1 + z2_2 + z2_2,
z3_1 + z3_2 + z3_2,
z4_1 + z4_2 + z4_2,
])
}
}
}
impl From<F51x4Reduced> for F51x4Unreduced {
#[inline]
fn from(x: F51x4Reduced) -> F51x4Unreduced {
F51x4Unreduced(x.0)
}
}
impl From<F51x4Unreduced> for F51x4Reduced {
#[inline]
fn from(x: F51x4Unreduced) -> F51x4Reduced {
let mask = u64x4::splat((1 << 51) - 1);
let r19 = u64x4::splat(19);
// Compute carryouts in parallel
let c0 = x.0[0] >> 51;
let c1 = x.0[1] >> 51;
let c2 = x.0[2] >> 51;
let c3 = x.0[3] >> 51;
let c4 = x.0[4] >> 51;
unsafe {
F51x4Reduced([
madd52lo(x.0[0] & mask, c4, r19),
(x.0[1] & mask) + c0,
(x.0[2] & mask) + c1,
(x.0[3] & mask) + c2,
(x.0[4] & mask) + c3,
])
}
}
}
impl Add<F51x4Unreduced> for F51x4Unreduced {
type Output = F51x4Unreduced;
#[inline]
fn add(self, rhs: F51x4Unreduced) -> F51x4Unreduced {
F51x4Unreduced([
self.0[0] + rhs.0[0],
self.0[1] + rhs.0[1],
self.0[2] + rhs.0[2],
self.0[3] + rhs.0[3],
self.0[4] + rhs.0[4],
])
}
}
impl<'a> Mul<(u32, u32, u32, u32)> for &'a F51x4Reduced {
type Output = F51x4Unreduced;
#[inline]
fn mul(self, scalars: (u32, u32, u32, u32)) -> F51x4Unreduced {
unsafe {
let x = &self.0;
let y = u64x4::new(
scalars.0 as u64,
scalars.1 as u64,
scalars.2 as u64,
scalars.3 as u64,
);
let r19 = u64x4::splat(19);
let mut z0_1 = u64x4::splat(0);
let mut z1_1 = u64x4::splat(0);
let mut z2_1 = u64x4::splat(0);
let mut z3_1 = u64x4::splat(0);
let mut z4_1 = u64x4::splat(0);
let mut z1_2 = u64x4::splat(0);
let mut z2_2 = u64x4::splat(0);
let mut z3_2 = u64x4::splat(0);
let mut z4_2 = u64x4::splat(0);
let mut z5_2 = u64x4::splat(0);
// Wave 0
z4_2 = madd52hi(z4_2, y, x[3]);
z5_2 = madd52hi(z5_2, y, x[4]);
z4_1 = madd52lo(z4_1, y, x[4]);
z0_1 = madd52lo(z0_1, y, x[0]);
z3_1 = madd52lo(z3_1, y, x[3]);
z2_1 = madd52lo(z2_1, y, x[2]);
z1_1 = madd52lo(z1_1, y, x[1]);
z3_2 = madd52hi(z3_2, y, x[2]);
// Wave 2
z2_2 = madd52hi(z2_2, y, x[1]);
z1_2 = madd52hi(z1_2, y, x[0]);
z0_1 = madd52lo(z0_1, z5_2 + z5_2, r19);
F51x4Unreduced([
z0_1,
z1_1 + z1_2 + z1_2,
z2_1 + z2_2 + z2_2,
z3_1 + z3_2 + z3_2,
z4_1 + z4_2 + z4_2,
])
}
}
}
impl<'a, 'b> Mul<&'b F51x4Reduced> for &'a F51x4Reduced {
type Output = F51x4Unreduced;
#[inline]
fn mul(self, rhs: &'b F51x4Reduced) -> F51x4Unreduced {
unsafe {
// Inputs
let x = &self.0;
let y = &rhs.0;
// Accumulators for terms with coeff 1
let mut z0_1 = u64x4::splat(0);
let mut z1_1 = u64x4::splat(0);
let mut z2_1 = u64x4::splat(0);
let mut z3_1 = u64x4::splat(0);
let mut z4_1 = u64x4::splat(0);
let mut z5_1 = u64x4::splat(0);
let mut z6_1 = u64x4::splat(0);
let mut z7_1 = u64x4::splat(0);
let mut z8_1 = u64x4::splat(0);
// Accumulators for terms with coeff 2
let mut z0_2 = u64x4::splat(0);
let mut z1_2 = u64x4::splat(0);
let mut z2_2 = u64x4::splat(0);
let mut z3_2 = u64x4::splat(0);
let mut z4_2 = u64x4::splat(0);
let mut z5_2 = u64x4::splat(0);
let mut z6_2 = u64x4::splat(0);
let mut z7_2 = u64x4::splat(0);
let mut z8_2 = u64x4::splat(0);
let mut z9_2 = u64x4::splat(0);
// LLVM doesn't seem to do much work reordering IFMA
// instructions, so try to organize them into "waves" of 8
// independent operations (4c latency, 0.5 c throughput
// means 8 in flight)
// Wave 0
z4_1 = madd52lo(z4_1, x[2], y[2]);
z5_2 = madd52hi(z5_2, x[2], y[2]);
z5_1 = madd52lo(z5_1, x[4], y[1]);
z6_2 = madd52hi(z6_2, x[4], y[1]);
z6_1 = madd52lo(z6_1, x[4], y[2]);
z7_2 = madd52hi(z7_2, x[4], y[2]);
z7_1 = madd52lo(z7_1, x[4], y[3]);
z8_2 = madd52hi(z8_2, x[4], y[3]);
// Wave 1
z4_1 = madd52lo(z4_1, x[3], y[1]);
z5_2 = madd52hi(z5_2, x[3], y[1]);
z5_1 = madd52lo(z5_1, x[3], y[2]);
z6_2 = madd52hi(z6_2, x[3], y[2]);
z6_1 = madd52lo(z6_1, x[3], y[3]);
z7_2 = madd52hi(z7_2, x[3], y[3]);
z7_1 = madd52lo(z7_1, x[3], y[4]);
z8_2 = madd52hi(z8_2, x[3], y[4]);
// Wave 2
z8_1 = madd52lo(z8_1, x[4], y[4]);
z9_2 = madd52hi(z9_2, x[4], y[4]);
z4_1 = madd52lo(z4_1, x[4], y[0]);
z5_2 = madd52hi(z5_2, x[4], y[0]);
z5_1 = madd52lo(z5_1, x[2], y[3]);
z6_2 = madd52hi(z6_2, x[2], y[3]);
z6_1 = madd52lo(z6_1, x[2], y[4]);
z7_2 = madd52hi(z7_2, x[2], y[4]);
let z8 = z8_1 + z8_2 + z8_2;
let z9 = z9_2 + z9_2;
// Wave 3
z3_1 = madd52lo(z3_1, x[3], y[0]);
z4_2 = madd52hi(z4_2, x[3], y[0]);
z4_1 = madd52lo(z4_1, x[1], y[3]);
z5_2 = madd52hi(z5_2, x[1], y[3]);
z5_1 = madd52lo(z5_1, x[1], y[4]);
z6_2 = madd52hi(z6_2, x[1], y[4]);
z2_1 = madd52lo(z2_1, x[2], y[0]);
z3_2 = madd52hi(z3_2, x[2], y[0]);
let z6 = z6_1 + z6_2 + z6_2;
let z7 = z7_1 + z7_2 + z7_2;
// Wave 4
z3_1 = madd52lo(z3_1, x[2], y[1]);
z4_2 = madd52hi(z4_2, x[2], y[1]);
z4_1 = madd52lo(z4_1, x[0], y[4]);
z5_2 = madd52hi(z5_2, x[0], y[4]);
z1_1 = madd52lo(z1_1, x[1], y[0]);
z2_2 = madd52hi(z2_2, x[1], y[0]);
z2_1 = madd52lo(z2_1, x[1], y[1]);
z3_2 = madd52hi(z3_2, x[1], y[1]);
let z5 = z5_1 + z5_2 + z5_2;
// Wave 5
z3_1 = madd52lo(z3_1, x[1], y[2]);
z4_2 = madd52hi(z4_2, x[1], y[2]);
z0_1 = madd52lo(z0_1, x[0], y[0]);
z1_2 = madd52hi(z1_2, x[0], y[0]);
z1_1 = madd52lo(z1_1, x[0], y[1]);
z2_1 = madd52lo(z2_1, x[0], y[2]);
z2_2 = madd52hi(z2_2, x[0], y[1]);
z3_2 = madd52hi(z3_2, x[0], y[2]);
let mut t0 = u64x4::splat(0);
let mut t1 = u64x4::splat(0);
let r19 = u64x4::splat(19);
// Wave 6
t0 = madd52hi(t0, r19, z9);
t1 = madd52lo(t1, r19, z9 >> 52);
z3_1 = madd52lo(z3_1, x[0], y[3]);
z4_2 = madd52hi(z4_2, x[0], y[3]);
z1_2 = madd52lo(z1_2, r19, z5 >> 52);
z2_2 = madd52lo(z2_2, r19, z6 >> 52);
z3_2 = madd52lo(z3_2, r19, z7 >> 52);
z0_1 = madd52lo(z0_1, r19, z5);
// Wave 7
z4_1 = madd52lo(z4_1, r19, z9);
z1_1 = madd52lo(z1_1, r19, z6);
z0_2 = madd52lo(z0_2, r19, t0 + t1);
z4_2 = madd52hi(z4_2, r19, z8);
z2_1 = madd52lo(z2_1, r19, z7);
z1_2 = madd52hi(z1_2, r19, z5);
z2_2 = madd52hi(z2_2, r19, z6);
z3_2 = madd52hi(z3_2, r19, z7);
// Wave 8
z3_1 = madd52lo(z3_1, r19, z8);
z4_2 = madd52lo(z4_2, r19, z8 >> 52);
F51x4Unreduced([
z0_1 + z0_2 + z0_2,
z1_1 + z1_2 + z1_2,
z2_1 + z2_2 + z2_2,
z3_1 + z3_2 + z3_2,
z4_1 + z4_2 + z4_2,
])
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn vpmadd52luq() {
let x = u64x4::splat(2);
let y = u64x4::splat(3);
let mut z = u64x4::splat(5);
z = unsafe { madd52lo(z, x, y) };
assert_eq!(z, u64x4::splat(5 + 2 * 3));
}
#[test]
fn new_split_round_trip_on_reduced_input() {
// Invert a small field element to get a big one
let a = FieldElement51([2438, 24, 243, 0, 0]).invert();
let ax4 = F51x4Unreduced::new(&a, &a, &a, &a);
let splits = ax4.split();
for i in 0..4 {
assert_eq!(a, splits[i]);
}
}
#[test]
fn new_split_round_trip_on_unreduced_input() {
// Invert a small field element to get a big one
let a = FieldElement51([2438, 24, 243, 0, 0]).invert();
// ... but now multiply it by 16 without reducing coeffs
let a16 = FieldElement51([
a.0[0] << 4,
a.0[1] << 4,
a.0[2] << 4,
a.0[3] << 4,
a.0[4] << 4,
]);
let a16x4 = F51x4Unreduced::new(&a16, &a16, &a16, &a16);
let splits = a16x4.split();
for i in 0..4 {
assert_eq!(a16, splits[i]);
}
}
#[test]
fn test_reduction() {
// Invert a small field element to get a big one
let a = FieldElement51([2438, 24, 243, 0, 0]).invert();
// ... but now multiply it by 128 without reducing coeffs
let abig = FieldElement51([
a.0[0] << 4,
a.0[1] << 4,
a.0[2] << 4,
a.0[3] << 4,
a.0[4] << 4,
]);
let abigx4: F51x4Reduced = F51x4Unreduced::new(&abig, &abig, &abig, &abig).into();
let splits = F51x4Unreduced::from(abigx4).split();
let c = &a * &FieldElement51([(1 << 4), 0, 0, 0, 0]);
for i in 0..4 {
assert_eq!(c, splits[i]);
}
}
#[test]
fn mul_matches_serial() {
// Invert a small field element to get a big one
let a = FieldElement51([2438, 24, 243, 0, 0]).invert();
let b = FieldElement51([98098, 87987897, 0, 1, 0]).invert();
let c = &a * &b;
let ax4: F51x4Reduced = F51x4Unreduced::new(&a, &a, &a, &a).into();
let bx4: F51x4Reduced = F51x4Unreduced::new(&b, &b, &b, &b).into();
let cx4 = &ax4 * &bx4;
let splits = cx4.split();
for i in 0..4 {
assert_eq!(c, splits[i]);
}
}
#[test]
fn iterated_mul_matches_serial() {
// Invert a small field element to get a big one
let a = FieldElement51([2438, 24, 243, 0, 0]).invert();
let b = FieldElement51([98098, 87987897, 0, 1, 0]).invert();
let mut c = &a * &b;
for _i in 0..1024 {
c = &a * &c;
c = &b * &c;
}
let ax4: F51x4Reduced = F51x4Unreduced::new(&a, &a, &a, &a).into();
let bx4: F51x4Reduced = F51x4Unreduced::new(&b, &b, &b, &b).into();
let mut cx4 = &ax4 * &bx4;
for _i in 0..1024 {
cx4 = &ax4 * &F51x4Reduced::from(cx4);
cx4 = &bx4 * &F51x4Reduced::from(cx4);
}
let splits = cx4.split();
for i in 0..4 {
assert_eq!(c, splits[i]);
}
}
#[test]
fn square_matches_mul() {
// Invert a small field element to get a big one
let a = FieldElement51([2438, 24, 243, 0, 0]).invert();
let ax4: F51x4Reduced = F51x4Unreduced::new(&a, &a, &a, &a).into();
let cx4 = &ax4 * &ax4;
let cx4_sq = ax4.square();
let splits = cx4.split();
let splits_sq = cx4_sq.split();
for i in 0..4 {
assert_eq!(splits_sq[i], splits[i]);
}
}
#[test]
fn iterated_square_matches_serial() {
// Invert a small field element to get a big one
let mut a = FieldElement51([2438, 24, 243, 0, 0]).invert();
let mut ax4 = F51x4Unreduced::new(&a, &a, &a, &a);
for _j in 0..1024 {
a = a.square();
ax4 = F51x4Reduced::from(ax4).square();
let splits = ax4.split();
for i in 0..4 {
assert_eq!(a, splits[i]);
}
}
}
#[test]
fn iterated_u32_mul_matches_serial() {
// Invert a small field element to get a big one
let a = FieldElement51([2438, 24, 243, 0, 0]).invert();
let b = FieldElement51([121665, 0, 0, 0, 0]);
let mut c = &a * &b;
for _i in 0..1024 {
c = &b * &c;
}
let ax4 = F51x4Unreduced::new(&a, &a, &a, &a);
let bx4 = (121665u32, 121665u32, 121665u32, 121665u32);
let mut cx4 = &F51x4Reduced::from(ax4) * bx4;
for _i in 0..1024 {
cx4 = &F51x4Reduced::from(cx4) * bx4;
}
let splits = cx4.split();
for i in 0..4 {
assert_eq!(c, splits[i]);
}
}
#[test]
fn shuffle_AAAA() {
let x0 = FieldElement51::from_bytes(&[0x10; 32]);
let x1 = FieldElement51::from_bytes(&[0x11; 32]);
let x2 = FieldElement51::from_bytes(&[0x12; 32]);
let x3 = FieldElement51::from_bytes(&[0x13; 32]);
let x = F51x4Unreduced::new(&x0, &x1, &x2, &x3);
let y = x.shuffle(Shuffle::AAAA);
let splits = y.split();
assert_eq!(splits[0], x0);
assert_eq!(splits[1], x0);
assert_eq!(splits[2], x0);
assert_eq!(splits[3], x0);
}
#[test]
fn blend_AB() {
let x0 = FieldElement51::from_bytes(&[0x10; 32]);
let x1 = FieldElement51::from_bytes(&[0x11; 32]);
let x2 = FieldElement51::from_bytes(&[0x12; 32]);
let x3 = FieldElement51::from_bytes(&[0x13; 32]);
let x = F51x4Unreduced::new(&x0, &x1, &x2, &x3);
let z = F51x4Unreduced::new(&x3, &x2, &x1, &x0);
let y = x.blend(&z, Lanes::AB);
let splits = y.split();
assert_eq!(splits[0], x3);
assert_eq!(splits[1], x2);
assert_eq!(splits[2], x2);
assert_eq!(splits[3], x3);
}
}

View file

@ -0,0 +1,19 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2018 Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Henry de Valence <hdevalence@hdevalence.ca>
#![cfg_attr(
all(feature = "nightly", feature = "stage2_build"),
doc(include = "../docs/ifma-notes.md")
)]
pub mod field;
pub mod edwards;
pub mod constants;

44
src/backend/vector/mod.rs Normal file
View file

@ -0,0 +1,44 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2016-2018 Isis Lovecruft, Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - Isis Agora Lovecruft <isis@patternsinthevoid.net>
// - Henry de Valence <hdevalence@hdevalence.ca>
// Conditionally include the notes if:
// - we're on nightly (so we can include docs at all)
// - we're in stage 2 of the build.
// The latter point prevents a really silly and annoying problem,
// where the location of ".." is different depending on whether we're
// building the crate for real, or whether we're in build.rs
// generating the lookup tables (in which case we're relative to the
// location of build.rs, not lib.rs, so the markdown file appears
// missing).
#![cfg_attr(
all(feature = "nightly", feature = "stage2_build"),
doc(include = "../docs/parallel-formulas.md")
)]
#[cfg(not(any(target_feature = "avx2", target_feature = "avx512ifma",)))]
compile_error!("simd_backend selected without target_feature=+avx2 or +avx512ifma");
#[cfg(any(all(target_feature = "avx2", not(target_feature = "avx512ifma")), rustdoc))]
#[doc(cfg(all(target_feature = "avx2", not(target_feature = "avx512ifma"))))]
pub mod avx2;
#[cfg(all(target_feature = "avx2", not(target_feature = "avx512ifma")))]
pub(crate) use self::avx2::{
constants::BASEPOINT_ODD_LOOKUP_TABLE, edwards::CachedPoint, edwards::ExtendedPoint,
};
#[cfg(any(target_feature = "avx512ifma", rustdoc))]
#[doc(cfg(target_feature = "avx512ifma"))]
pub mod ifma;
#[cfg(target_feature = "avx512ifma")]
pub(crate) use self::ifma::{
constants::BASEPOINT_ODD_LOOKUP_TABLE, edwards::CachedPoint, edwards::ExtendedPoint,
};
pub mod scalar_mul;

View file

@ -10,7 +10,10 @@
pub mod variable_base;
#[cfg(feature = "stage2_build")]
pub mod vartime_double_base;
#[cfg(feature = "alloc")]
pub mod straus;
#[cfg(feature = "alloc")]
pub mod precomputed_straus;

View file

@ -0,0 +1,111 @@
// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2019 Henry de Valence.
// See LICENSE for licensing information.
//
// Authors:
// - Henry de Valence <hdevalence@hdevalence.ca>
//! Precomputation for Straus's method.
#![allow(non_snake_case)]
use core::borrow::Borrow;
use backend::vector::{CachedPoint, ExtendedPoint};
use edwards::EdwardsPoint;
use scalar::Scalar;
use traits::Identity;
use traits::VartimePrecomputedMultiscalarMul;
use window::{NafLookupTable5, NafLookupTable8};
#[allow(unused_imports)]
use prelude::*;
pub struct VartimePrecomputedStraus {
static_lookup_tables: Vec<NafLookupTable8<CachedPoint>>,
}
impl VartimePrecomputedMultiscalarMul for VartimePrecomputedStraus {
type Point = EdwardsPoint;
fn new<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Self::Point>,
{
Self {
static_lookup_tables: static_points
.into_iter()
.map(|P| NafLookupTable8::<CachedPoint>::from(P.borrow()))
.collect(),
}
}
fn optional_mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Option<Self::Point>
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator<Item = Option<Self::Point>>,
{
let static_nafs = static_scalars
.into_iter()
.map(|c| c.borrow().non_adjacent_form(5))
.collect::<Vec<_>>();
let dynamic_nafs: Vec<_> = dynamic_scalars
.into_iter()
.map(|c| c.borrow().non_adjacent_form(5))
.collect::<Vec<_>>();
let dynamic_lookup_tables = match dynamic_points
.into_iter()
.map(|P_opt| P_opt.map(|P| NafLookupTable5::<CachedPoint>::from(&P)))
.collect::<Option<Vec<_>>>()
{
Some(x) => x,
None => return None,
};
let sp = self.static_lookup_tables.len();
let dp = dynamic_lookup_tables.len();
assert_eq!(sp, static_nafs.len());
assert_eq!(dp, dynamic_nafs.len());
// We could save some doublings by looking for the highest
// nonzero NAF coefficient, but since we might have a lot of
// them to search, it's not clear it's worthwhile to check.
let mut R = ExtendedPoint::identity();
for j in (0..255).rev() {
R = R.double();
for i in 0..dp {
let t_ij = dynamic_nafs[i][j];
if t_ij > 0 {
R = &R + &dynamic_lookup_tables[i].select(t_ij as usize);
} else if t_ij < 0 {
R = &R - &dynamic_lookup_tables[i].select(-t_ij as usize);
}
}
for i in 0..sp {
let t_ij = static_nafs[i][j];
if t_ij > 0 {
R = &R + &self.static_lookup_tables[i].select(t_ij as usize);
} else if t_ij < 0 {
R = &R - &self.static_lookup_tables[i].select(-t_ij as usize);
}
}
}
Some(R.into())
}
}

View file

@ -14,10 +14,10 @@ use core::borrow::Borrow;
use clear_on_drop::ClearOnDrop;
use backend::avx2::edwards::{CachedPoint, ExtendedPoint};
use backend::vector::{CachedPoint, ExtendedPoint};
use edwards::EdwardsPoint;
use scalar::Scalar;
use scalar_mul::window::{LookupTable, NafLookupTable5};
use window::{LookupTable, NafLookupTable5};
use traits::{Identity, MultiscalarMul, VartimeMultiscalarMul};
#[allow(unused_imports)]
@ -33,7 +33,6 @@ use prelude::*;
/// point representation on the fly.
pub struct Straus {}
#[cfg(feature = "alloc")]
impl MultiscalarMul for Straus {
type Point = EdwardsPoint;
@ -71,7 +70,6 @@ impl MultiscalarMul for Straus {
}
}
#[cfg(feature = "alloc")]
impl VartimeMultiscalarMul for Straus {
type Point = EdwardsPoint;

View file

@ -1,10 +1,10 @@
#![allow(non_snake_case)]
use traits::Identity;
use scalar::Scalar;
use backend::vector::{CachedPoint, ExtendedPoint};
use edwards::EdwardsPoint;
use backend::avx2::edwards::{ExtendedPoint, CachedPoint};
use scalar_mul::window::LookupTable;
use scalar::Scalar;
use traits::Identity;
use window::LookupTable;
/// Perform constant-time, variable-base scalar multiplication.
pub fn mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint {

View file

@ -9,12 +9,12 @@
// - Henry de Valence <hdevalence@hdevalence.ca>
#![allow(non_snake_case)]
use traits::Identity;
use scalar::Scalar;
use backend::vector::BASEPOINT_ODD_LOOKUP_TABLE;
use backend::vector::{CachedPoint, ExtendedPoint};
use edwards::EdwardsPoint;
use scalar_mul::window::NafLookupTable5;
use backend::avx2::edwards::{CachedPoint, ExtendedPoint};
use backend::avx2::constants::BASEPOINT_ODD_LOOKUP_TABLE;
use scalar::Scalar;
use traits::Identity;
use window::NafLookupTable5;
/// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the Ed25519 basepoint.
pub fn mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint {

View file

@ -34,9 +34,9 @@ use montgomery::MontgomeryPoint;
use scalar::Scalar;
#[cfg(feature = "u64_backend")]
pub use backend::u64::constants::*;
pub use backend::serial::u64::constants::*;
#[cfg(feature = "u32_backend")]
pub use backend::u32::constants::*;
pub use backend::serial::u32::constants::*;
/// The Ed25519 basepoint, in `CompressedEdwardsY` format.
///
@ -151,9 +151,9 @@ mod test {
#[test]
#[cfg(feature = "u32_backend")]
fn test_d_vs_ratio() {
use backend::u32::field::FieldElement32;
let a = -&FieldElement32([121665,0,0,0,0,0,0,0,0,0]);
let b = FieldElement32([121666,0,0,0,0,0,0,0,0,0]);
use backend::serial::u32::field::FieldElement2625;
let a = -&FieldElement2625([121665,0,0,0,0,0,0,0,0,0]);
let b = FieldElement2625([121666,0,0,0,0,0,0,0,0,0]);
let d = &a * &b.invert();
let d2 = &d + &d;
assert_eq!(d, constants::EDWARDS_D);
@ -164,9 +164,9 @@ mod test {
#[test]
#[cfg(feature = "u64_backend")]
fn test_d_vs_ratio() {
use backend::u64::field::FieldElement64;
let a = -&FieldElement64([121665,0,0,0,0]);
let b = FieldElement64([121666,0,0,0,0]);
use backend::serial::u64::field::FieldElement51;
let a = -&FieldElement51([121665,0,0,0,0]);
let b = FieldElement51([121666,0,0,0,0]);
let d = &a * &b.invert();
let d2 = &d + &d;
assert_eq!(d, constants::EDWARDS_D);

View file

@ -112,23 +112,34 @@ use scalar::Scalar;
use montgomery::MontgomeryPoint;
use curve_models::ProjectivePoint;
use curve_models::CompletedPoint;
use curve_models::AffineNielsPoint;
use curve_models::ProjectiveNielsPoint;
use backend::serial::curve_models::AffineNielsPoint;
use backend::serial::curve_models::CompletedPoint;
use backend::serial::curve_models::ProjectiveNielsPoint;
use backend::serial::curve_models::ProjectivePoint;
use window::LookupTable;
#[allow(unused_imports)]
use prelude::*;
use scalar_mul::window::LookupTable;
use traits::{Identity, IsIdentity};
use traits::ValidityCheck;
use traits::{Identity, IsIdentity};
#[cfg(any(feature = "alloc", feature = "std"))]
use traits::MultiscalarMul;
#[cfg(any(feature = "alloc", feature = "std"))]
use traits::VartimeMultiscalarMul;
use traits::{VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul};
#[cfg(not(all(
feature = "simd_backend",
any(target_feature = "avx2", target_feature = "avx512ifma")
)))]
use backend::serial::scalar_mul;
#[cfg(all(
feature = "simd_backend",
any(target_feature = "avx2", target_feature = "avx512ifma")
))]
use backend::vector::scalar_mul;
// ------------------------------------------------------------------------
// Compressed points
@ -439,6 +450,10 @@ impl EdwardsPoint {
/// Convert this `EdwardsPoint` on the Edwards model to the
/// corresponding `MontgomeryPoint` on the Montgomery model.
///
/// This function has one exceptional case; the identity point of
/// the Edwards curve is sent to the 2-torsion point \\((0,0)\\)
/// on the Montgomery curve.
///
/// Note that this is a one-way conversion, since the Montgomery
/// model does not retain sign information.
pub fn to_montgomery(&self) -> MontgomeryPoint {
@ -446,7 +461,7 @@ impl EdwardsPoint {
//
// The denominator is zero only when y=1, the identity point of
// the Edwards curve. Since 0.invert() = 0, in this case we
// compute u = 0, the identity point of the Montgomery line.
// compute the 2-torsion point (0,0).
let U = &self.Z + &self.Y;
let W = &self.Z - &self.Y;
let u = &U * &W.invert();
@ -576,18 +591,7 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a EdwardsPoint {
/// For scalar multiplication of a basepoint,
/// `EdwardsBasepointTable` is approximately 4x faster.
fn mul(self, scalar: &'b Scalar) -> EdwardsPoint {
// If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="avx2_backend", target_feature="avx2"))]
{
use backend::avx2::scalar_mul::variable_base::mul;
mul(self, scalar)
}
// Otherwise, use the serial backend:
#[cfg(not(all(feature="avx2_backend", target_feature="avx2")))]
{
use scalar_mul::variable_base::mul;
mul(self, scalar)
}
scalar_mul::variable_base::mul(self, scalar)
}
}
@ -613,7 +617,7 @@ impl<'a, 'b> Mul<&'b EdwardsPoint> for &'a Scalar {
#[cfg(feature = "alloc")]
impl MultiscalarMul for EdwardsPoint {
type Point = EdwardsPoint;
fn multiscalar_mul<I, J>(scalars: I, points: J) -> EdwardsPoint
where
I: IntoIterator,
@ -638,21 +642,14 @@ impl MultiscalarMul for EdwardsPoint {
// size-dependent algorithm dispatch, use this as the hint.
let _size = s_lo;
// If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="avx2_backend", target_feature="avx2"))]
use backend::avx2::scalar_mul::straus::Straus;
// Otherwise, proceed as normal:
#[cfg(not(all(feature="avx2_backend", target_feature="avx2")))]
use scalar_mul::straus::Straus;
Straus::multiscalar_mul(scalars, points)
scalar_mul::straus::Straus::multiscalar_mul(scalars, points)
}
}
#[cfg(feature = "alloc")]
impl VartimeMultiscalarMul for EdwardsPoint {
type Point = EdwardsPoint;
fn optional_multiscalar_mul<I, J>(scalars: I, points: J) -> Option<EdwardsPoint>
where
I: IntoIterator,
@ -676,29 +673,56 @@ impl VartimeMultiscalarMul for EdwardsPoint {
// size-dependent algorithm dispatch, use this as the hint.
let _size = s_lo;
// If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="avx2_backend", target_feature="avx2"))]
use backend::avx2::scalar_mul::straus::Straus;
// Otherwise, proceed as normal:
#[cfg(not(all(feature="avx2_backend", target_feature="avx2")))]
use scalar_mul::straus::Straus;
scalar_mul::straus::Straus::optional_multiscalar_mul(scalars, points)
}
}
Straus::optional_multiscalar_mul(scalars, points)
/// Precomputation for variable-time multiscalar multiplication with `EdwardsPoint`s.
// This wraps the inner implementation in a facade type so that we can
// decouple stability of the inner type from the stability of the
// outer type.
#[cfg(feature = "alloc")]
pub struct VartimeEdwardsPrecomputation(scalar_mul::precomputed_straus::VartimePrecomputedStraus);
#[cfg(feature = "alloc")]
impl VartimePrecomputedMultiscalarMul for VartimeEdwardsPrecomputation {
type Point = EdwardsPoint;
fn new<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Self::Point>,
{
Self(scalar_mul::precomputed_straus::VartimePrecomputedStraus::new(static_points))
}
fn optional_mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Option<Self::Point>
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator<Item = Option<Self::Point>>,
{
self.0
.optional_mixed_multiscalar_mul(static_scalars, dynamic_scalars, dynamic_points)
}
}
impl EdwardsPoint {
/// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the Ed25519 basepoint.
#[cfg(feature = "stage2_build")]
pub fn vartime_double_scalar_mul_basepoint(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint {
// If we built with AVX2, use the AVX2 backend.
#[cfg(all(feature="avx2_backend", target_feature="avx2"))]
use backend::avx2::scalar_mul::vartime_double_base;
// Otherwise, use the serial backend:
#[cfg(not(all(feature="avx2_backend", target_feature="avx2")))]
use scalar_mul::vartime_double_base;
vartime_double_base::mul(a, A, b)
pub fn vartime_double_scalar_mul_basepoint(
a: &Scalar,
A: &EdwardsPoint,
b: &Scalar,
) -> EdwardsPoint {
scalar_mul::vartime_double_base::mul(a, A, b)
}
}
@ -1220,6 +1244,49 @@ mod test {
assert!(P1.compress().to_bytes() == P2.compress().to_bytes());
}
#[test]
fn vartime_precomputed_vs_nonprecomputed_multiscalar() {
let mut rng = rand::thread_rng();
let B = &::constants::ED25519_BASEPOINT_TABLE;
let static_scalars = (0..128)
.map(|_| Scalar::random(&mut rng))
.collect::<Vec<_>>();
let dynamic_scalars = (0..128)
.map(|_| Scalar::random(&mut rng))
.collect::<Vec<_>>();
let check_scalar: Scalar = static_scalars
.iter()
.chain(dynamic_scalars.iter())
.map(|s| s * s)
.sum();
let static_points = static_scalars.iter().map(|s| s * B).collect::<Vec<_>>();
let dynamic_points = dynamic_scalars.iter().map(|s| s * B).collect::<Vec<_>>();
let precomputation = VartimeEdwardsPrecomputation::new(static_points.iter());
let P = precomputation.vartime_mixed_multiscalar_mul(
&static_scalars,
&dynamic_scalars,
&dynamic_points,
);
use traits::VartimeMultiscalarMul;
let Q = EdwardsPoint::vartime_multiscalar_mul(
static_scalars.iter().chain(dynamic_scalars.iter()),
static_points.iter().chain(dynamic_points.iter()),
);
let R = &check_scalar * B;
assert_eq!(P.compress(), R.compress());
assert_eq!(Q.compress(), R.compress());
}
mod vartime {
use super::super::*;
use super::{A_SCALAR, B_SCALAR, A_TIMES_BASEPOINT, DOUBLE_SCALAR_MULT_RESULT};

View file

@ -12,8 +12,8 @@
//!
//! The `curve25519_dalek::field` module provides a type alias
//! `curve25519_dalek::field::FieldElement` to a field element type
//! defined in the `backend` module; either `FieldElement64` or
//! `FieldElement32`.
//! defined in the `backend` module; either `FieldElement51` or
//! `FieldElement2625`.
//!
//! Field operations defined in terms of machine
//! operations, such as field multiplication or squaring, are defined in
@ -33,24 +33,24 @@ use constants;
use backend;
#[cfg(feature = "u64_backend")]
pub use backend::u64::field::*;
pub use backend::serial::u64::field::*;
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// The `FieldElement` type is an alias for one of the platform-specific
/// implementations.
#[cfg(feature = "u64_backend")]
pub type FieldElement = backend::u64::field::FieldElement64;
pub type FieldElement = backend::serial::u64::field::FieldElement51;
#[cfg(feature = "u32_backend")]
pub use backend::u32::field::*;
pub use backend::serial::u32::field::*;
/// A `FieldElement` represents an element of the field
/// \\( \mathbb Z / (2\^{255} - 19)\\).
///
/// The `FieldElement` type is an alias for one of the platform-specific
/// implementations.
#[cfg(feature = "u32_backend")]
pub type FieldElement = backend::u32::field::FieldElement32;
pub type FieldElement = backend::serial::u32::field::FieldElement2625;
impl Eq for FieldElement {}
@ -255,7 +255,7 @@ impl FieldElement {
(was_nonzero_square, r)
}
/// Attempt to compute `1/sqrt(self)` in constant time.
/// Attempt to compute `sqrt(1/self)` in constant time.
///
/// Convenience wrapper around `sqrt_ratio_i`.
///
@ -265,7 +265,7 @@ impl FieldElement {
///
/// - `(Choice(1), +sqrt(1/self)) ` if `self` is a nonzero square;
/// - `(Choice(0), zero) ` if `self` is zero;
/// - `(Choice(0), +sqrt(i*u/v)) ` if `self` is a nonzero nonsquare;
/// - `(Choice(0), +sqrt(i/self)) ` if `self` is a nonzero nonsquare;
///
pub fn invsqrt(&self) -> (Choice, FieldElement) {
FieldElement::sqrt_ratio_i(&FieldElement::one(), self)
@ -280,7 +280,7 @@ mod test {
/// Random element a of GF(2^255-19), from Sage
/// a = 1070314506888354081329385823235218444233221\
/// 2228051251926706380353716438957572
pub static A_BYTES: [u8; 32] =
static A_BYTES: [u8; 32] =
[ 0x04, 0xfe, 0xdf, 0x98, 0xa7, 0xfa, 0x0a, 0x68,
0x84, 0x92, 0xbd, 0x59, 0x08, 0x07, 0xa7, 0x03,
0x9e, 0xd1, 0xf6, 0xf2, 0xe1, 0xd9, 0xe2, 0xa4,

View file

@ -9,11 +9,17 @@
// - Henry de Valence <hdevalence@hdevalence.ca>
#![no_std]
#![cfg_attr(
any(
all(feature = "simd_backend", target_feature = "avx512ifma"),
all(feature = "nightly", rustdoc)
),
feature(simd_ffi, link_llvm_intrinsics)
)]
#![cfg_attr(feature = "nightly", feature(test))]
#![cfg_attr(all(feature = "alloc", not(feature = "std")), feature(alloc))]
#![cfg_attr(feature = "nightly", feature(cfg_target_feature))]
#![cfg_attr(feature = "nightly", feature(external_doc))]
#![cfg_attr(feature = "nightly", feature(doc_cfg))]
// Refuse to compile if documentation is missing, but only on nightly.
//
// This means that missing docs will still fail CI, but means we can use
@ -38,21 +44,23 @@ extern crate alloc;
#[macro_use]
extern crate std;
#[cfg(all(feature = "nightly", feature = "avx2_backend"))]
#[cfg(all(feature = "nightly", feature = "packed_simd"))]
extern crate packed_simd;
extern crate rand;
extern crate clear_on_drop;
extern crate byteorder;
extern crate clear_on_drop;
pub extern crate digest;
extern crate rand_core;
#[cfg(all(test, feature = "stage2_build"))]
extern crate rand_os;
// Used for traits related to constant-time code.
extern crate subtle;
#[cfg(feature = "serde")]
extern crate serde;
#[cfg(all(test, feature = "serde"))]
extern crate bincode;
#[cfg(feature = "serde")]
extern crate serde;
// Internal macros. Must come first!
#[macro_use]
@ -90,11 +98,8 @@ pub(crate) mod field;
// Arithmetic backends (using u32, u64, etc) live here
pub(crate) mod backend;
// Internal curve models which are not part of the public API.
pub(crate) mod curve_models;
// Crate-local prelude (for alloc-dependent features like `Vec`)
pub(crate) mod prelude;
// Implementations of scalar mul algorithms live here
pub(crate) mod scalar_mul;
// Generic code for window lookups
pub(crate) mod window;

View file

@ -305,7 +305,7 @@ mod test {
use super::*;
#[cfg(feature = "rand")]
use rand::rngs::OsRng;
use rand_os::OsRng;
/// Test Montgomery -> Edwards on the X/Ed25519 basepoint
#[test]

View file

@ -164,7 +164,7 @@ use core::ops::{Add, Neg, Sub};
use core::ops::{AddAssign, SubAssign};
use core::ops::{Mul, MulAssign};
use rand::{CryptoRng, Rng};
use rand_core::{CryptoRng, RngCore};
use digest::generic_array::typenum::U64;
use digest::Digest;
@ -185,11 +185,20 @@ use prelude::*;
use scalar::Scalar;
use curve_models::CompletedPoint;
use traits::Identity;
#[cfg(any(feature = "alloc", feature = "std"))]
use traits::{MultiscalarMul, VartimeMultiscalarMul};
use traits::{MultiscalarMul, VartimeMultiscalarMul, VartimePrecomputedMultiscalarMul};
#[cfg(not(all(
feature = "simd_backend",
any(target_feature = "avx2", target_feature = "avx512ifma")
)))]
use backend::serial::scalar_mul;
#[cfg(all(
feature = "simd_backend",
any(target_feature = "avx2", target_feature = "avx512ifma")
))]
use backend::vector::scalar_mul;
// ------------------------------------------------------------------------
// Compressed points
@ -468,8 +477,8 @@ impl RistrettoPoint {
/// ```
/// # extern crate curve25519_dalek;
/// # use curve25519_dalek::ristretto::RistrettoPoint;
/// extern crate rand;
/// use rand::rngs::OsRng;
/// extern crate rand_os;
/// use rand_os::OsRng;
///
/// # // Need fn main() here in comment so the doctest compiles
/// # // See https://doc.rust-lang.org/book/documentation.html#documentation-as-tests
@ -602,6 +611,8 @@ impl RistrettoPoint {
let N_t = &(&(&c * &(&r - &one)) * &d_minus_one_sq) - &D;
let s_sq = s.square();
use backend::serial::curve_models::CompletedPoint;
// The conversion from W_i is exactly the conversion from P1xP1.
RistrettoPoint(CompletedPoint{
X: &(&s + &s) * &D,
@ -615,7 +626,7 @@ impl RistrettoPoint {
///
/// # Inputs
///
/// * `rng`: any RNG which implements the `rand::Rng` interface.
/// * `rng`: any RNG which implements the `RngCore + CryptoRng` interface.
///
/// # Returns
///
@ -627,9 +638,9 @@ impl RistrettoPoint {
/// discrete log of the output point with respect to any other
/// point should be unknown. The map is applied twice and the
/// results are added, to ensure a uniform distribution.
pub fn random<T: Rng + CryptoRng>(rng: &mut T) -> Self {
pub fn random<T: RngCore + CryptoRng>(mut rng: T) -> Self {
let mut uniform_bytes = [0u8; 64];
rng.fill(&mut uniform_bytes);
rng.fill_bytes(&mut uniform_bytes);
RistrettoPoint::from_uniform_bytes(&uniform_bytes)
}
@ -891,8 +902,53 @@ impl VartimeMultiscalarMul for RistrettoPoint {
{
let extended_points = points.into_iter().map(|opt_P| opt_P.map(|P| P.borrow().0));
EdwardsPoint::optional_multiscalar_mul(scalars, extended_points)
.map(|P| RistrettoPoint(P))
EdwardsPoint::optional_multiscalar_mul(scalars, extended_points).map(|P| RistrettoPoint(P))
}
}
/// Precomputation for variable-time multiscalar multiplication with `RistrettoPoint`s.
// This wraps the inner implementation in a facade type so that we can
// decouple stability of the inner type from the stability of the
// outer type.
#[cfg(feature = "alloc")]
pub struct VartimeRistrettoPrecomputation(scalar_mul::precomputed_straus::VartimePrecomputedStraus);
#[cfg(feature = "alloc")]
impl VartimePrecomputedMultiscalarMul for VartimeRistrettoPrecomputation {
type Point = RistrettoPoint;
fn new<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Self::Point>,
{
Self(
scalar_mul::precomputed_straus::VartimePrecomputedStraus::new(
static_points.into_iter().map(|P| P.borrow().0),
),
)
}
fn optional_mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Option<Self::Point>
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator<Item = Option<Self::Point>>,
{
self.0
.optional_mixed_multiscalar_mul(
static_scalars,
dynamic_scalars,
dynamic_points.into_iter().map(|P_opt| P_opt.map(|P| P.0)),
)
.map(|P_ed| RistrettoPoint(P_ed))
}
}
@ -1020,7 +1076,7 @@ impl Debug for RistrettoPoint {
#[cfg(all(test, feature = "stage2_build"))]
mod test {
#[cfg(feature = "rand")]
use rand::rngs::OsRng;
use rand_os::OsRng;
use scalar::Scalar;
use constants;
@ -1263,4 +1319,47 @@ mod test {
P.compress();
}
}
#[test]
fn vartime_precomputed_vs_nonprecomputed_multiscalar() {
let mut rng = rand::thread_rng();
let B = &::constants::RISTRETTO_BASEPOINT_TABLE;
let static_scalars = (0..128)
.map(|_| Scalar::random(&mut rng))
.collect::<Vec<_>>();
let dynamic_scalars = (0..128)
.map(|_| Scalar::random(&mut rng))
.collect::<Vec<_>>();
let check_scalar: Scalar = static_scalars
.iter()
.chain(dynamic_scalars.iter())
.map(|s| s * s)
.sum();
let static_points = static_scalars.iter().map(|s| s * B).collect::<Vec<_>>();
let dynamic_points = dynamic_scalars.iter().map(|s| s * B).collect::<Vec<_>>();
let precomputation = VartimeRistrettoPrecomputation::new(static_points.iter());
let P = precomputation.vartime_mixed_multiscalar_mul(
&static_scalars,
&dynamic_scalars,
&dynamic_points,
);
use traits::VartimeMultiscalarMul;
let Q = RistrettoPoint::vartime_multiscalar_mul(
static_scalars.iter().chain(dynamic_scalars.iter()),
static_points.iter().chain(dynamic_points.iter()),
);
let R = &check_scalar * B;
assert_eq!(P.compress(), R.compress());
assert_eq!(Q.compress(), R.compress());
}
}

View file

@ -151,7 +151,7 @@ use core::ops::{Sub, SubAssign};
#[allow(unused_imports)]
use prelude::*;
use rand::{CryptoRng, Rng};
use rand_core::{CryptoRng, RngCore};
use digest::generic_array::typenum::U64;
use digest::Digest;
@ -168,14 +168,14 @@ use constants;
/// This is a type alias for one of the scalar types in the `backend`
/// module.
#[cfg(feature = "u64_backend")]
type UnpackedScalar = backend::u64::scalar::Scalar64;
type UnpackedScalar = backend::serial::u64::scalar::Scalar52;
/// An `UnpackedScalar` represents an element of the field GF(l), optimized for speed.
///
/// This is a type alias for one of the scalar types in the `backend`
/// module.
#[cfg(feature = "u32_backend")]
type UnpackedScalar = backend::u32::scalar::Scalar32;
type UnpackedScalar = backend::serial::u32::scalar::Scalar29;
/// The `Scalar` struct holds an integer \\(s < 2\^{255} \\) which
@ -507,7 +507,7 @@ impl Scalar {
///
/// # Inputs
///
/// * `rng`: any RNG which implements the `rand::CryptoRng` interface.
/// * `rng`: any RNG which implements the `RngCore + CryptoRng` interface.
///
/// # Returns
///
@ -516,20 +516,20 @@ impl Scalar {
/// # Example
///
/// ```
/// extern crate rand;
/// extern crate rand_os;
/// # extern crate curve25519_dalek;
/// #
/// # fn main() {
/// use curve25519_dalek::scalar::Scalar;
///
/// use rand::OsRng;
/// use rand_os::OsRng;
///
/// let mut csprng: OsRng = OsRng::new().unwrap();
/// let a: Scalar = Scalar::random(&mut csprng);
/// # }
pub fn random<T: Rng + CryptoRng>(rng: &mut T) -> Self {
pub fn random<T: RngCore + CryptoRng>(mut rng: T) -> Self {
let mut scalar_bytes = [0u8; 64];
rng.fill(&mut scalar_bytes);
rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}

View file

@ -219,11 +219,149 @@ pub trait VartimeMultiscalarMul {
{
Self::optional_multiscalar_mul(
scalars,
points.into_iter().map(|P| Some(P.borrow().clone()))
).unwrap()
points.into_iter().map(|P| Some(P.borrow().clone())),
)
.unwrap()
}
}
/// A trait for variable-time multiscalar multiplication with precomputation.
///
/// A general multiscalar multiplication with precomputation can be written as
/// $$
/// Q = a_1 A_1 + \cdots + a_n A_n + b_1 B_1 + \cdots + b_m B_m,
/// $$
/// where the \\(B_i\\) are *static* points, for which precomputation
/// is possible, and the \\(A_j\\) are *dynamic* points, for which
/// precomputation is not possible.
///
/// This trait has three methods for performing this computation:
///
/// * [`vartime_multiscalar_mul`], which handles the special case
/// where \\(n = 0\\) and there are no dynamic points;
///
/// * [`vartime_mixed_multiscalar_mul`], which takes the dynamic
/// points as already-validated `Point`s and is infallible;
///
/// * [`optional_mixed_multiscalar_mul`], which takes the dynamic
/// points as `Option<Point>`s and returns an `Option<Point>`,
/// allowing decompression to be composed into the input iterators.
///
/// All methods require that the lengths of the input iterators be
/// known and matching, as if they were `ExactSizeIterator`s. (It
/// does not require `ExactSizeIterator` only because that trait is
/// broken).
pub trait VartimePrecomputedMultiscalarMul: Sized {
/// The type of point to be multiplied, e.g., `RistrettoPoint`.
type Point: Clone;
/// Given the static points \\( B_i \\), perform precomputation
/// and return the precomputation data.
fn new<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Self::Point>;
/// Given `static_scalars`, an iterator of public scalars
/// \\(b_i\\), compute
/// $$
/// Q = b_1 B_1 + \cdots + b_m B_m,
/// $$
/// where the \\(B_j\\) are the points that were supplied to `new`.
///
/// It is an error to call this function with iterators of
/// inconsistent lengths.
///
/// The trait bound aims for maximum flexibility: the input must
/// be convertable to iterators (`I: IntoIter`), and the
/// iterator's items must be `Borrow<Scalar>`, to allow iterators
/// returning either `Scalar`s or `&Scalar`s.
fn vartime_multiscalar_mul<I>(&self, static_scalars: I) -> Self::Point
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
{
use core::iter;
Self::vartime_mixed_multiscalar_mul(
self,
static_scalars,
iter::empty::<Scalar>(),
iter::empty::<Self::Point>(),
)
}
/// Given `static_scalars`, an iterator of public scalars
/// \\(b_i\\), `dynamic_scalars`, an iterator of public scalars
/// \\(a_i\\), and `dynamic_points`, an iterator of points
/// \\(A_i\\), compute
/// $$
/// Q = a_1 A_1 + \cdots + a_n A_n + b_1 B_1 + \cdots + b_m B_m,
/// $$
/// where the \\(B_j\\) are the points that were supplied to `new`.
///
/// It is an error to call this function with iterators of
/// inconsistent lengths.
///
/// The trait bound aims for maximum flexibility: the inputs must be
/// convertable to iterators (`I: IntoIter`), and the iterator's items
/// must be `Borrow<Scalar>` (or `Borrow<Point>`), to allow
/// iterators returning either `Scalar`s or `&Scalar`s.
fn vartime_mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Self::Point
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator,
K::Item: Borrow<Self::Point>,
{
Self::optional_mixed_multiscalar_mul(
self,
static_scalars,
dynamic_scalars,
dynamic_points.into_iter().map(|P| Some(P.borrow().clone())),
)
.unwrap()
}
/// Given `static_scalars`, an iterator of public scalars
/// \\(b_i\\), `dynamic_scalars`, an iterator of public scalars
/// \\(a_i\\), and `dynamic_points`, an iterator of points
/// \\(A_i\\), compute
/// $$
/// Q = a_1 A_1 + \cdots + a_n A_n + b_1 B_1 + \cdots + b_m B_m,
/// $$
/// where the \\(B_j\\) are the points that were supplied to `new`.
///
/// If any of the dynamic points were `None`, return `None`.
///
/// It is an error to call this function with iterators of
/// inconsistent lengths.
///
/// This function is particularly useful when verifying statements
/// involving compressed points. Accepting `Option<Point>` allows
/// inlining point decompression into the multiscalar call,
/// avoiding the need for temporary buffers.
fn optional_mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Option<Self::Point>
where
I: IntoIterator,
I::Item: Borrow<Scalar>,
J: IntoIterator,
J::Item: Borrow<Scalar>,
K: IntoIterator<Item = Option<Self::Point>>;
}
// ------------------------------------------------------------------------
// Private Traits
// ------------------------------------------------------------------------

View file

@ -22,8 +22,8 @@ use subtle::Choice;
use traits::Identity;
use edwards::EdwardsPoint;
use curve_models::ProjectiveNielsPoint;
use curve_models::AffineNielsPoint;
use backend::serial::curve_models::ProjectiveNielsPoint;
use backend::serial::curve_models::AffineNielsPoint;
/// A lookup table of precomputed multiples of a point \\(P\\), used to
/// compute \\( xP \\) for \\( -8 \leq x \leq 8 \\).