mirror of
https://github.com/saymrwulf/anza-cryptography-source.git
synced 2026-09-03 20:13:46 +00:00
impl secp256r1 (#45)
* impl secp256r1 * CI * refactor: remove sigantures; add msm * clean up scalar_mul APIs * ci * address comments * CI * fix Eq/PartialEq bug for projective point
This commit is contained in:
parent
06cc7111db
commit
dd4164eefe
11 changed files with 2923 additions and 0 deletions
141
Cargo.lock
generated
141
Cargo.lock
generated
|
|
@ -222,6 +222,12 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base16ct"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.3"
|
||||
|
|
@ -539,6 +545,18 @@ version = "0.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
|
|
@ -681,6 +699,23 @@ version = "1.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.13.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"ff",
|
||||
"generic-array",
|
||||
"group",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-ordinalize"
|
||||
version = "4.3.2"
|
||||
|
|
@ -768,6 +803,21 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
|
|
@ -806,6 +856,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
|||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1103,12 +1154,59 @@ version = "11.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.116"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p3-air"
|
||||
version = "0.4.3"
|
||||
|
|
@ -1421,6 +1519,12 @@ dependencies = [
|
|||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
|
|
@ -1468,6 +1572,15 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
|
|
@ -1708,6 +1821,28 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
"generic-array",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256r1"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"openssl",
|
||||
"p256",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
|
|
@ -2086,6 +2221,12 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ members = [
|
|||
"curve25519/curve25519-cuda",
|
||||
"curve25519/solana-ed25519",
|
||||
"experimental/ed25519-pokos",
|
||||
"secp256r1",
|
||||
"syscall/bls12-381-syscall",
|
||||
"syscall/bn254-syscall",
|
||||
]
|
||||
|
|
@ -41,7 +42,10 @@ group = { version = "0.13.0", default-features = false }
|
|||
hashbrown = "0.15"
|
||||
hex = "0.4.2"
|
||||
hex-literal = "1.1.0"
|
||||
hmac = "0.12"
|
||||
openssl = "0.10"
|
||||
pairing = "0.23.0"
|
||||
p256 = { version = "0.13", default-features = false, features = ["arithmetic", "expose-field"] }
|
||||
pkcs8 = { version = "0.10.1" }
|
||||
once_cell = "1.21"
|
||||
proptest = "=1.6.0"
|
||||
|
|
|
|||
27
secp256r1/Cargo.toml
Normal file
27
secp256r1/Cargo.toml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "secp256r1"
|
||||
description = "Pure-Rust secp256r1/P-256 field, scalar, and group operations"
|
||||
version = "0.1.0"
|
||||
authors = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
license = { workspace = true }
|
||||
edition = "2024"
|
||||
readme = "README.md"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion.workspace = true
|
||||
openssl.workspace = true
|
||||
p256.workspace = true
|
||||
|
||||
[[bench]]
|
||||
name = "field"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "group"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "scalar"
|
||||
harness = false
|
||||
125
secp256r1/README.md
Normal file
125
secp256r1/README.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# secp256r1
|
||||
|
||||
Pure-Rust secp256r1/P-256 field, scalar, and group operations.
|
||||
|
||||
This crate is scoped to low-level public curve arithmetic for benchmarking,
|
||||
experimentation, and syscall plumbing. It does not expose ECDSA signing or
|
||||
verification APIs.
|
||||
|
||||
## Status
|
||||
|
||||
This crate is performance-oriented and experimental. It has not been audited.
|
||||
Group scalar multiplication APIs are variable time and intended for public
|
||||
inputs. Do not use them with secret scalars in environments where local
|
||||
timing/cache side channels are in scope.
|
||||
|
||||
Current scope:
|
||||
|
||||
- Base-field arithmetic modulo the P-256 field modulus
|
||||
- Scalar-field arithmetic modulo the P-256 group order
|
||||
- Affine and Jacobian projective point operations
|
||||
- Compressed and uncompressed fixed-length point input
|
||||
- Uncompressed fixed-length point output
|
||||
- Single-scalar, fixed-base scalar, double-scalar, and multiscalar multiplication
|
||||
|
||||
OpenSSL and `p256` are used only as dev/benchmark comparison dependencies.
|
||||
|
||||
## Installation
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
secp256r1 = { path = "." }
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```rust
|
||||
use secp256r1::{
|
||||
group::{AffinePoint, ProjectivePoint},
|
||||
scalar::Scalar,
|
||||
};
|
||||
```
|
||||
|
||||
### Scalar Multiplication
|
||||
|
||||
```rust
|
||||
use secp256r1::group::{AffinePoint, ProjectivePoint};
|
||||
|
||||
let scalar = [7u8; 32];
|
||||
|
||||
let fixed_base = ProjectivePoint::fixed_base_scalar_mul_vartime(scalar);
|
||||
let variable_base = ProjectivePoint::from_affine(AffinePoint::generator())
|
||||
.mul_scalar_vartime(scalar);
|
||||
|
||||
assert_eq!(fixed_base.to_affine(), variable_base.to_affine());
|
||||
```
|
||||
|
||||
### Multiscalar Multiplication
|
||||
|
||||
```rust
|
||||
use secp256r1::group::{AffinePoint, ProjectivePoint};
|
||||
|
||||
let points = [AffinePoint::generator(), ProjectivePoint::generator().double().to_affine()];
|
||||
let scalars = [[7u8; 32], [11u8; 32]];
|
||||
|
||||
let msm = ProjectivePoint::multi_scalar_mul_vartime(&points, &scalars).unwrap();
|
||||
let separate = ProjectivePoint::from_affine(points[0]).mul_scalar_vartime(scalars[0])
|
||||
+ ProjectivePoint::from_affine(points[1]).mul_scalar_vartime(scalars[1]);
|
||||
|
||||
assert_eq!(msm.to_affine(), separate.to_affine());
|
||||
```
|
||||
|
||||
### Encoded Points
|
||||
|
||||
```rust
|
||||
use secp256r1::group::{AffinePoint, ProjectivePoint};
|
||||
|
||||
let uncompressed = ProjectivePoint::generator().to_uncompressed().unwrap();
|
||||
let parsed = AffinePoint::from_uncompressed(uncompressed).unwrap();
|
||||
|
||||
assert_eq!(parsed, AffinePoint::generator());
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Run all secp256r1 benchmarks:
|
||||
|
||||
```sh
|
||||
cargo bench -p secp256r1
|
||||
```
|
||||
|
||||
Focused benchmark groups:
|
||||
|
||||
```sh
|
||||
cargo bench -p secp256r1 --bench field
|
||||
cargo bench -p secp256r1 --bench scalar
|
||||
cargo bench -p secp256r1 --bench group
|
||||
```
|
||||
|
||||
Representative local results from this workspace:
|
||||
|
||||
### Group Ops
|
||||
|
||||
| Benchmark | rust | p256 | OpenSSL |
|
||||
|---|---:|---:|---:|
|
||||
| point double | 81.184 ns | 198.83 ns | 222.82 ns public EC |
|
||||
| point add | 131.49 ns | 222.38 ns | 216.44 ns public EC |
|
||||
| mixed add | 95.753 ns | 195.68 ns | n/a |
|
||||
| variable-base scalar mul | 30.579 us | 75.541 us | n/a |
|
||||
| fixed-base scalar mul | 3.087 us | n/a | 3.539 us |
|
||||
| double scalar mul | 36.716 us | 150.58 us separate | 25.352 us |
|
||||
|
||||
### Multiscalar Multiplication
|
||||
|
||||
| Benchmark | rust MSM | rust separate | p256 separate |
|
||||
|---|---:|---:|---:|
|
||||
| 8-point MSM | 96.571 us | 244.41 us | 601.21 us |
|
||||
| 32-point MSM | 322.63 us | 1.300 ms | 2.410 ms |
|
||||
|
||||
Benchmark numbers are machine- and compiler-dependent. Re-run locally before
|
||||
making performance decisions.
|
||||
|
||||
## Safety
|
||||
|
||||
The crate forbids `unsafe` in library code. Benchmark code uses OpenSSL public
|
||||
APIs for comparison and is not part of the library.
|
||||
179
secp256r1/benches/field.rs
Normal file
179
secp256r1/benches/field.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
use std::hint::black_box;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use openssl::bn::{BigNum, BigNumContext};
|
||||
use p256::{FieldElement as P256FieldElement, elliptic_curve::ff::PrimeField};
|
||||
use secp256r1::field::FieldElement;
|
||||
|
||||
const A: [u8; 32] = [
|
||||
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
||||
0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
|
||||
];
|
||||
const B: [u8; 32] = [
|
||||
0x0f, 0xed, 0xcb, 0xa9, 0x87, 0x65, 0x43, 0x21, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
|
||||
0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10,
|
||||
];
|
||||
const P: [u8; 32] = [
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
];
|
||||
|
||||
struct Fixture {
|
||||
rust_a: FieldElement,
|
||||
rust_b: FieldElement,
|
||||
openssl_a: BigNum,
|
||||
openssl_b: BigNum,
|
||||
openssl_p: BigNum,
|
||||
p256_a: P256FieldElement,
|
||||
p256_b: P256FieldElement,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
rust_a: FieldElement::from_be_bytes(A).unwrap(),
|
||||
rust_b: FieldElement::from_be_bytes(B).unwrap(),
|
||||
openssl_a: BigNum::from_slice(&A).unwrap(),
|
||||
openssl_b: BigNum::from_slice(&B).unwrap(),
|
||||
openssl_p: BigNum::from_slice(&P).unwrap(),
|
||||
p256_a: p256_field(A),
|
||||
p256_b: p256_field(B),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn p256_field(bytes: [u8; 32]) -> P256FieldElement {
|
||||
Option::from(P256FieldElement::from_repr(bytes.into())).unwrap()
|
||||
}
|
||||
|
||||
fn bench_field_add(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let mut context = BigNumContext::new().unwrap();
|
||||
let mut openssl_out = BigNum::new().unwrap();
|
||||
let mut group = c.benchmark_group("secp256r1_field_add");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_a) + black_box(fixture.rust_b));
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_a) + black_box(fixture.p256_b));
|
||||
});
|
||||
|
||||
group.bench_function("openssl_bn_mod_add", |b| {
|
||||
b.iter(|| {
|
||||
openssl_out
|
||||
.mod_add(
|
||||
black_box(&fixture.openssl_a),
|
||||
black_box(&fixture.openssl_b),
|
||||
black_box(&fixture.openssl_p),
|
||||
&mut context,
|
||||
)
|
||||
.unwrap();
|
||||
black_box(&openssl_out);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_field_sub(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let mut context = BigNumContext::new().unwrap();
|
||||
let mut openssl_out = BigNum::new().unwrap();
|
||||
let mut group = c.benchmark_group("secp256r1_field_sub");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_a) - black_box(fixture.rust_b));
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_a) - black_box(fixture.p256_b));
|
||||
});
|
||||
|
||||
group.bench_function("openssl_bn_mod_sub", |b| {
|
||||
b.iter(|| {
|
||||
openssl_out
|
||||
.mod_sub(
|
||||
black_box(&fixture.openssl_a),
|
||||
black_box(&fixture.openssl_b),
|
||||
black_box(&fixture.openssl_p),
|
||||
&mut context,
|
||||
)
|
||||
.unwrap();
|
||||
black_box(&openssl_out);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_field_mul(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let mut context = BigNumContext::new().unwrap();
|
||||
let mut openssl_out = BigNum::new().unwrap();
|
||||
let mut group = c.benchmark_group("secp256r1_field_mul");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_a) * black_box(fixture.rust_b));
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_a) * black_box(fixture.p256_b));
|
||||
});
|
||||
|
||||
group.bench_function("openssl_bn_mod_mul", |b| {
|
||||
b.iter(|| {
|
||||
openssl_out
|
||||
.mod_mul(
|
||||
black_box(&fixture.openssl_a),
|
||||
black_box(&fixture.openssl_b),
|
||||
black_box(&fixture.openssl_p),
|
||||
&mut context,
|
||||
)
|
||||
.unwrap();
|
||||
black_box(&openssl_out);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_field_square(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let mut context = BigNumContext::new().unwrap();
|
||||
let mut openssl_out = BigNum::new().unwrap();
|
||||
let mut group = c.benchmark_group("secp256r1_field_square");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_a).square());
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_a).square());
|
||||
});
|
||||
|
||||
group.bench_function("openssl_bn_mod_sqr", |b| {
|
||||
b.iter(|| {
|
||||
openssl_out
|
||||
.mod_sqr(
|
||||
black_box(&fixture.openssl_a),
|
||||
black_box(&fixture.openssl_p),
|
||||
&mut context,
|
||||
)
|
||||
.unwrap();
|
||||
black_box(&openssl_out);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_field_add,
|
||||
bench_field_sub,
|
||||
bench_field_mul,
|
||||
bench_field_square
|
||||
);
|
||||
criterion_main!(benches);
|
||||
331
secp256r1/benches/group.rs
Normal file
331
secp256r1/benches/group.rs
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
use std::hint::black_box;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use openssl::{
|
||||
bn::{BigNum, BigNumContext},
|
||||
ec::{EcGroup, EcPoint},
|
||||
nid::Nid,
|
||||
};
|
||||
use p256::{
|
||||
AffinePoint as P256AffinePoint, ProjectivePoint as P256ProjectivePoint, Scalar,
|
||||
elliptic_curve::{ff::PrimeField, group::Group},
|
||||
};
|
||||
use secp256r1::group::{AffinePoint, ProjectivePoint};
|
||||
|
||||
const SCALAR: [u8; 32] = [
|
||||
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
||||
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
||||
];
|
||||
|
||||
const MSM_8_POINTS: usize = 8;
|
||||
const MSM_32_POINTS: usize = 32;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Fixture {
|
||||
rust_g: ProjectivePoint,
|
||||
rust_2g: ProjectivePoint,
|
||||
rust_affine_g: AffinePoint,
|
||||
p256_g: P256ProjectivePoint,
|
||||
p256_2g: P256ProjectivePoint,
|
||||
p256_affine_g: P256AffinePoint,
|
||||
p256_scalar: Scalar,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
let rust_g = ProjectivePoint::generator();
|
||||
let p256_g = P256ProjectivePoint::generator();
|
||||
|
||||
Self {
|
||||
rust_g,
|
||||
rust_2g: rust_g.double(),
|
||||
rust_affine_g: AffinePoint::generator(),
|
||||
p256_g,
|
||||
p256_2g: p256_g.double(),
|
||||
p256_affine_g: P256AffinePoint::GENERATOR,
|
||||
p256_scalar: Option::<Scalar>::from(Scalar::from_repr(SCALAR.into())).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scalar_for_index(index: usize) -> [u8; 32] {
|
||||
let mut scalar = SCALAR;
|
||||
scalar[30] ^= (index as u8).wrapping_mul(17);
|
||||
scalar[31] = scalar[31].wrapping_add(index as u8);
|
||||
scalar
|
||||
}
|
||||
|
||||
fn rust_msm_fixture(count: usize) -> (Vec<AffinePoint>, Vec<[u8; 32]>) {
|
||||
let generator = ProjectivePoint::generator();
|
||||
let mut point = generator;
|
||||
let mut points = Vec::with_capacity(count);
|
||||
let mut scalars = Vec::with_capacity(count);
|
||||
|
||||
for i in 0..count {
|
||||
points.push(point.to_affine());
|
||||
scalars.push(scalar_for_index(i));
|
||||
point = point + generator;
|
||||
}
|
||||
|
||||
(points, scalars)
|
||||
}
|
||||
|
||||
fn p256_msm_fixture(count: usize) -> (Vec<P256ProjectivePoint>, Vec<Scalar>) {
|
||||
let generator = P256ProjectivePoint::generator();
|
||||
let mut point = generator;
|
||||
let mut points = Vec::with_capacity(count);
|
||||
let mut scalars = Vec::with_capacity(count);
|
||||
|
||||
for i in 0..count {
|
||||
points.push(point);
|
||||
scalars
|
||||
.push(Option::<Scalar>::from(Scalar::from_repr(scalar_for_index(i).into())).unwrap());
|
||||
point += generator;
|
||||
}
|
||||
|
||||
(points, scalars)
|
||||
}
|
||||
|
||||
fn openssl_fixture() -> (EcGroup, BigNumContext, EcPoint, EcPoint) {
|
||||
let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap();
|
||||
let mut context = BigNumContext::new().unwrap();
|
||||
let generator = group.generator_opt().unwrap().to_owned(&group).unwrap();
|
||||
let mut double_generator = EcPoint::new(&group).unwrap();
|
||||
double_generator
|
||||
.add(&group, &generator, &generator, &mut context)
|
||||
.unwrap();
|
||||
|
||||
(group, context, generator, double_generator)
|
||||
}
|
||||
|
||||
fn bench_group_double(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let (openssl_group, mut openssl_context, openssl_g, _) = openssl_fixture();
|
||||
let mut openssl_out = EcPoint::new(&openssl_group).unwrap();
|
||||
let mut group = c.benchmark_group("secp256r1_group_double");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_g).double());
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_g).double());
|
||||
});
|
||||
|
||||
group.bench_function("openssl_ec_point_add_self", |b| {
|
||||
b.iter(|| {
|
||||
openssl_out
|
||||
.add(
|
||||
&openssl_group,
|
||||
black_box(&openssl_g),
|
||||
black_box(&openssl_g),
|
||||
&mut openssl_context,
|
||||
)
|
||||
.unwrap();
|
||||
black_box(&openssl_out);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_group_add(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let (openssl_group, mut openssl_context, openssl_g, openssl_2g) = openssl_fixture();
|
||||
let mut openssl_out = EcPoint::new(&openssl_group).unwrap();
|
||||
let mut group = c.benchmark_group("secp256r1_group_add");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_2g) + black_box(fixture.rust_g));
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_2g) + black_box(fixture.p256_g));
|
||||
});
|
||||
|
||||
group.bench_function("openssl_ec_point_add", |b| {
|
||||
b.iter(|| {
|
||||
openssl_out
|
||||
.add(
|
||||
&openssl_group,
|
||||
black_box(&openssl_2g),
|
||||
black_box(&openssl_g),
|
||||
&mut openssl_context,
|
||||
)
|
||||
.unwrap();
|
||||
black_box(&openssl_out);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_group_mixed_add(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let mut group = c.benchmark_group("secp256r1_group_mixed_add");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_2g).add_mixed(black_box(fixture.rust_affine_g)));
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_2g) + black_box(fixture.p256_affine_g));
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_group_base_scalar_mul(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let (openssl_group, mut openssl_context, _, _) = openssl_fixture();
|
||||
let openssl_scalar = BigNum::from_slice(&SCALAR).unwrap();
|
||||
let mut openssl_out = EcPoint::new(&openssl_group).unwrap();
|
||||
let mut group = c.benchmark_group("secp256r1_group_base_scalar_mul");
|
||||
|
||||
group.bench_function("rust_variable_base", |b| {
|
||||
b.iter(|| black_box(fixture.rust_g).mul_scalar_vartime(black_box(SCALAR)));
|
||||
});
|
||||
|
||||
group.bench_function("rust_fixed_base", |b| {
|
||||
b.iter(|| ProjectivePoint::fixed_base_scalar_mul_vartime(black_box(SCALAR)));
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_g) * black_box(fixture.p256_scalar));
|
||||
});
|
||||
|
||||
group.bench_function("openssl_ec_point_mul_generator", |b| {
|
||||
b.iter(|| {
|
||||
openssl_out
|
||||
.mul_generator2(
|
||||
&openssl_group,
|
||||
black_box(&openssl_scalar),
|
||||
&mut openssl_context,
|
||||
)
|
||||
.unwrap();
|
||||
black_box(&openssl_out);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_group_double_scalar_mul(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let rust_q = fixture.rust_2g.to_affine();
|
||||
let rust_msm_points = [AffinePoint::generator(), rust_q];
|
||||
let rust_msm_scalars = [SCALAR, SCALAR];
|
||||
let (openssl_group, mut openssl_context, _, openssl_q) = openssl_fixture();
|
||||
let openssl_scalar = BigNum::from_slice(&SCALAR).unwrap();
|
||||
let mut openssl_out = EcPoint::new(&openssl_group).unwrap();
|
||||
let mut group = c.benchmark_group("secp256r1_group_double_scalar_mul");
|
||||
|
||||
group.bench_function("rust_separate_projective_q", |b| {
|
||||
b.iter(|| {
|
||||
ProjectivePoint::fixed_base_scalar_mul_vartime(black_box(SCALAR))
|
||||
+ ProjectivePoint::from_affine(black_box(rust_q))
|
||||
.mul_scalar_vartime(black_box(SCALAR))
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("rust_msm_window4", |b| {
|
||||
b.iter(|| {
|
||||
ProjectivePoint::multi_scalar_mul_vartime(
|
||||
black_box(&rust_msm_points),
|
||||
black_box(&rust_msm_scalars),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("rust_double_scalar", |b| {
|
||||
b.iter(|| {
|
||||
ProjectivePoint::double_scalar_mul_vartime(
|
||||
black_box(SCALAR),
|
||||
black_box(rust_q),
|
||||
black_box(SCALAR),
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| {
|
||||
(black_box(fixture.p256_g) * black_box(fixture.p256_scalar))
|
||||
+ (black_box(fixture.p256_2g) * black_box(fixture.p256_scalar))
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("openssl_ec_point_mul_full", |b| {
|
||||
b.iter(|| {
|
||||
openssl_out
|
||||
.mul_full(
|
||||
&openssl_group,
|
||||
black_box(&openssl_scalar),
|
||||
black_box(&openssl_q),
|
||||
black_box(&openssl_scalar),
|
||||
&mut openssl_context,
|
||||
)
|
||||
.unwrap();
|
||||
black_box(&openssl_out);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_group_multi_scalar_mul(c: &mut Criterion) {
|
||||
for count in [MSM_8_POINTS, MSM_32_POINTS] {
|
||||
let (rust_points, rust_scalars) = rust_msm_fixture(count);
|
||||
let (p256_points, p256_scalars) = p256_msm_fixture(count);
|
||||
let mut group = c.benchmark_group(format!("secp256r1_group_multi_scalar_mul_{count}"));
|
||||
|
||||
group.bench_function("rust_msm_window4", |b| {
|
||||
b.iter(|| {
|
||||
ProjectivePoint::multi_scalar_mul_vartime(
|
||||
black_box(rust_points.as_slice()),
|
||||
black_box(rust_scalars.as_slice()),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("rust_separate", |b| {
|
||||
b.iter(|| {
|
||||
let mut out = ProjectivePoint::identity();
|
||||
|
||||
for (point, scalar) in rust_points.iter().zip(rust_scalars.iter()) {
|
||||
out = out
|
||||
+ ProjectivePoint::from_affine(black_box(*point))
|
||||
.mul_scalar_vartime(black_box(*scalar));
|
||||
}
|
||||
|
||||
out
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("p256_separate", |b| {
|
||||
b.iter(|| {
|
||||
let mut out = P256ProjectivePoint::IDENTITY;
|
||||
|
||||
for (point, scalar) in p256_points.iter().zip(p256_scalars.iter()) {
|
||||
out += black_box(*point) * black_box(*scalar);
|
||||
}
|
||||
|
||||
out
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_group_double,
|
||||
bench_group_add,
|
||||
bench_group_mixed_add,
|
||||
bench_group_base_scalar_mul,
|
||||
bench_group_double_scalar_mul,
|
||||
bench_group_multi_scalar_mul
|
||||
);
|
||||
criterion_main!(benches);
|
||||
115
secp256r1/benches/scalar.rs
Normal file
115
secp256r1/benches/scalar.rs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
use std::hint::black_box;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use openssl::bn::{BigNum, BigNumContext};
|
||||
use p256::{Scalar as P256Scalar, elliptic_curve::ff::PrimeField};
|
||||
use secp256r1::scalar::Scalar;
|
||||
|
||||
const A: [u8; 32] = [
|
||||
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
||||
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
|
||||
];
|
||||
|
||||
const B: [u8; 32] = [
|
||||
0x0f, 0xed, 0xcb, 0xa9, 0x87, 0x65, 0x43, 0x21, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88,
|
||||
0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10,
|
||||
];
|
||||
|
||||
const ORDER: [u8; 32] = [
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51,
|
||||
];
|
||||
|
||||
struct Fixture {
|
||||
rust_a: Scalar,
|
||||
rust_b: Scalar,
|
||||
p256_a: P256Scalar,
|
||||
p256_b: P256Scalar,
|
||||
openssl_a: BigNum,
|
||||
openssl_order: BigNum,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
rust_a: Scalar::from_be_bytes(A).unwrap(),
|
||||
rust_b: Scalar::from_be_bytes(B).unwrap(),
|
||||
p256_a: p256_scalar(A),
|
||||
p256_b: p256_scalar(B),
|
||||
openssl_a: BigNum::from_slice(&A).unwrap(),
|
||||
openssl_order: BigNum::from_slice(&ORDER).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn p256_scalar(bytes: [u8; 32]) -> P256Scalar {
|
||||
Option::from(P256Scalar::from_repr(bytes.into())).unwrap()
|
||||
}
|
||||
|
||||
fn bench_scalar_mul(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let mut group = c.benchmark_group("secp256r1_scalar_mul");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_a) * black_box(fixture.rust_b));
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_a) * black_box(fixture.p256_b));
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_scalar_square(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let mut group = c.benchmark_group("secp256r1_scalar_square");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_a).square());
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_a).square());
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_scalar_invert(c: &mut Criterion) {
|
||||
let fixture = Fixture::new();
|
||||
let mut context = BigNumContext::new().unwrap();
|
||||
let mut openssl_out = BigNum::new().unwrap();
|
||||
let mut group = c.benchmark_group("secp256r1_scalar_invert");
|
||||
|
||||
group.bench_function("rust", |b| {
|
||||
b.iter(|| black_box(fixture.rust_a).invert().unwrap());
|
||||
});
|
||||
|
||||
group.bench_function("p256", |b| {
|
||||
b.iter(|| black_box(fixture.p256_a).invert().unwrap());
|
||||
});
|
||||
|
||||
group.bench_function("openssl_bn_mod_inverse", |b| {
|
||||
b.iter(|| {
|
||||
openssl_out
|
||||
.mod_inverse(
|
||||
black_box(&fixture.openssl_a),
|
||||
black_box(&fixture.openssl_order),
|
||||
&mut context,
|
||||
)
|
||||
.unwrap();
|
||||
black_box(&openssl_out);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_scalar_mul,
|
||||
bench_scalar_square,
|
||||
bench_scalar_invert
|
||||
);
|
||||
criterion_main!(benches);
|
||||
665
secp256r1/src/field.rs
Normal file
665
secp256r1/src/field.rs
Normal file
|
|
@ -0,0 +1,665 @@
|
|||
//! P-256 base field arithmetic.
|
||||
//!
|
||||
//! [`FieldElement`] represents an element of GF(p) where
|
||||
//! `p = 2^256 - 2^224 + 2^192 + 2^96 - 1` (the P-256 field prime).
|
||||
//! Internally elements are stored in Montgomery form; use
|
||||
//! [`from_be_bytes`][FieldElement::from_be_bytes] and
|
||||
//! [`to_be_bytes`][FieldElement::to_be_bytes] to convert from/to canonical
|
||||
//! big-endian representation.
|
||||
|
||||
use core::ops::{Add, Mul, Neg, Sub};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct FieldElement {
|
||||
limbs: [u64; 4],
|
||||
}
|
||||
|
||||
const MODULUS: [u64; 4] = [
|
||||
0xffff_ffff_ffff_ffff,
|
||||
0x0000_0000_ffff_ffff,
|
||||
0x0000_0000_0000_0000,
|
||||
0xffff_ffff_0000_0001,
|
||||
];
|
||||
|
||||
const R2: [u64; 4] = [
|
||||
0x0000_0000_0000_0003,
|
||||
0xffff_fffb_ffff_ffff,
|
||||
0xffff_ffff_ffff_fffe,
|
||||
0x0000_0004_ffff_fffd,
|
||||
];
|
||||
|
||||
const P_PLUS_ONE_DIV_4: [u8; 32] = [
|
||||
0x3f, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
impl FieldElement {
|
||||
pub const ZERO: Self = Self { limbs: [0; 4] };
|
||||
pub const ONE: Self = Self {
|
||||
limbs: [
|
||||
0x0000_0000_0000_0001,
|
||||
0xffff_ffff_0000_0000,
|
||||
0xffff_ffff_ffff_ffff,
|
||||
0x0000_0000_ffff_fffe,
|
||||
],
|
||||
};
|
||||
|
||||
#[inline]
|
||||
pub fn from_u64(value: u64) -> Self {
|
||||
Self::from_canonical_limbs([value, 0, 0, 0]).expect("u64 is canonical")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_be_bytes(bytes: [u8; 32]) -> Option<Self> {
|
||||
Self::from_canonical_limbs(limbs_from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_be_bytes(self) -> [u8; 32] {
|
||||
be_bytes_from_limbs(from_montgomery(self.limbs))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_zero(self) -> bool {
|
||||
self == Self::ZERO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn square(self) -> Self {
|
||||
Self {
|
||||
limbs: montgomery_square(self.limbs),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn invert(self) -> Option<Self> {
|
||||
if self.is_zero() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
limbs: montgomery_mul(invert_canonical(from_montgomery(self.limbs)), R2),
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn sqrt(self) -> Option<Self> {
|
||||
let candidate = self.pow(P_PLUS_ONE_DIV_4);
|
||||
(candidate.square() == self).then_some(candidate)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pow(self, exponent: [u8; 32]) -> Self {
|
||||
let mut out = Self::ONE;
|
||||
|
||||
for byte in exponent {
|
||||
for bit in (0..8).rev() {
|
||||
out = out.square();
|
||||
if ((byte >> bit) & 1) == 1 {
|
||||
out = out * self;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn montgomery_limbs(self) -> [u64; 4] {
|
||||
self.limbs
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) const fn from_montgomery_limbs(limbs: [u64; 4]) -> Self {
|
||||
Self { limbs }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_canonical_limbs(limbs: [u64; 4]) -> Option<Self> {
|
||||
if ge_limbs(limbs, MODULUS) {
|
||||
None
|
||||
} else {
|
||||
Some(Self {
|
||||
limbs: montgomery_mul(limbs, R2),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for FieldElement {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let (sum, carry) = add_limbs(self.limbs, rhs.limbs);
|
||||
Self {
|
||||
limbs: reduce_sum(sum, carry),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for FieldElement {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
let (difference, borrow) = sub_limbs(self.limbs, rhs.limbs);
|
||||
let (corrected, _) = add_limbs(difference, MODULUS);
|
||||
|
||||
Self {
|
||||
limbs: if borrow == 0 { difference } else { corrected },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for FieldElement {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
limbs: montgomery_mul(self.limbs, rhs.limbs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for FieldElement {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
if self.is_zero() {
|
||||
self
|
||||
} else {
|
||||
Self::ZERO - self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_limbs(a: [u64; 4], b: [u64; 4]) -> ([u64; 4], u64) {
|
||||
let mut out = [0; 4];
|
||||
let mut carry = 0u64;
|
||||
|
||||
for i in 0..4 {
|
||||
let (sum, carry1) = a[i].overflowing_add(b[i]);
|
||||
let (sum, carry2) = sum.overflowing_add(carry);
|
||||
out[i] = sum;
|
||||
carry = u64::from(carry1 | carry2);
|
||||
}
|
||||
|
||||
(out, carry)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sub_limbs(a: [u64; 4], b: [u64; 4]) -> ([u64; 4], u64) {
|
||||
let mut out = [0; 4];
|
||||
let mut borrow = 0u64;
|
||||
|
||||
for i in 0..4 {
|
||||
let (difference, borrow1) = a[i].overflowing_sub(b[i]);
|
||||
let (difference, borrow2) = difference.overflowing_sub(borrow);
|
||||
out[i] = difference;
|
||||
borrow = u64::from(borrow1 | borrow2);
|
||||
}
|
||||
|
||||
(out, borrow)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn reduce_sum(sum: [u64; 4], carry: u64) -> [u64; 4] {
|
||||
let (reduced, borrow) = sub_limbs(sum, MODULUS);
|
||||
|
||||
if carry != 0 || borrow == 0 {
|
||||
reduced
|
||||
} else {
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ge_limbs(a: [u64; 4], b: [u64; 4]) -> bool {
|
||||
sub_limbs(a, b).1 == 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_one_limbs(a: [u64; 4]) -> bool {
|
||||
a == [1, 0, 0, 0]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_even_limbs(a: [u64; 4]) -> bool {
|
||||
(a[0] & 1) == 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn shr1_limbs(a: [u64; 4]) -> [u64; 4] {
|
||||
shr1_limbs_with_carry(a, 0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn shr1_limbs_with_carry(a: [u64; 4], carry: u64) -> [u64; 4] {
|
||||
[
|
||||
(a[0] >> 1) | (a[1] << 63),
|
||||
(a[1] >> 1) | (a[2] << 63),
|
||||
(a[2] >> 1) | (a[3] << 63),
|
||||
(a[3] >> 1) | (carry << 63),
|
||||
]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn half_mod(a: [u64; 4]) -> [u64; 4] {
|
||||
if is_even_limbs(a) {
|
||||
shr1_limbs(a)
|
||||
} else {
|
||||
let (sum, carry) = add_limbs(a, MODULUS);
|
||||
shr1_limbs_with_carry(sum, carry)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sub_mod(a: [u64; 4], b: [u64; 4]) -> [u64; 4] {
|
||||
let (difference, borrow) = sub_limbs(a, b);
|
||||
|
||||
if borrow == 0 {
|
||||
difference
|
||||
} else {
|
||||
add_limbs(difference, MODULUS).0
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_canonical(value: [u64; 4]) -> [u64; 4] {
|
||||
let mut u = value;
|
||||
let mut v = MODULUS;
|
||||
let mut x1 = [1, 0, 0, 0];
|
||||
let mut x2 = [0; 4];
|
||||
|
||||
while !is_one_limbs(u) && !is_one_limbs(v) {
|
||||
while is_even_limbs(u) {
|
||||
u = shr1_limbs(u);
|
||||
x1 = half_mod(x1);
|
||||
}
|
||||
|
||||
while is_even_limbs(v) {
|
||||
v = shr1_limbs(v);
|
||||
x2 = half_mod(x2);
|
||||
}
|
||||
|
||||
if ge_limbs(u, v) {
|
||||
u = sub_limbs(u, v).0;
|
||||
x1 = sub_mod(x1, x2);
|
||||
} else {
|
||||
v = sub_limbs(v, u).0;
|
||||
x2 = sub_mod(x2, x1);
|
||||
}
|
||||
}
|
||||
|
||||
if is_one_limbs(u) { x1 } else { x2 }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mul_wide(a: [u64; 4], b: [u64; 4]) -> [u64; 8] {
|
||||
let (w0, carry) = mac(0, a[0], b[0], 0);
|
||||
let (w1, carry) = mac(0, a[0], b[1], carry);
|
||||
let (w2, carry) = mac(0, a[0], b[2], carry);
|
||||
let (w3, w4) = mac(0, a[0], b[3], carry);
|
||||
|
||||
let (w1, carry) = mac(w1, a[1], b[0], 0);
|
||||
let (w2, carry) = mac(w2, a[1], b[1], carry);
|
||||
let (w3, carry) = mac(w3, a[1], b[2], carry);
|
||||
let (w4, w5) = mac(w4, a[1], b[3], carry);
|
||||
|
||||
let (w2, carry) = mac(w2, a[2], b[0], 0);
|
||||
let (w3, carry) = mac(w3, a[2], b[1], carry);
|
||||
let (w4, carry) = mac(w4, a[2], b[2], carry);
|
||||
let (w5, w6) = mac(w5, a[2], b[3], carry);
|
||||
|
||||
let (w3, carry) = mac(w3, a[3], b[0], 0);
|
||||
let (w4, carry) = mac(w4, a[3], b[1], carry);
|
||||
let (w5, carry) = mac(w5, a[3], b[2], carry);
|
||||
let (w6, w7) = mac(w6, a[3], b[3], carry);
|
||||
|
||||
[w0, w1, w2, w3, w4, w5, w6, w7]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn montgomery_mul(a: [u64; 4], b: [u64; 4]) -> [u64; 4] {
|
||||
montgomery_reduce(mul_wide(a, b))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn montgomery_square(a: [u64; 4]) -> [u64; 4] {
|
||||
let a0 = a[0] as u128;
|
||||
let a1 = a[1] as u128;
|
||||
let a2 = a[2] as u128;
|
||||
let a3 = a[3] as u128;
|
||||
|
||||
let p00 = a0 * a0;
|
||||
let w0 = p00 as u64;
|
||||
let mut acc = p00 >> 64;
|
||||
let mut top = 0u64;
|
||||
|
||||
let p01 = a0 * a1;
|
||||
top += (p01 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p01 << 1);
|
||||
let w1 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
top = 0;
|
||||
let p02 = a0 * a2;
|
||||
top += (p02 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p02 << 1);
|
||||
acc = sum;
|
||||
top += u64::from(overflow);
|
||||
let (sum, overflow) = acc.overflowing_add(a1 * a1);
|
||||
let w2 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
top = 0;
|
||||
let p03 = a0 * a3;
|
||||
top += (p03 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p03 << 1);
|
||||
acc = sum;
|
||||
top += u64::from(overflow);
|
||||
let p12 = a1 * a2;
|
||||
top += (p12 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p12 << 1);
|
||||
let w3 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
top = 0;
|
||||
let p13 = a1 * a3;
|
||||
top += (p13 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p13 << 1);
|
||||
acc = sum;
|
||||
top += u64::from(overflow);
|
||||
let (sum, overflow) = acc.overflowing_add(a2 * a2);
|
||||
let w4 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
top = 0;
|
||||
let p23 = a2 * a3;
|
||||
top += (p23 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p23 << 1);
|
||||
let w5 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
let (sum, overflow) = acc.overflowing_add(a3 * a3);
|
||||
let w6 = sum as u64;
|
||||
let w7 = (sum >> 64) as u64;
|
||||
debug_assert!(!overflow);
|
||||
|
||||
montgomery_reduce_words(w0, w1, w2, w3, w4, w5, w6, w7)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn from_montgomery(a: [u64; 4]) -> [u64; 4] {
|
||||
montgomery_reduce([a[0], a[1], a[2], a[3], 0, 0, 0, 0])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn montgomery_reduce(input: [u64; 8]) -> [u64; 4] {
|
||||
montgomery_reduce_words(
|
||||
input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7],
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn montgomery_reduce_words(
|
||||
r0: u64,
|
||||
r1: u64,
|
||||
r2: u64,
|
||||
r3: u64,
|
||||
r4: u64,
|
||||
r5: u64,
|
||||
r6: u64,
|
||||
r7: u64,
|
||||
) -> [u64; 4] {
|
||||
let (r1, carry) = mac(r1, r0, MODULUS[1], r0);
|
||||
let (r2, carry) = adc(r2, 0, carry);
|
||||
let (r3, carry) = mac(r3, r0, MODULUS[3], carry);
|
||||
let (r4, carry2) = adc(r4, 0, carry);
|
||||
|
||||
let (r2, carry) = mac(r2, r1, MODULUS[1], r1);
|
||||
let (r3, carry) = adc(r3, 0, carry);
|
||||
let (r4, carry) = mac(r4, r1, MODULUS[3], carry);
|
||||
let (r5, carry2) = adc(r5, carry2, carry);
|
||||
|
||||
let (r3, carry) = mac(r3, r2, MODULUS[1], r2);
|
||||
let (r4, carry) = adc(r4, 0, carry);
|
||||
let (r5, carry) = mac(r5, r2, MODULUS[3], carry);
|
||||
let (r6, carry2) = adc(r6, carry2, carry);
|
||||
|
||||
let (r4, carry) = mac(r4, r3, MODULUS[1], r3);
|
||||
let (r5, carry) = adc(r5, 0, carry);
|
||||
let (r6, carry) = mac(r6, r3, MODULUS[3], carry);
|
||||
let (r7, r8) = adc(r7, carry2, carry);
|
||||
|
||||
reduce_wide([r4, r5, r6, r7, r8])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn reduce_wide(value: [u64; 5]) -> [u64; 4] {
|
||||
let (w0, borrow) = sbb(value[0], MODULUS[0], 0);
|
||||
let (w1, borrow) = sbb(value[1], MODULUS[1], borrow);
|
||||
let (w2, borrow) = sbb(value[2], MODULUS[2], borrow);
|
||||
let (w3, borrow) = sbb(value[3], MODULUS[3], borrow);
|
||||
let (_, borrow) = sbb(value[4], 0, borrow);
|
||||
|
||||
if borrow == 0 {
|
||||
[w0, w1, w2, w3]
|
||||
} else {
|
||||
[value[0], value[1], value[2], value[3]]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) {
|
||||
let sum = (a as u128) + (b as u128) + (carry as u128);
|
||||
(sum as u64, (sum >> 64) as u64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sbb(a: u64, b: u64, borrow: u64) -> (u64, u64) {
|
||||
let difference = (a as u128).wrapping_sub((b as u128) + (borrow as u128));
|
||||
(difference as u64, u64::from((difference >> 127) != 0))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) {
|
||||
let product = (a as u128) + (b as u128) * (c as u128) + (carry as u128);
|
||||
(product as u64, (product >> 64) as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn limbs_from_be_bytes(bytes: [u8; 32]) -> [u64; 4] {
|
||||
let mut limbs = [0u64; 4];
|
||||
|
||||
for (i, chunk) in bytes.chunks_exact(8).rev().enumerate() {
|
||||
limbs[i] = u64::from_be_bytes(chunk.try_into().expect("chunk length is 8"));
|
||||
}
|
||||
|
||||
limbs
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn be_bytes_from_limbs(limbs: [u64; 4]) -> [u8; 32] {
|
||||
let mut bytes = [0u8; 32];
|
||||
|
||||
for (i, limb) in limbs.iter().rev().enumerate() {
|
||||
bytes[i * 8..(i + 1) * 8].copy_from_slice(&limb.to_be_bytes());
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FieldElement;
|
||||
use p256::{FieldElement as P256FieldElement, elliptic_curve::ff::PrimeField};
|
||||
|
||||
const A: [u8; 32] = [
|
||||
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60,
|
||||
0x70, 0x80,
|
||||
];
|
||||
const B: [u8; 32] = [
|
||||
0x0f, 0xed, 0xcb, 0xa9, 0x87, 0x65, 0x43, 0x21, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99,
|
||||
0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0x80, 0x70, 0x60, 0x50, 0x40, 0x30,
|
||||
0x20, 0x10,
|
||||
];
|
||||
const P_MINUS_ONE: [u8; 32] = [
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xfe,
|
||||
];
|
||||
const P: [u8; 32] = [
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff,
|
||||
];
|
||||
|
||||
fn p256_field(bytes: [u8; 32]) -> P256FieldElement {
|
||||
Option::from(P256FieldElement::from_repr(bytes.into())).unwrap()
|
||||
}
|
||||
|
||||
fn assert_matches_p256(rust: FieldElement, p256: P256FieldElement, operation: &'static str) {
|
||||
let p256_bytes: [u8; 32] = p256.to_repr().into();
|
||||
assert_eq!(rust.to_be_bytes(), p256_bytes, "{operation}");
|
||||
}
|
||||
|
||||
fn sample(mut seed: u64) -> [u8; 32] {
|
||||
let mut bytes = [0u8; 32];
|
||||
|
||||
for chunk in bytes.chunks_exact_mut(8) {
|
||||
seed ^= seed << 13;
|
||||
seed ^= seed >> 7;
|
||||
seed ^= seed << 17;
|
||||
chunk.copy_from_slice(&seed.to_be_bytes());
|
||||
}
|
||||
|
||||
// Keep samples well below p so generation cannot accidentally produce
|
||||
// a non-canonical field encoding.
|
||||
bytes[0] &= 0x7f;
|
||||
bytes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_non_canonical_values() {
|
||||
assert!(FieldElement::from_be_bytes(P).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trips_canonical_values() {
|
||||
for bytes in [[0u8; 32], A, B, P_MINUS_ONE] {
|
||||
let element = FieldElement::from_be_bytes(bytes).unwrap();
|
||||
assert_eq!(element.to_be_bytes(), bytes);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_matches_p256() {
|
||||
assert_matches_p256(
|
||||
FieldElement::from_be_bytes(A).unwrap() + FieldElement::from_be_bytes(B).unwrap(),
|
||||
p256_field(A) + p256_field(B),
|
||||
"add",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_matches_p256() {
|
||||
assert_matches_p256(
|
||||
FieldElement::from_be_bytes(A).unwrap() - FieldElement::from_be_bytes(B).unwrap(),
|
||||
p256_field(A) - p256_field(B),
|
||||
"sub",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_matches_p256() {
|
||||
assert_matches_p256(
|
||||
FieldElement::from_be_bytes(A).unwrap() * FieldElement::from_be_bytes(B).unwrap(),
|
||||
p256_field(A) * p256_field(B),
|
||||
"mul",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn square_matches_p256() {
|
||||
assert_matches_p256(
|
||||
FieldElement::from_be_bytes(A).unwrap().square(),
|
||||
p256_field(A).square(),
|
||||
"square",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invert_matches_p256() {
|
||||
assert!(FieldElement::ZERO.invert().is_none());
|
||||
|
||||
for bytes in [A, B, P_MINUS_ONE] {
|
||||
let rust = FieldElement::from_be_bytes(bytes).unwrap();
|
||||
let p256 = p256_field(bytes);
|
||||
|
||||
assert_matches_p256(
|
||||
rust.invert().unwrap(),
|
||||
Option::from(p256.invert()).unwrap(),
|
||||
"invert",
|
||||
);
|
||||
assert_eq!(
|
||||
(rust * rust.invert().unwrap()).to_be_bytes(),
|
||||
FieldElement::ONE.to_be_bytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edge_values_match_p256() {
|
||||
let rust = FieldElement::from_be_bytes(P_MINUS_ONE).unwrap();
|
||||
let p256 = p256_field(P_MINUS_ONE);
|
||||
let mut one_bytes = [0u8; 32];
|
||||
one_bytes[31] = 1;
|
||||
let rust_one = FieldElement::ONE;
|
||||
let p256_one = p256_field(one_bytes);
|
||||
|
||||
assert_matches_p256(rust + rust, p256 + p256, "p_minus_one add");
|
||||
assert_matches_p256(rust - rust_one, p256 - p256_one, "p_minus_one sub");
|
||||
assert_matches_p256(rust * rust, p256 * p256, "p_minus_one mul");
|
||||
assert_matches_p256(rust.square(), p256.square(), "p_minus_one square");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arithmetic_matches_p256_for_many_samples() {
|
||||
for i in 0..256 {
|
||||
let a = sample(i);
|
||||
let b = sample(i ^ 0xa5a5_a5a5_a5a5_a5a5);
|
||||
let rust_a = FieldElement::from_be_bytes(a).unwrap();
|
||||
let rust_b = FieldElement::from_be_bytes(b).unwrap();
|
||||
let p256_a = p256_field(a);
|
||||
let p256_b = p256_field(b);
|
||||
|
||||
assert_matches_p256(rust_a + rust_b, p256_a + p256_b, "add");
|
||||
assert_matches_p256(rust_a - rust_b, p256_a - p256_b, "sub");
|
||||
assert_matches_p256(rust_a * rust_b, p256_a * p256_b, "mul");
|
||||
assert_matches_p256(rust_a.square(), p256_a.square(), "square");
|
||||
if !rust_a.is_zero() {
|
||||
assert_matches_p256(
|
||||
rust_a.invert().unwrap(),
|
||||
Option::from(p256_a.invert()).unwrap(),
|
||||
"invert",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
746
secp256r1/src/group.rs
Normal file
746
secp256r1/src/group.rs
Normal file
|
|
@ -0,0 +1,746 @@
|
|||
//! P-256 elliptic curve group operations.
|
||||
//!
|
||||
//! Points are represented in two forms:
|
||||
//!
|
||||
//! - [`AffinePoint`] — standard `(x, y)` coordinates, used for storage and
|
||||
//! table entries. Includes an `infinity` flag for the identity element.
|
||||
//! - [`ProjectivePoint`] — Jacobian `(X : Y : Z)` coordinates, used during
|
||||
//! multi-step scalar multiplication to avoid per-step field inversions.
|
||||
//!
|
||||
//! Use [`ProjectivePoint::to_affine`] to convert back and pay the single
|
||||
//! field inversion, or [`batch_normalize`][`ProjectivePoint`] implicitly via
|
||||
//! the precomputed table builders.
|
||||
|
||||
use core::ops::{Add, Neg, Sub};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::field::FieldElement;
|
||||
|
||||
const BASE_WINDOWS: usize = 32;
|
||||
const BASE_WINDOW_POINTS: usize = 256;
|
||||
const SHAMIR_WINDOW_POINTS: usize = 16;
|
||||
|
||||
const CURVE_B: FieldElement = FieldElement::from_montgomery_limbs([
|
||||
0xd89c_df62_29c4_bddf,
|
||||
0xacf0_05cd_7884_3090,
|
||||
0xe5a2_20ab_f721_2ed6,
|
||||
0xdc30_061d_0487_4834,
|
||||
]);
|
||||
|
||||
const GENERATOR_X: FieldElement = FieldElement::from_montgomery_limbs([
|
||||
0x79e7_30d4_18a9_143c,
|
||||
0x75ba_95fc_5fed_b601,
|
||||
0x79fb_732b_7762_2510,
|
||||
0x1890_5f76_a537_55c6,
|
||||
]);
|
||||
|
||||
const GENERATOR_Y: FieldElement = FieldElement::from_montgomery_limbs([
|
||||
0xddf2_5357_ce95_560a,
|
||||
0x8b4a_b8e4_ba19_e45c,
|
||||
0xd2e8_8688_dd21_f325,
|
||||
0x8571_ff18_2588_5d85,
|
||||
]);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct AffinePoint {
|
||||
x: FieldElement,
|
||||
y: FieldElement,
|
||||
infinity: bool,
|
||||
}
|
||||
|
||||
impl AffinePoint {
|
||||
pub const IDENTITY: Self = Self {
|
||||
x: FieldElement::ZERO,
|
||||
y: FieldElement::ZERO,
|
||||
infinity: true,
|
||||
};
|
||||
|
||||
pub const GENERATOR: Self = Self {
|
||||
x: GENERATOR_X,
|
||||
y: GENERATOR_Y,
|
||||
infinity: false,
|
||||
};
|
||||
|
||||
#[inline]
|
||||
pub fn identity() -> Self {
|
||||
Self::IDENTITY
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn generator() -> Self {
|
||||
Self::GENERATOR
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new(x: FieldElement, y: FieldElement) -> Option<Self> {
|
||||
let point = Self {
|
||||
x,
|
||||
y,
|
||||
infinity: false,
|
||||
};
|
||||
|
||||
point.is_on_curve().then_some(point)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_uncompressed(bytes: [u8; 65]) -> Option<Self> {
|
||||
if bytes[0] != 0x04 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut x = [0u8; 32];
|
||||
let mut y = [0u8; 32];
|
||||
x.copy_from_slice(&bytes[1..33]);
|
||||
y.copy_from_slice(&bytes[33..65]);
|
||||
|
||||
Self::new(
|
||||
FieldElement::from_be_bytes(x)?,
|
||||
FieldElement::from_be_bytes(y)?,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_compressed(bytes: [u8; 33]) -> Option<Self> {
|
||||
if bytes[0] != 0x02 && bytes[0] != 0x03 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut x_bytes = [0u8; 32];
|
||||
x_bytes.copy_from_slice(&bytes[1..33]);
|
||||
let x = FieldElement::from_be_bytes(x_bytes)?;
|
||||
let rhs = x.square() * x - triple(x) + CURVE_B;
|
||||
let mut y = rhs.sqrt()?;
|
||||
|
||||
if (y.to_be_bytes()[31] & 1) != (bytes[0] & 1) {
|
||||
y = -y;
|
||||
}
|
||||
|
||||
((y.to_be_bytes()[31] & 1) == (bytes[0] & 1)).then_some(Self {
|
||||
x,
|
||||
y,
|
||||
infinity: false,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_projective(self) -> ProjectivePoint {
|
||||
if self.infinity {
|
||||
ProjectivePoint::IDENTITY
|
||||
} else {
|
||||
ProjectivePoint {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
z: FieldElement::ONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_uncompressed(self) -> Option<[u8; 65]> {
|
||||
if self.infinity {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut out = [0u8; 65];
|
||||
out[0] = 0x04;
|
||||
out[1..33].copy_from_slice(&self.x.to_be_bytes());
|
||||
out[33..65].copy_from_slice(&self.y.to_be_bytes());
|
||||
Some(out)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_identity(self) -> bool {
|
||||
self.infinity
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn x(self) -> Option<FieldElement> {
|
||||
(!self.infinity).then_some(self.x)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn y(self) -> Option<FieldElement> {
|
||||
(!self.infinity).then_some(self.y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_on_curve(self) -> bool {
|
||||
if self.infinity {
|
||||
return true;
|
||||
}
|
||||
|
||||
let y2 = self.y.square();
|
||||
let x2 = self.x.square();
|
||||
let x3 = x2 * self.x;
|
||||
let three_x = triple(self.x);
|
||||
y2 == x3 - three_x + CURVE_B
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for AffinePoint {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
if self.infinity {
|
||||
self
|
||||
} else {
|
||||
Self {
|
||||
x: self.x,
|
||||
y: -self.y,
|
||||
infinity: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ProjectivePoint {
|
||||
x: FieldElement,
|
||||
y: FieldElement,
|
||||
z: FieldElement,
|
||||
}
|
||||
|
||||
impl PartialEq for ProjectivePoint {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let self_is_identity = self.z.is_zero();
|
||||
let other_is_identity = other.z.is_zero();
|
||||
|
||||
if self_is_identity || other_is_identity {
|
||||
return self_is_identity && other_is_identity;
|
||||
}
|
||||
|
||||
let self_z2 = self.z.square();
|
||||
let other_z2 = other.z.square();
|
||||
let self_z3 = self_z2 * self.z;
|
||||
let other_z3 = other_z2 * other.z;
|
||||
|
||||
// x1 * z2^2 == x2 * z1^2
|
||||
// y1 * z2^3 == y2 * z1^3
|
||||
|
||||
self.x * other_z2 == other.x * self_z2 && self.y * other_z3 == other.y * self_z3
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ProjectivePoint {}
|
||||
|
||||
impl ProjectivePoint {
|
||||
pub const IDENTITY: Self = Self {
|
||||
x: FieldElement::ZERO,
|
||||
y: FieldElement::ONE,
|
||||
z: FieldElement::ZERO,
|
||||
};
|
||||
|
||||
pub const GENERATOR: Self = Self {
|
||||
x: GENERATOR_X,
|
||||
y: GENERATOR_Y,
|
||||
z: FieldElement::ONE,
|
||||
};
|
||||
|
||||
#[inline]
|
||||
pub fn identity() -> Self {
|
||||
Self::IDENTITY
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn generator() -> Self {
|
||||
Self::GENERATOR
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_affine(point: AffinePoint) -> Self {
|
||||
point.to_projective()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_affine(self) -> AffinePoint {
|
||||
match self.z.invert() {
|
||||
Some(zinv) => {
|
||||
let zinv2 = zinv.square();
|
||||
AffinePoint {
|
||||
x: self.x * zinv2,
|
||||
y: self.y * zinv2 * zinv,
|
||||
infinity: false,
|
||||
}
|
||||
}
|
||||
None => AffinePoint::IDENTITY,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_uncompressed(self) -> Option<[u8; 65]> {
|
||||
self.to_affine().to_uncompressed()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_identity(self) -> bool {
|
||||
self.z.is_zero()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_affine_x(self, x: FieldElement) -> bool {
|
||||
!self.is_identity() && self.x == x * self.z.square()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn double(self) -> Self {
|
||||
if self.is_identity() || self.y.is_zero() {
|
||||
return Self::IDENTITY;
|
||||
}
|
||||
|
||||
let xx = self.x.square();
|
||||
let yy = self.y.square();
|
||||
let yyyy = yy.square();
|
||||
let zz = self.z.square();
|
||||
let s = double((self.x + yy).square() - xx - yyyy);
|
||||
let m = triple(xx - zz.square());
|
||||
let x = m.square() - double(s);
|
||||
let y = m * (s - x) - double(double(double(yyyy)));
|
||||
let z = (self.y + self.z).square() - yy - zz;
|
||||
|
||||
Self { x, y, z }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_mixed(self, rhs: AffinePoint) -> Self {
|
||||
if rhs.infinity {
|
||||
return self;
|
||||
}
|
||||
if self.is_identity() {
|
||||
return rhs.to_projective();
|
||||
}
|
||||
|
||||
let z1z1 = self.z.square();
|
||||
let u2 = rhs.x * z1z1;
|
||||
let s2 = rhs.y * self.z * z1z1;
|
||||
let h = u2 - self.x;
|
||||
let slope_num = double(s2 - self.y);
|
||||
|
||||
if h.is_zero() {
|
||||
return if slope_num.is_zero() {
|
||||
self.double()
|
||||
} else {
|
||||
Self::IDENTITY
|
||||
};
|
||||
}
|
||||
|
||||
let hh = h.square();
|
||||
let i = double(double(hh));
|
||||
let j = h * i;
|
||||
let v = self.x * i;
|
||||
let x = slope_num.square() - j - double(v);
|
||||
let y = slope_num * (v - x) - double(self.y * j);
|
||||
let z = (self.z + h).square() - z1z1 - hh;
|
||||
|
||||
Self { x, y, z }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mul_scalar_vartime(self, scalar: [u8; 32]) -> Self {
|
||||
let mut table = [Self::IDENTITY; 16];
|
||||
table[1] = self;
|
||||
|
||||
for i in 2..16 {
|
||||
table[i] = table[i - 1] + self;
|
||||
}
|
||||
let affine_table = batch_normalize(table);
|
||||
|
||||
let mut out = Self::IDENTITY;
|
||||
|
||||
for &byte in scalar.iter() {
|
||||
out = out.double().double().double().double();
|
||||
out = out.add_mixed(affine_table[(byte >> 4) as usize]);
|
||||
out = out.double().double().double().double();
|
||||
out = out.add_mixed(affine_table[(byte & 0x0f) as usize]);
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fixed_base_scalar_mul_vartime(scalar: [u8; 32]) -> Self {
|
||||
mul_window8_vartime(generator_window8_table(), &scalar)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn double_scalar_mul_vartime(
|
||||
generator_scalar: [u8; 32],
|
||||
point: AffinePoint,
|
||||
point_scalar: [u8; 32],
|
||||
) -> Self {
|
||||
let generator_table = generator_window4_table();
|
||||
let point_table = window4_table(point);
|
||||
let mut out = Self::IDENTITY;
|
||||
|
||||
for (&generator_byte, &point_byte) in generator_scalar.iter().zip(point_scalar.iter()) {
|
||||
out = double_n(out, 4)
|
||||
.add_mixed(generator_table[(generator_byte >> 4) as usize])
|
||||
.add_mixed(point_table[(point_byte >> 4) as usize]);
|
||||
out = double_n(out, 4)
|
||||
.add_mixed(generator_table[(generator_byte & 0x0f) as usize])
|
||||
.add_mixed(point_table[(point_byte & 0x0f) as usize]);
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// Computes `sum(scalars[i] * points[i])` using variable-time table
|
||||
/// lookups.
|
||||
///
|
||||
/// Returns `None` when `points` and `scalars` have different lengths.
|
||||
/// This routine is intended for public inputs, such as syscall MSM
|
||||
/// plumbing; do not use it with secret scalars.
|
||||
#[inline]
|
||||
pub fn multi_scalar_mul_vartime(points: &[AffinePoint], scalars: &[[u8; 32]]) -> Option<Self> {
|
||||
if points.len() != scalars.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let tables: Vec<_> = points.iter().copied().map(window4_table).collect();
|
||||
let mut out = Self::IDENTITY;
|
||||
|
||||
for byte_index in 0..32 {
|
||||
out = double_n(out, 4);
|
||||
for (table, scalar) in tables.iter().zip(scalars) {
|
||||
out = out.add_mixed(table[(scalar[byte_index] >> 4) as usize]);
|
||||
}
|
||||
|
||||
out = double_n(out, 4);
|
||||
for (table, scalar) in tables.iter().zip(scalars) {
|
||||
out = out.add_mixed(table[(scalar[byte_index] & 0x0f) as usize]);
|
||||
}
|
||||
}
|
||||
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for ProjectivePoint {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
if self.is_identity() {
|
||||
return rhs;
|
||||
}
|
||||
if rhs.is_identity() {
|
||||
return self;
|
||||
}
|
||||
|
||||
let z1z1 = self.z.square();
|
||||
let z2z2 = rhs.z.square();
|
||||
let u1 = self.x * z2z2;
|
||||
let u2 = rhs.x * z1z1;
|
||||
let s1 = self.y * rhs.z * z2z2;
|
||||
let s2 = rhs.y * self.z * z1z1;
|
||||
|
||||
if u1 == u2 {
|
||||
return if s1 == s2 {
|
||||
self.double()
|
||||
} else {
|
||||
Self::IDENTITY
|
||||
};
|
||||
}
|
||||
|
||||
let h = u2 - u1;
|
||||
let i = double(h).square();
|
||||
let j = h * i;
|
||||
let slope_num = double(s2 - s1);
|
||||
let v = u1 * i;
|
||||
let x = slope_num.square() - j - double(v);
|
||||
let y = slope_num * (v - x) - double(s1 * j);
|
||||
let z = ((self.z + rhs.z).square() - z1z1 - z2z2) * h;
|
||||
|
||||
Self { x, y, z }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for ProjectivePoint {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
self + (-rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for ProjectivePoint {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
Self {
|
||||
x: self.x,
|
||||
y: -self.y,
|
||||
z: self.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn double(x: FieldElement) -> FieldElement {
|
||||
x + x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn triple(x: FieldElement) -> FieldElement {
|
||||
x + x + x
|
||||
}
|
||||
|
||||
fn generator_window8_table() -> &'static [[AffinePoint; BASE_WINDOW_POINTS]; BASE_WINDOWS] {
|
||||
static TABLE: OnceLock<Box<[[AffinePoint; BASE_WINDOW_POINTS]; BASE_WINDOWS]>> =
|
||||
OnceLock::new();
|
||||
|
||||
TABLE
|
||||
.get_or_init(|| build_window8_table(ProjectivePoint::GENERATOR))
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
fn generator_window4_table() -> &'static [AffinePoint; SHAMIR_WINDOW_POINTS] {
|
||||
static TABLE: OnceLock<[AffinePoint; SHAMIR_WINDOW_POINTS]> = OnceLock::new();
|
||||
|
||||
TABLE.get_or_init(|| window4_table(AffinePoint::GENERATOR))
|
||||
}
|
||||
|
||||
fn window4_table(base: AffinePoint) -> [AffinePoint; SHAMIR_WINDOW_POINTS] {
|
||||
let mut projective = [ProjectivePoint::IDENTITY; SHAMIR_WINDOW_POINTS];
|
||||
|
||||
for i in 1..SHAMIR_WINDOW_POINTS {
|
||||
projective[i] = projective[i - 1].add_mixed(base);
|
||||
}
|
||||
|
||||
batch_normalize(projective)
|
||||
}
|
||||
|
||||
fn batch_normalize<const N: usize>(points: [ProjectivePoint; N]) -> [AffinePoint; N] {
|
||||
let mut products = [FieldElement::ONE; N];
|
||||
let mut acc = FieldElement::ONE;
|
||||
|
||||
for (i, point) in points.iter().enumerate() {
|
||||
products[i] = acc;
|
||||
if !point.is_identity() {
|
||||
acc = acc * point.z;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(mut acc_inverse) = acc.invert() else {
|
||||
return [AffinePoint::IDENTITY; N];
|
||||
};
|
||||
let mut out = [AffinePoint::IDENTITY; N];
|
||||
|
||||
for i in (0..N).rev() {
|
||||
let point = points[i];
|
||||
if point.is_identity() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let z_inverse = acc_inverse * products[i];
|
||||
acc_inverse = acc_inverse * point.z;
|
||||
let z_inverse2 = z_inverse.square();
|
||||
out[i] = AffinePoint {
|
||||
x: point.x * z_inverse2,
|
||||
y: point.y * z_inverse2 * z_inverse,
|
||||
infinity: false,
|
||||
};
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn double_n(mut point: ProjectivePoint, count: usize) -> ProjectivePoint {
|
||||
for _ in 0..count {
|
||||
point = point.double();
|
||||
}
|
||||
|
||||
point
|
||||
}
|
||||
|
||||
fn build_window8_table(
|
||||
mut base: ProjectivePoint,
|
||||
) -> Box<[[AffinePoint; BASE_WINDOW_POINTS]; BASE_WINDOWS]> {
|
||||
let mut rows = Vec::with_capacity(BASE_WINDOWS);
|
||||
|
||||
for _ in 0..BASE_WINDOWS {
|
||||
rows.push(projective_window8_table(base));
|
||||
|
||||
for _ in 0..8 {
|
||||
base = base.double();
|
||||
}
|
||||
}
|
||||
|
||||
rows.into_boxed_slice()
|
||||
.try_into()
|
||||
.expect("fixed-point table has the expected number of rows")
|
||||
}
|
||||
|
||||
fn projective_window8_table(base: ProjectivePoint) -> [AffinePoint; BASE_WINDOW_POINTS] {
|
||||
let mut projective = [ProjectivePoint::IDENTITY; BASE_WINDOW_POINTS];
|
||||
let mut multiple = ProjectivePoint::IDENTITY;
|
||||
|
||||
for entry in projective.iter_mut().skip(1) {
|
||||
multiple = multiple + base;
|
||||
*entry = multiple;
|
||||
}
|
||||
|
||||
batch_normalize(projective)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mul_window8_vartime(
|
||||
table: &[[AffinePoint; BASE_WINDOW_POINTS]; BASE_WINDOWS],
|
||||
scalar: &[u8; 32],
|
||||
) -> ProjectivePoint {
|
||||
let mut out = ProjectivePoint::IDENTITY;
|
||||
|
||||
for (window, byte) in scalar.iter().rev().enumerate() {
|
||||
out = out.add_mixed(table[window][*byte as usize]);
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AffinePoint, ProjectivePoint};
|
||||
use crate::field::FieldElement;
|
||||
use p256::{
|
||||
ProjectivePoint as P256ProjectivePoint, Scalar,
|
||||
elliptic_curve::{ff::PrimeField, group::Group, sec1::ToEncodedPoint},
|
||||
};
|
||||
|
||||
const SCALAR: [u8; 32] = [
|
||||
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
|
||||
0x77, 0x88,
|
||||
];
|
||||
const SMALL_SCALAR: [u8; 32] = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 5,
|
||||
];
|
||||
|
||||
fn assert_matches_p256(rust: ProjectivePoint, p256: P256ProjectivePoint) {
|
||||
let rust_bytes = rust.to_uncompressed().unwrap();
|
||||
let p256_bytes = p256.to_affine().to_encoded_point(false);
|
||||
assert_eq!(rust_bytes.as_slice(), p256_bytes.as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator_matches_p256() {
|
||||
assert_matches_p256(
|
||||
ProjectivePoint::generator(),
|
||||
P256ProjectivePoint::generator(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_and_serializes_generator() {
|
||||
let bytes = ProjectivePoint::generator().to_uncompressed().unwrap();
|
||||
assert_eq!(
|
||||
AffinePoint::from_uncompressed(bytes).unwrap(),
|
||||
AffinePoint::generator()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn projective_equality_ignores_jacobian_scale() {
|
||||
let point = ProjectivePoint::generator().double();
|
||||
let normalized = ProjectivePoint::from_affine(point.to_affine());
|
||||
assert_eq!(point, normalized);
|
||||
|
||||
let z = FieldElement::from_u64(7);
|
||||
let z2 = z.square();
|
||||
let scaled = ProjectivePoint {
|
||||
x: point.x * z2,
|
||||
y: point.y * z2 * z,
|
||||
z: point.z * z,
|
||||
};
|
||||
|
||||
assert_eq!(point, scaled);
|
||||
assert_ne!(point, -point);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_matches_p256() {
|
||||
let p256 = P256ProjectivePoint::generator().double();
|
||||
assert_matches_p256(ProjectivePoint::generator().double(), p256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_matches_p256() {
|
||||
let rust_g = ProjectivePoint::generator();
|
||||
let rust_2g = rust_g.double();
|
||||
let p256_g = P256ProjectivePoint::generator();
|
||||
let p256_2g = p256_g.double();
|
||||
|
||||
assert_matches_p256(rust_2g + rust_g, p256_2g + p256_g);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_add_matches_p256() {
|
||||
let rust_g = ProjectivePoint::generator();
|
||||
let rust_2g = rust_g.double();
|
||||
let p256_g = P256ProjectivePoint::generator();
|
||||
let p256_2g = p256_g.double();
|
||||
|
||||
assert_matches_p256(
|
||||
rust_2g.add_mixed(AffinePoint::generator()),
|
||||
p256_2g + p256_g,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scalar_mul_matches_p256() {
|
||||
let scalar = Option::<Scalar>::from(Scalar::from_repr(SCALAR.into())).unwrap();
|
||||
assert_matches_p256(
|
||||
ProjectivePoint::generator().mul_scalar_vartime(SCALAR),
|
||||
P256ProjectivePoint::generator() * scalar,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixed_base_scalar_mul_matches_p256() {
|
||||
let scalar = Option::<Scalar>::from(Scalar::from_repr(SCALAR.into())).unwrap();
|
||||
assert_matches_p256(
|
||||
ProjectivePoint::fixed_base_scalar_mul_vartime(SCALAR),
|
||||
P256ProjectivePoint::generator() * scalar,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_scalar_mul_matches_p256() {
|
||||
let scalar = Option::<Scalar>::from(Scalar::from_repr(SCALAR.into())).unwrap();
|
||||
let point = ProjectivePoint::generator().double().to_affine();
|
||||
let p256_point = P256ProjectivePoint::generator().double();
|
||||
|
||||
assert_matches_p256(
|
||||
ProjectivePoint::double_scalar_mul_vartime(SCALAR, point, SCALAR),
|
||||
(P256ProjectivePoint::generator() * scalar) + (p256_point * scalar),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_scalar_mul_matches_p256() {
|
||||
let scalar = Option::<Scalar>::from(Scalar::from_repr(SCALAR.into())).unwrap();
|
||||
let small_scalar = Option::<Scalar>::from(Scalar::from_repr(SMALL_SCALAR.into())).unwrap();
|
||||
let point = ProjectivePoint::generator().double().to_affine();
|
||||
let p256_point = P256ProjectivePoint::generator().double();
|
||||
|
||||
assert_matches_p256(
|
||||
ProjectivePoint::multi_scalar_mul_vartime(
|
||||
&[AffinePoint::generator(), point],
|
||||
&[SCALAR, SMALL_SCALAR],
|
||||
)
|
||||
.unwrap(),
|
||||
(P256ProjectivePoint::generator() * scalar) + (p256_point * small_scalar),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_scalar_mul_rejects_length_mismatch() {
|
||||
assert!(
|
||||
ProjectivePoint::multi_scalar_mul_vartime(&[AffinePoint::generator()], &[]).is_none()
|
||||
);
|
||||
}
|
||||
}
|
||||
29
secp256r1/src/lib.rs
Normal file
29
secp256r1/src/lib.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//! secp256r1/P-256 field, scalar, and group operations.
|
||||
//!
|
||||
//! This crate implements low-level arithmetic for the NIST P-256
|
||||
//! (secp256r1) curve in pure Rust, with no C dependencies in the library.
|
||||
//! It is designed for benchmarking, experimentation, and public-input syscall
|
||||
//! plumbing.
|
||||
//!
|
||||
//! # Scope
|
||||
//!
|
||||
//! The public modules are:
|
||||
//!
|
||||
//! - [`field`] for arithmetic modulo the P-256 base field.
|
||||
//! - [`scalar`] for arithmetic modulo the P-256 group order.
|
||||
//! - [`group`] for affine/projective points, fixed-length point parsing, scalar
|
||||
//! multiplication, double-scalar multiplication, and variable-time
|
||||
//! multiscalar multiplication.
|
||||
//!
|
||||
//! # Security
|
||||
//!
|
||||
//! This crate is experimental and has not been audited. Group scalar
|
||||
//! multiplication APIs are variable time and intended for public inputs. Do not
|
||||
//! use them with secret scalars in environments where local timing/cache side
|
||||
//! channels are in scope.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod field;
|
||||
pub mod group;
|
||||
pub mod scalar;
|
||||
561
secp256r1/src/scalar.rs
Normal file
561
secp256r1/src/scalar.rs
Normal file
|
|
@ -0,0 +1,561 @@
|
|||
//! P-256 scalar field arithmetic.
|
||||
//!
|
||||
//! [`Scalar`] represents an element of GF(n) where
|
||||
//! `n = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551`
|
||||
//! is the P-256 group order. Internally scalars are stored in Montgomery
|
||||
//! form; use [`from_be_bytes`][Scalar::from_be_bytes] and
|
||||
//! [`to_be_bytes`][Scalar::to_be_bytes] to convert from/to canonical
|
||||
//! big-endian representation.
|
||||
|
||||
use core::ops::{Add, Mul, Neg, Sub};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct Scalar {
|
||||
limbs: [u64; 4],
|
||||
}
|
||||
|
||||
const MODULUS: [u64; 4] = [
|
||||
0xf3b9_cac2_fc63_2551,
|
||||
0xbce6_faad_a717_9e84,
|
||||
0xffff_ffff_ffff_ffff,
|
||||
0xffff_ffff_0000_0000,
|
||||
];
|
||||
|
||||
const MODULUS_INV: u64 = 0xccd1_c8aa_ee00_bc4f;
|
||||
|
||||
const R2: [u64; 4] = [
|
||||
0x8324_4c95_be79_eea2,
|
||||
0x4699_799c_49bd_6fa6,
|
||||
0x2845_b239_2b6b_ec59,
|
||||
0x66e1_2d94_f3d9_5620,
|
||||
];
|
||||
|
||||
const MODULUS_MINUS_TWO: [u8; 32] = [
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x4f,
|
||||
];
|
||||
const INVERT_WINDOW: usize = 5;
|
||||
const INVERT_TABLE_POINTS: usize = 1 << (INVERT_WINDOW - 1);
|
||||
|
||||
impl Scalar {
|
||||
pub const ZERO: Self = Self { limbs: [0; 4] };
|
||||
pub const ONE: Self = Self {
|
||||
limbs: [
|
||||
0x0c46_353d_039c_daaf,
|
||||
0x4319_0552_58e8_617b,
|
||||
0x0000_0000_0000_0000,
|
||||
0x0000_0000_ffff_ffff,
|
||||
],
|
||||
};
|
||||
|
||||
#[inline]
|
||||
pub fn from_be_bytes(bytes: [u8; 32]) -> Option<Self> {
|
||||
Self::from_canonical_limbs(limbs_from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_be_bytes_reduced(bytes: [u8; 32]) -> Self {
|
||||
let mut limbs = limbs_from_be_bytes(bytes);
|
||||
|
||||
if ge_limbs(limbs, MODULUS) {
|
||||
limbs = sub_limbs(limbs, MODULUS).0;
|
||||
}
|
||||
|
||||
Self {
|
||||
limbs: montgomery_mul(limbs, R2),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_be_bytes(self) -> [u8; 32] {
|
||||
be_bytes_from_limbs(from_montgomery(self.limbs))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_zero(self) -> bool {
|
||||
self == Self::ZERO
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn square(self) -> Self {
|
||||
Self {
|
||||
limbs: montgomery_square(self.limbs),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn invert(self) -> Option<Self> {
|
||||
if self.is_zero() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let table = odd_powers(self);
|
||||
let mut out = Self::ONE;
|
||||
let mut bit = 255isize;
|
||||
|
||||
while bit >= 0 {
|
||||
if !modulus_minus_two_bit(bit as usize) {
|
||||
out = out.square();
|
||||
bit -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut width = INVERT_WINDOW.min(bit as usize + 1);
|
||||
while width > 1 && !modulus_minus_two_bit(bit as usize + 1 - width) {
|
||||
width -= 1;
|
||||
}
|
||||
|
||||
let low = bit as usize + 1 - width;
|
||||
let mut value = 0usize;
|
||||
for i in (low..=bit as usize).rev() {
|
||||
value = (value << 1) | usize::from(modulus_minus_two_bit(i));
|
||||
}
|
||||
|
||||
for _ in 0..width {
|
||||
out = out.square();
|
||||
}
|
||||
out = out * table[value >> 1];
|
||||
bit -= width as isize;
|
||||
}
|
||||
|
||||
Some(out)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn from_canonical_limbs(limbs: [u64; 4]) -> Option<Self> {
|
||||
if ge_limbs(limbs, MODULUS) {
|
||||
None
|
||||
} else {
|
||||
Some(Self {
|
||||
limbs: montgomery_mul(limbs, R2),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Scalar {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let (sum, carry) = add_limbs(self.limbs, rhs.limbs);
|
||||
Self {
|
||||
limbs: reduce_sum(sum, carry),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Scalar {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
let (difference, borrow) = sub_limbs(self.limbs, rhs.limbs);
|
||||
let (corrected, _) = add_limbs(difference, MODULUS);
|
||||
|
||||
Self {
|
||||
limbs: if borrow == 0 { difference } else { corrected },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Scalar {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
limbs: montgomery_mul(self.limbs, rhs.limbs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Scalar {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
Self::ZERO - self
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn odd_powers(value: Scalar) -> [Scalar; INVERT_TABLE_POINTS] {
|
||||
let mut table = [Scalar::ZERO; INVERT_TABLE_POINTS];
|
||||
table[0] = value;
|
||||
|
||||
let value_squared = value.square();
|
||||
for i in 1..INVERT_TABLE_POINTS {
|
||||
table[i] = table[i - 1] * value_squared;
|
||||
}
|
||||
|
||||
table
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn modulus_minus_two_bit(bit: usize) -> bool {
|
||||
((MODULUS_MINUS_TWO[31 - bit / 8] >> (bit % 8)) & 1) == 1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_limbs(a: [u64; 4], b: [u64; 4]) -> ([u64; 4], u64) {
|
||||
let mut out = [0; 4];
|
||||
let mut carry = 0u64;
|
||||
|
||||
for i in 0..4 {
|
||||
let (sum, carry1) = a[i].overflowing_add(b[i]);
|
||||
let (sum, carry2) = sum.overflowing_add(carry);
|
||||
out[i] = sum;
|
||||
carry = u64::from(carry1 | carry2);
|
||||
}
|
||||
|
||||
(out, carry)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sub_limbs(a: [u64; 4], b: [u64; 4]) -> ([u64; 4], u64) {
|
||||
let mut out = [0; 4];
|
||||
let mut borrow = 0u64;
|
||||
|
||||
for i in 0..4 {
|
||||
let (difference, borrow1) = a[i].overflowing_sub(b[i]);
|
||||
let (difference, borrow2) = difference.overflowing_sub(borrow);
|
||||
out[i] = difference;
|
||||
borrow = u64::from(borrow1 | borrow2);
|
||||
}
|
||||
|
||||
(out, borrow)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn reduce_sum(sum: [u64; 4], carry: u64) -> [u64; 4] {
|
||||
let (reduced, borrow) = sub_limbs(sum, MODULUS);
|
||||
|
||||
if carry != 0 || borrow == 0 {
|
||||
reduced
|
||||
} else {
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ge_limbs(a: [u64; 4], b: [u64; 4]) -> bool {
|
||||
sub_limbs(a, b).1 == 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mul_wide(a: [u64; 4], b: [u64; 4]) -> [u64; 8] {
|
||||
let (w0, carry) = mac(0, a[0], b[0], 0);
|
||||
let (w1, carry) = mac(0, a[0], b[1], carry);
|
||||
let (w2, carry) = mac(0, a[0], b[2], carry);
|
||||
let (w3, w4) = mac(0, a[0], b[3], carry);
|
||||
|
||||
let (w1, carry) = mac(w1, a[1], b[0], 0);
|
||||
let (w2, carry) = mac(w2, a[1], b[1], carry);
|
||||
let (w3, carry) = mac(w3, a[1], b[2], carry);
|
||||
let (w4, w5) = mac(w4, a[1], b[3], carry);
|
||||
|
||||
let (w2, carry) = mac(w2, a[2], b[0], 0);
|
||||
let (w3, carry) = mac(w3, a[2], b[1], carry);
|
||||
let (w4, carry) = mac(w4, a[2], b[2], carry);
|
||||
let (w5, w6) = mac(w5, a[2], b[3], carry);
|
||||
|
||||
let (w3, carry) = mac(w3, a[3], b[0], 0);
|
||||
let (w4, carry) = mac(w4, a[3], b[1], carry);
|
||||
let (w5, carry) = mac(w5, a[3], b[2], carry);
|
||||
let (w6, w7) = mac(w6, a[3], b[3], carry);
|
||||
|
||||
[w0, w1, w2, w3, w4, w5, w6, w7]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn montgomery_mul(a: [u64; 4], b: [u64; 4]) -> [u64; 4] {
|
||||
montgomery_reduce(mul_wide(a, b))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn montgomery_square(a: [u64; 4]) -> [u64; 4] {
|
||||
let a0 = a[0] as u128;
|
||||
let a1 = a[1] as u128;
|
||||
let a2 = a[2] as u128;
|
||||
let a3 = a[3] as u128;
|
||||
|
||||
let p00 = a0 * a0;
|
||||
let w0 = p00 as u64;
|
||||
let mut acc = p00 >> 64;
|
||||
let mut top = 0u64;
|
||||
|
||||
let p01 = a0 * a1;
|
||||
top += (p01 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p01 << 1);
|
||||
let w1 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
top = 0;
|
||||
let p02 = a0 * a2;
|
||||
top += (p02 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p02 << 1);
|
||||
acc = sum;
|
||||
top += u64::from(overflow);
|
||||
let (sum, overflow) = acc.overflowing_add(a1 * a1);
|
||||
let w2 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
top = 0;
|
||||
let p03 = a0 * a3;
|
||||
top += (p03 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p03 << 1);
|
||||
acc = sum;
|
||||
top += u64::from(overflow);
|
||||
let p12 = a1 * a2;
|
||||
top += (p12 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p12 << 1);
|
||||
let w3 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
top = 0;
|
||||
let p13 = a1 * a3;
|
||||
top += (p13 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p13 << 1);
|
||||
acc = sum;
|
||||
top += u64::from(overflow);
|
||||
let (sum, overflow) = acc.overflowing_add(a2 * a2);
|
||||
let w4 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
top = 0;
|
||||
let p23 = a2 * a3;
|
||||
top += (p23 >> 127) as u64;
|
||||
let (sum, overflow) = acc.overflowing_add(p23 << 1);
|
||||
let w5 = sum as u64;
|
||||
acc = sum >> 64;
|
||||
top += u64::from(overflow);
|
||||
|
||||
acc |= (top as u128) << 64;
|
||||
let (sum, overflow) = acc.overflowing_add(a3 * a3);
|
||||
let w6 = sum as u64;
|
||||
let w7 = (sum >> 64) as u64;
|
||||
debug_assert!(!overflow);
|
||||
|
||||
montgomery_reduce_words(w0, w1, w2, w3, w4, w5, w6, w7)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn from_montgomery(a: [u64; 4]) -> [u64; 4] {
|
||||
montgomery_reduce([a[0], a[1], a[2], a[3], 0, 0, 0, 0])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn montgomery_reduce(input: [u64; 8]) -> [u64; 4] {
|
||||
montgomery_reduce_words(
|
||||
input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7],
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn montgomery_reduce_words(
|
||||
r0: u64,
|
||||
r1: u64,
|
||||
r2: u64,
|
||||
r3: u64,
|
||||
r4: u64,
|
||||
r5: u64,
|
||||
r6: u64,
|
||||
r7: u64,
|
||||
) -> [u64; 4] {
|
||||
let k = r0.wrapping_mul(MODULUS_INV);
|
||||
let (_, carry) = mac(r0, k, MODULUS[0], 0);
|
||||
let (r1, carry) = mac(r1, k, MODULUS[1], carry);
|
||||
let (r2, carry) = mac(r2, k, MODULUS[2], carry);
|
||||
let (r3, carry) = mac(r3, k, MODULUS[3], carry);
|
||||
let (r4, carry2) = adc(r4, 0, carry);
|
||||
|
||||
let k = r1.wrapping_mul(MODULUS_INV);
|
||||
let (_, carry) = mac(r1, k, MODULUS[0], 0);
|
||||
let (r2, carry) = mac(r2, k, MODULUS[1], carry);
|
||||
let (r3, carry) = mac(r3, k, MODULUS[2], carry);
|
||||
let (r4, carry) = mac(r4, k, MODULUS[3], carry);
|
||||
let (r5, carry2) = adc(r5, carry2, carry);
|
||||
|
||||
let k = r2.wrapping_mul(MODULUS_INV);
|
||||
let (_, carry) = mac(r2, k, MODULUS[0], 0);
|
||||
let (r3, carry) = mac(r3, k, MODULUS[1], carry);
|
||||
let (r4, carry) = mac(r4, k, MODULUS[2], carry);
|
||||
let (r5, carry) = mac(r5, k, MODULUS[3], carry);
|
||||
let (r6, carry2) = adc(r6, carry2, carry);
|
||||
|
||||
let k = r3.wrapping_mul(MODULUS_INV);
|
||||
let (_, carry) = mac(r3, k, MODULUS[0], 0);
|
||||
let (r4, carry) = mac(r4, k, MODULUS[1], carry);
|
||||
let (r5, carry) = mac(r5, k, MODULUS[2], carry);
|
||||
let (r6, carry) = mac(r6, k, MODULUS[3], carry);
|
||||
let (r7, r8) = adc(r7, carry2, carry);
|
||||
|
||||
reduce_wide([r4, r5, r6, r7, r8])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn reduce_wide(value: [u64; 5]) -> [u64; 4] {
|
||||
let (w0, borrow) = sbb(value[0], MODULUS[0], 0);
|
||||
let (w1, borrow) = sbb(value[1], MODULUS[1], borrow);
|
||||
let (w2, borrow) = sbb(value[2], MODULUS[2], borrow);
|
||||
let (w3, borrow) = sbb(value[3], MODULUS[3], borrow);
|
||||
let (_, borrow) = sbb(value[4], 0, borrow);
|
||||
|
||||
if borrow == 0 {
|
||||
[w0, w1, w2, w3]
|
||||
} else {
|
||||
[value[0], value[1], value[2], value[3]]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) {
|
||||
let sum = (a as u128) + (b as u128) + (carry as u128);
|
||||
(sum as u64, (sum >> 64) as u64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sbb(a: u64, b: u64, borrow: u64) -> (u64, u64) {
|
||||
let difference = (a as u128).wrapping_sub((b as u128) + (borrow as u128));
|
||||
(difference as u64, u64::from((difference >> 127) != 0))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) {
|
||||
let product = (a as u128) + (b as u128) * (c as u128) + (carry as u128);
|
||||
(product as u64, (product >> 64) as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn limbs_from_be_bytes(bytes: [u8; 32]) -> [u64; 4] {
|
||||
let mut limbs = [0u64; 4];
|
||||
|
||||
for (i, chunk) in bytes.chunks_exact(8).rev().enumerate() {
|
||||
limbs[i] = u64::from_be_bytes(chunk.try_into().expect("chunk length is 8"));
|
||||
}
|
||||
|
||||
limbs
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn be_bytes_from_limbs(limbs: [u64; 4]) -> [u8; 32] {
|
||||
let mut bytes = [0u8; 32];
|
||||
|
||||
for (i, limb) in limbs.iter().rev().enumerate() {
|
||||
bytes[i * 8..(i + 1) * 8].copy_from_slice(&limb.to_be_bytes());
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Scalar;
|
||||
use p256::{
|
||||
Scalar as P256Scalar,
|
||||
elliptic_curve::{bigint::U256, ff::PrimeField, ops::Reduce},
|
||||
};
|
||||
|
||||
const A: [u8; 32] = [
|
||||
0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
||||
0x88, 0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
|
||||
0x77, 0x88,
|
||||
];
|
||||
const B: [u8; 32] = [
|
||||
0x0f, 0xed, 0xcb, 0xa9, 0x87, 0x65, 0x43, 0x21, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99,
|
||||
0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0x80, 0x70, 0x60, 0x50, 0x40, 0x30,
|
||||
0x20, 0x10,
|
||||
];
|
||||
const N: [u8; 32] = [
|
||||
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63,
|
||||
0x25, 0x51,
|
||||
];
|
||||
|
||||
fn p256_scalar(bytes: [u8; 32]) -> P256Scalar {
|
||||
Option::from(P256Scalar::from_repr(bytes.into())).unwrap()
|
||||
}
|
||||
|
||||
fn assert_matches_p256(rust: Scalar, p256: P256Scalar) {
|
||||
let p256_bytes: [u8; 32] = p256.to_repr().into();
|
||||
assert_eq!(rust.to_be_bytes(), p256_bytes);
|
||||
}
|
||||
|
||||
fn sample(mut seed: u64) -> [u8; 32] {
|
||||
let mut bytes = [0u8; 32];
|
||||
|
||||
for chunk in bytes.chunks_exact_mut(8) {
|
||||
seed ^= seed << 13;
|
||||
seed ^= seed >> 7;
|
||||
seed ^= seed << 17;
|
||||
chunk.copy_from_slice(&seed.to_be_bytes());
|
||||
}
|
||||
|
||||
bytes[0] &= 0x7f;
|
||||
bytes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_non_canonical_order() {
|
||||
assert!(Scalar::from_be_bytes(N).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trips() {
|
||||
for bytes in [[0u8; 32], A, B] {
|
||||
assert_eq!(Scalar::from_be_bytes(bytes).unwrap().to_be_bytes(), bytes);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arithmetic_matches_p256() {
|
||||
let a = Scalar::from_be_bytes(A).unwrap();
|
||||
let b = Scalar::from_be_bytes(B).unwrap();
|
||||
let p256_a = p256_scalar(A);
|
||||
let p256_b = p256_scalar(B);
|
||||
|
||||
assert_matches_p256(a + b, p256_a + p256_b);
|
||||
assert_matches_p256(a - b, p256_a - p256_b);
|
||||
assert_matches_p256(a * b, p256_a * p256_b);
|
||||
assert_matches_p256(a.square(), p256_a.square());
|
||||
assert_matches_p256(a.invert().unwrap(), Option::from(p256_a.invert()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reduced_bytes_match_p256() {
|
||||
let rust = Scalar::from_be_bytes_reduced(N);
|
||||
let p256 = P256Scalar::reduce(U256::from_be_slice(&N));
|
||||
assert_matches_p256(rust, p256);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arithmetic_matches_p256_for_many_samples() {
|
||||
for i in 1..128 {
|
||||
let a = sample(i);
|
||||
let b = sample(i ^ 0xa5a5_a5a5_a5a5_a5a5);
|
||||
let rust_a = Scalar::from_be_bytes(a).unwrap();
|
||||
let rust_b = Scalar::from_be_bytes(b).unwrap();
|
||||
let p256_a = p256_scalar(a);
|
||||
let p256_b = p256_scalar(b);
|
||||
|
||||
assert_matches_p256(rust_a + rust_b, p256_a + p256_b);
|
||||
assert_matches_p256(rust_a - rust_b, p256_a - p256_b);
|
||||
assert_matches_p256(rust_a * rust_b, p256_a * p256_b);
|
||||
assert_matches_p256(rust_a.square(), p256_a.square());
|
||||
assert_matches_p256(
|
||||
rust_a.invert().unwrap(),
|
||||
Option::from(p256_a.invert()).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue