ic_ec/lib.rs
1//! # ic-ec — elliptic-curve cryptography
2//!
3//! Curve25519 in two guises:
4//!
5//! * [`X25519`] — RFC 7748 key agreement.
6//! * [`Ed25519`] — RFC 8032 signatures.
7//!
8//! Both are built on a shared constant-time field implementation
9//! (`field::Fe`) with 51-bit limbs, and on x86-64 CPUs with BMI2 and ADX on a
10//! four-limb field (`fe64::Fe64`) whose multiplication is `MULX`/`ADX`
11//! assembly. The second is chosen at run time, once per operation; the first
12//! is the fallback and the reference the second is tested against.
13//!
14//! ```
15//! # #[cfg(not(ic_approved_only))]
16//! # fn main() -> ic_core::Result<()> {
17//! use ic_ec::X25519;
18//! use ic_core::traits::KeyAgreement;
19//!
20//! let alice_sk = [0x11u8; 32];
21//! let bob_sk = [0x22u8; 32];
22//! let (mut alice_pk, mut bob_pk) = ([0u8; 32], [0u8; 32]);
23//! X25519::public_key(&alice_sk, &mut alice_pk)?;
24//! X25519::public_key(&bob_sk, &mut bob_pk)?;
25//!
26//! let (mut s1, mut s2) = ([0u8; 32], [0u8; 32]);
27//! X25519::agree(&alice_sk, &bob_pk, &mut s1)?;
28//! X25519::agree(&bob_sk, &alice_pk, &mut s2)?;
29//! assert_eq!(s1, s2);
30//! # Ok(())
31//! # }
32//! # #[cfg(ic_approved_only)]
33//! # fn main() {}
34//! ```
35//!
36//! ## FIPS position
37//!
38//! Curve25519 is **not** on the FIPS 186-5 / SP 800-186 approved list for
39//! signatures, and X25519 is not an approved SP 800-56A scheme. They are here
40//! because modern protocols require them, and the ontology marks them
41//! accordingly so `ic-fips` blocks them in approved mode, and the
42//! `ic_approved_only` cfg leaves them out of the build. The approved curves
43//! are here too: ECDSA and ECDH over P-256, P-384 and P-521, in [`p256`],
44//! [`p384`] and [`p521`].
45#![cfg_attr(not(feature = "std"), no_std)]
46// `deny` rather than `forbid`, so that the x86-64 assembly can be allowed it
47// and nothing else: `fe64` and `f51x4` for Curve25519, and `nist::p256_x86`,
48// `nist::p384_x86`, the generated `nist::p256_x86_point`,
49// `nist::p384_x86_point` and `nist::mont_x86` for P-256 and P-384. Each is
50// `asm!` and so `unsafe` by construction; everywhere else in this crate
51// `unsafe` is a compile error.
52#![deny(unsafe_code)]
53#![deny(unsafe_op_in_unsafe_fn)]
54#![deny(missing_docs)]
55#![warn(clippy::all)]
56
57// Each `not(ic_approved_only)` below is an algorithm FIPS approved mode does
58// not permit, left out of that build; see `ic_core::APPROVED_ONLY`.
59#[cfg(not(ic_approved_only))]
60pub mod ed25519;
61#[cfg(not(ic_approved_only))]
62pub mod hash_to_curve;
63#[cfg(not(ic_approved_only))]
64pub mod oprf;
65// GF(2^255 - 19). Five 51-bit limbs where a 64x64 multiply is cheap; ten limbs
66// of 26 and 25 bits on 32-bit RISC-V, where 128-bit arithmetic compiles to
67// branches on secret carries. `--cfg ic_limb32` selects the second anywhere, so
68// the Curve25519 vectors can be run against it on a host.
69#[cfg(all(not(ic_approved_only), not(any(target_arch = "riscv32", ic_limb32))))]
70mod field;
71#[cfg(all(not(ic_approved_only), any(target_arch = "riscv32", ic_limb32)))]
72#[path = "field32.rs"]
73mod field;
74// Compiled beside the five-limb field under test, to be compared with it.
75#[cfg(all(
76 test,
77 not(ic_approved_only),
78 not(any(target_arch = "riscv32", ic_limb32))
79))]
80mod field32;
81// GF(2^255 - 19) in four 64-bit limbs, for x86-64 CPUs with BMI2 and ADX,
82// selected at run time; see `field_ops::dispatch!`. Not under `ic_limb32`,
83// whose purpose is to run the curve vectors against the ten-limb field, nor
84// under `ic_ec_portable`, which runs everything on the portable field.
85#[cfg(all(
86 not(ic_approved_only),
87 target_arch = "x86_64",
88 not(any(ic_limb32, ic_ec_portable))
89))]
90#[allow(unsafe_code)]
91pub(crate) mod fe64;
92// Four field elements at once on AVX-512 IFMA, and the Edwards operations on
93// them; selected at run time where the CPU has it. Same conditions as `fe64`,
94// which it converts through.
95#[cfg(all(
96 not(ic_approved_only),
97 target_arch = "x86_64",
98 not(any(ic_limb32, ic_ec_portable))
99))]
100#[allow(unsafe_code)]
101// Its unsafe functions are `#[target_feature]` leaves, each a few lines of
102// IFMA asm, whose one contract -- the CPU has AVX-512 IFMA -- is the
103// function's own and is discharged once, by the `Ifma` token. A separate
104// `unsafe` block around each of the 149 operations inside them would restate
105// that contract and add nothing; the rest of the crate keeps the lint.
106#[allow(unsafe_op_in_unsafe_fn)]
107pub(crate) mod f51x4;
108#[cfg(not(ic_approved_only))]
109mod field_ops;
110
111#[cfg(feature = "ct-probes")]
112#[doc(hidden)]
113pub mod ct_probes;
114mod nist;
115pub mod p256;
116pub mod p384;
117pub mod p521;
118#[cfg(not(ic_approved_only))]
119mod scalar;
120#[cfg(not(ic_approved_only))]
121pub mod x25519;
122
123#[cfg(not(ic_approved_only))]
124pub use ed25519::{Ed25519, Ed25519Key, Ed25519VerifyKey};
125pub use p256::{EcdhP256, EcdsaP256Sha256};
126pub use p384::{EcdhP384, EcdsaP384Sha384};
127#[cfg(not(ic_approved_only))]
128pub use x25519::X25519;
129
130/// The implementation of GF(2^255 - 19) that X25519 and Ed25519 run on here.
131///
132/// A published vector passes whichever implementation runs, so it cannot tell
133/// which one did; this can.
134#[cfg(not(ic_approved_only))]
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum Curve25519Backend {
137 /// Four elements at once on AVX-512 IFMA (x86-64), for X25519 and, under
138 /// `std`, Ed25519's scalar multiplications; the MULX/ADX field for the
139 /// rest.
140 Ifma,
141 /// Four 64-bit limbs on MULX and ADX (x86-64).
142 MulxAdx,
143 /// The portable field: five 51-bit limbs, or ten of 26 and 25 bits on
144 /// 32-bit RISC-V and under `--cfg ic_limb32`.
145 Portable,
146}
147
148/// Which [`Curve25519Backend`] this CPU and build run, chosen at run time on
149/// the CPU's features alone.
150#[cfg(not(ic_approved_only))]
151#[must_use]
152pub fn curve25519_backend() -> Curve25519Backend {
153 #[cfg(all(target_arch = "x86_64", not(any(ic_limb32, ic_ec_portable))))]
154 {
155 if f51x4::Ifma::get().is_some() {
156 return Curve25519Backend::Ifma;
157 }
158 if fe64::available() {
159 return Curve25519Backend::MulxAdx;
160 }
161 }
162 Curve25519Backend::Portable
163}
164
165/// Digest widths ECDSA's `verify_prehash` accepts.
166pub use nist::ecdsa::PREHASH_LENS;
167
168/// The narrowest digest ECDSA's `verify_prehash` accepts on a curve.
169pub use nist::ecdsa::min_prehash_len;
170
171/// Build every precomputed table now, rather than on first use.
172///
173/// Under `std`, the generator tables for P-256, P-384 and P-521 and the
174/// Ed25519 basepoint tables are built the first time an operation needs them:
175/// about one scalar multiplication per curve, a few hundred microseconds in
176/// all on a host. They live in statics, so building them allocates nothing
177/// whenever it happens. What this changes is *when* the time is spent: a
178/// caller that wants its first handshake to cost what every later one does can
179/// pay it at start-up instead.
180///
181/// Safe to call more than once and from several threads: the work is done
182/// once, and a second caller waits for the first. Under `no_std` there are no
183/// tables, and this does nothing.
184pub fn prepare() {
185 use nist::gentable::HasGeneratorTable;
186 p256::P256::prepare_generator_table();
187 p384::P384::prepare_generator_table();
188 p521::P521::prepare_generator_table();
189 #[cfg(all(feature = "std", not(ic_approved_only)))]
190 ed25519::prepare_tables();
191}
192
193/// Ontology identifiers for the schemes implemented here.
194pub const EC_IDS: &[&str] = &[
195 #[cfg(not(ic_approved_only))]
196 "x25519",
197 #[cfg(not(ic_approved_only))]
198 "ed25519",
199 "ecdh-p256",
200 "ecdsa-p256-sha256",
201 "ecdh-p384",
202 "ecdsa-p384-sha384",
203];