Skip to main content

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, `avx512` to ask the
48// CPU, and `nist::p256_x86`, `nist::p384_x86`, the generated
49// `nist::p256_x86_point`, `nist::p384_x86_point`, `nist::mont_x86` and
50// `nist::ifma_kernels`, and `nist::ifma`, for P-256 and P-384. Each is
51// `asm!` and so `unsafe` by construction; everywhere else in this crate
52// `unsafe` is a compile error.
53#![deny(unsafe_code)]
54#![deny(unsafe_op_in_unsafe_fn)]
55#![deny(missing_docs)]
56#![warn(clippy::all)]
57
58// Each `not(ic_approved_only)` below is an algorithm FIPS approved mode does
59// not permit, left out of that build; see `ic_core::APPROVED_ONLY`.
60#[cfg(not(ic_approved_only))]
61pub mod ed25519;
62#[cfg(not(ic_approved_only))]
63pub mod hash_to_curve;
64#[cfg(not(ic_approved_only))]
65pub mod oprf;
66// GF(2^255 - 19). Five 51-bit limbs where a 64x64 multiply is cheap; ten limbs
67// of 26 and 25 bits on 32-bit RISC-V, where 128-bit arithmetic compiles to
68// branches on secret carries. `--cfg ic_limb32` selects the second anywhere, so
69// the Curve25519 vectors can be run against it on a host.
70#[cfg(all(not(ic_approved_only), not(any(target_arch = "riscv32", ic_limb32))))]
71mod field;
72#[cfg(all(not(ic_approved_only), any(target_arch = "riscv32", ic_limb32)))]
73#[path = "field32.rs"]
74mod field;
75// Compiled beside the five-limb field under test, to be compared with it.
76#[cfg(all(
77    test,
78    not(ic_approved_only),
79    not(any(target_arch = "riscv32", ic_limb32))
80))]
81mod field32;
82// GF(2^255 - 19) in four 64-bit limbs, for x86-64 CPUs with BMI2 and ADX,
83// selected at run time; see `field_ops::dispatch!`. Not under `ic_limb32`,
84// whose purpose is to run the curve vectors against the ten-limb field, nor
85// under `ic_ec_portable`, which runs everything on the portable field.
86#[cfg(all(
87    not(ic_approved_only),
88    target_arch = "x86_64",
89    not(any(ic_limb32, ic_ec_portable))
90))]
91#[allow(unsafe_code)]
92pub(crate) mod fe64;
93// Four field elements at once on AVX-512 IFMA, and the Edwards operations on
94// them; selected at run time where the CPU has it. Same conditions as `fe64`,
95// which it converts through.
96#[cfg(all(
97    not(ic_approved_only),
98    target_arch = "x86_64",
99    not(any(ic_limb32, ic_ec_portable))
100))]
101#[allow(unsafe_code)]
102// Its unsafe functions are `#[target_feature]` leaves, each a few lines of
103// IFMA asm, whose one contract -- the CPU has AVX-512 IFMA -- is the
104// function's own and is discharged once, by the `Ifma` token. A separate
105// `unsafe` block around each of the 149 operations inside them would restate
106// that contract and add nothing; the rest of the crate keeps the lint.
107#[allow(unsafe_op_in_unsafe_fn)]
108pub(crate) mod f51x4;
109#[cfg(not(ic_approved_only))]
110mod field_ops;
111// Whether the CPU runs AVX-512 IFMA, for `f51x4` and `nist::ifma`.
112#[cfg(target_arch = "x86_64")]
113#[allow(unsafe_code)]
114pub(crate) mod avx512;
115
116#[cfg(feature = "ct-probes")]
117#[doc(hidden)]
118pub mod ct_probes;
119mod nist;
120pub mod p256;
121pub mod p384;
122pub mod p521;
123#[cfg(not(ic_approved_only))]
124mod scalar;
125#[cfg(not(ic_approved_only))]
126pub mod x25519;
127
128#[cfg(not(ic_approved_only))]
129pub use ed25519::{Ed25519, Ed25519Key, Ed25519VerifyKey};
130pub use p256::{EcdhP256, EcdsaP256Sha256};
131pub use p384::{EcdhP384, EcdsaP384Sha384};
132#[cfg(not(ic_approved_only))]
133pub use x25519::X25519;
134
135/// The implementation P-256's and P-384's scalar multiplications run on here.
136///
137/// A published vector passes whichever implementation runs, so it cannot tell
138/// which one did; this can. P-521 runs the generic field everywhere.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum NistBackend {
141    /// Four field elements at once on AVX-512 IFMA (x86-64): ECDH's, a
142    /// prepared key's and ECDSA signing's scalar multiplications; the rest,
143    /// verification among it, on MULX/ADX.
144    Ifma,
145    /// The field and the point formulas in MULX/ADCX/ADOX assembly (x86-64
146    /// with BMI2 and ADX).
147    MulxAdx,
148    /// Without BMI2 and ADX: on x86-64 P-256's field in baseline `MUL`/`ADC`
149    /// assembly and P-384's generic word loop; elsewhere the portable
150    /// arithmetic.
151    Portable,
152}
153
154/// Which [`NistBackend`] this CPU and build run, chosen at run time on the
155/// CPU's features alone; `--cfg ic_no_avx512` takes `Ifma` away.
156#[must_use]
157pub fn nist_backend() -> NistBackend {
158    #[cfg(target_arch = "x86_64")]
159    {
160        if nist::ifma::Ifma::get().is_some() {
161            return NistBackend::Ifma;
162        }
163        if nist::p256_x86::has_adx() {
164            return NistBackend::MulxAdx;
165        }
166    }
167    NistBackend::Portable
168}
169
170/// The implementation of GF(2^255 - 19) that X25519 and Ed25519 run on here.
171///
172/// A published vector passes whichever implementation runs, so it cannot tell
173/// which one did; this can.
174#[cfg(not(ic_approved_only))]
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum Curve25519Backend {
177    /// Four elements at once on AVX-512 IFMA (x86-64), for X25519 and, under
178    /// `std`, Ed25519's scalar multiplications; the MULX/ADX field for the
179    /// rest.
180    Ifma,
181    /// Four 64-bit limbs on MULX and ADX (x86-64).
182    MulxAdx,
183    /// The portable field: five 51-bit limbs, or ten of 26 and 25 bits on
184    /// 32-bit RISC-V and under `--cfg ic_limb32`.
185    Portable,
186}
187
188/// Which [`Curve25519Backend`] this CPU and build run, chosen at run time on
189/// the CPU's features alone.
190#[cfg(not(ic_approved_only))]
191#[must_use]
192pub fn curve25519_backend() -> Curve25519Backend {
193    #[cfg(all(target_arch = "x86_64", not(any(ic_limb32, ic_ec_portable))))]
194    {
195        if f51x4::Ifma::get().is_some() {
196            return Curve25519Backend::Ifma;
197        }
198        if fe64::available() {
199            return Curve25519Backend::MulxAdx;
200        }
201    }
202    Curve25519Backend::Portable
203}
204
205/// Digest widths ECDSA's `verify_prehash` accepts.
206pub use nist::ecdsa::PREHASH_LENS;
207
208/// The narrowest digest ECDSA's `verify_prehash` accepts on a curve.
209pub use nist::ecdsa::min_prehash_len;
210
211/// Build every precomputed table now, rather than on first use.
212///
213/// Under `std`, the generator tables for P-256, P-384 and P-521 and the
214/// Ed25519 basepoint tables are built the first time an operation needs them:
215/// about one scalar multiplication per curve, a few hundred microseconds in
216/// all on a host. They live in statics, so building them allocates nothing
217/// whenever it happens. What this changes is *when* the time is spent: a
218/// caller that wants its first handshake to cost what every later one does can
219/// pay it at start-up instead.
220///
221/// Safe to call more than once and from several threads: the work is done
222/// once, and a second caller waits for the first. Under `no_std` there are no
223/// tables, and this does nothing.
224pub fn prepare() {
225    use nist::gentable::HasGeneratorTable;
226    p256::P256::prepare_generator_table();
227    p384::P384::prepare_generator_table();
228    p521::P521::prepare_generator_table();
229    #[cfg(all(feature = "std", not(ic_approved_only)))]
230    ed25519::prepare_tables();
231}
232
233/// Ontology identifiers for the schemes implemented here.
234pub const EC_IDS: &[&str] = &[
235    #[cfg(not(ic_approved_only))]
236    "x25519",
237    #[cfg(not(ic_approved_only))]
238    "ed25519",
239    "ecdh-p256",
240    "ecdsa-p256-sha256",
241    "ecdh-p384",
242    "ecdsa-p384-sha384",
243];