mirror of
https://github.com/saymrwulf/anza-cryptography-source.git
synced 2026-09-05 20:30:51 +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",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base16ct"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.8.3"
|
version = "1.8.3"
|
||||||
|
|
@ -539,6 +545,18 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
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]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
|
@ -681,6 +699,23 @@ version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
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]]
|
[[package]]
|
||||||
name = "enum-ordinalize"
|
name = "enum-ordinalize"
|
||||||
version = "4.3.2"
|
version = "4.3.2"
|
||||||
|
|
@ -768,6 +803,21 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
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]]
|
[[package]]
|
||||||
name = "funty"
|
name = "funty"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
|
@ -806,6 +856,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1103,12 +1154,59 @@ version = "11.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
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]]
|
[[package]]
|
||||||
name = "owo-colors"
|
name = "owo-colors"
|
||||||
version = "4.3.0"
|
version = "4.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
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]]
|
[[package]]
|
||||||
name = "p3-air"
|
name = "p3-air"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
@ -1421,6 +1519,12 @@ dependencies = [
|
||||||
"spki",
|
"spki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plotters"
|
name = "plotters"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
|
@ -1468,6 +1572,15 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primeorder"
|
||||||
|
version = "0.13.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
|
||||||
|
dependencies = [
|
||||||
|
"elliptic-curve",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
|
|
@ -1708,6 +1821,28 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
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]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.28"
|
version = "1.0.28"
|
||||||
|
|
@ -2086,6 +2221,12 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ members = [
|
||||||
"curve25519/curve25519-cuda",
|
"curve25519/curve25519-cuda",
|
||||||
"curve25519/solana-ed25519",
|
"curve25519/solana-ed25519",
|
||||||
"experimental/ed25519-pokos",
|
"experimental/ed25519-pokos",
|
||||||
|
"secp256r1",
|
||||||
"syscall/bls12-381-syscall",
|
"syscall/bls12-381-syscall",
|
||||||
"syscall/bn254-syscall",
|
"syscall/bn254-syscall",
|
||||||
]
|
]
|
||||||
|
|
@ -41,7 +42,10 @@ group = { version = "0.13.0", default-features = false }
|
||||||
hashbrown = "0.15"
|
hashbrown = "0.15"
|
||||||
hex = "0.4.2"
|
hex = "0.4.2"
|
||||||
hex-literal = "1.1.0"
|
hex-literal = "1.1.0"
|
||||||
|
hmac = "0.12"
|
||||||
|
openssl = "0.10"
|
||||||
pairing = "0.23.0"
|
pairing = "0.23.0"
|
||||||
|
p256 = { version = "0.13", default-features = false, features = ["arithmetic", "expose-field"] }
|
||||||
pkcs8 = { version = "0.10.1" }
|
pkcs8 = { version = "0.10.1" }
|
||||||
once_cell = "1.21"
|
once_cell = "1.21"
|
||||||
proptest = "=1.6.0"
|
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