gmcrypto_core/lib.rs
1//! Constant-time-designed pure-Rust SM2 / SM3 / SM4 primitives.
2//!
3//! See the workspace `README.md` for scope, threat model, and the honest
4//! framing of the in-CI `dudect`-based timing-leak regression harness.
5//!
6//! # Modules
7//!
8//! - [`sm2`] — SM2 elliptic-curve sign / verify / encrypt / decrypt
9//! (GB/T 32918). Comb-table fixed-base scalar mult (v0.3 W6).
10//! - [`sm3`] — SM3 hash (GB/T 32905) with streaming `new/update/finalize`.
11//! - [`sm4`] — SM4 block cipher (GB/T 32907) + CBC mode (single-shot
12//! and v0.3 W5 streaming). v0.4 W3 adds an opt-in bitsliced
13//! (table-less, gate-only) S-box behind the `sm4-bitsliced` feature.
14//! - [`hmac`] — HMAC-SM3 (RFC 2104), single-shot + v0.3 W5 streaming.
15//! - [`kdf`] — PBKDF2-HMAC-SM3 (RFC 8018 §5.2).
16//! - [`asn1`] — strict-canonical DER reader / writer / OID constants
17//! (v0.3 W1); GM/T 0009 SM2 ciphertext SEQUENCE; RFC 3279 SM2
18//! signature SEQUENCE.
19//! - [`pem`] — RFC 7468 PEM codec (v0.3 W2; hand-rolled, `no_std`).
20//! - [`spki`] — RFC 5280 `SubjectPublicKeyInfo` for SM2 (v0.3 W2).
21//! - [`sec1`] — RFC 5915 `ECPrivateKey` + SEC1 uncompressed point (v0.3 W2).
22//! - [`pkcs8`] — RFC 5958 `OneAsymmetricKey` + RFC 8018 PBES2 (v0.3 W2).
23//! - [`traits`] — in-crate `Hash` / `Mac` / `BlockCipher` traits
24//! (v0.3 W5). v0.4 W2 adds RustCrypto-trait fit (`digest::Digest`,
25//! `digest::Mac`, `cipher::BlockCipherEncrypt`/`BlockCipherDecrypt`)
26//! behind the opt-in `digest-traits` / `cipher-traits` features
27//! (migrated to `digest 0.11` / `cipher 0.5` in v0.11).
28//!
29//! # Crate features
30//!
31//! - `default` — `no_std`, `alloc`-only. No optional dependencies.
32//! - `digest-traits` — opt-in (v0.4 W2). Implements `digest::Digest` for
33//! [`sm3::Sm3`] and `digest::Mac` for [`hmac::HmacSm3`]. Pulls
34//! `digest = "0.11"` — a pre-1.0 ecosystem crate, so a breaking `digest`
35//! release is **not** covered by `gmcrypto-core`'s `SemVer` (bump your own).
36//! - `cipher-traits` — opt-in (v0.4 W2). Implements
37//! `cipher::{BlockCipherEncrypt, BlockCipherDecrypt, BlockSizeUser,
38//! KeySizeUser, KeyInit}` for [`sm4::Sm4Cipher`]. Pulls `cipher = "0.5"` —
39//! a pre-1.0 ecosystem crate, so a breaking `cipher` release is **not**
40//! covered by `gmcrypto-core`'s `SemVer` (bump your own).
41//! - `sm4-bitsliced` — opt-in (v0.4 W3). Routes the SM4 S-box through
42//! a bitsliced (table-less, gate-only) Itoh-Tsujii inversion in
43//! GF(2^8). Byte-identical output to the default linear-scan path;
44//! constant-time by construction (no table lookups, no branches on
45//! secret bits).
46//! - `sm4-bitsliced-simd` — opt-in (v0.5 W4 scaffolding; AVX2 / NEON
47//! intrinsic implementations land in v0.5.x). Implies
48//! `sm4-bitsliced`. Default-off.
49//! - `crypto-bigint-scalar` — opt-in (v0.5 W5). Exposes
50//! [`sm2::Sm2PrivateKey::from_scalar`] which takes a
51//! `crypto_bigint::U256` directly. Default-off; the always-on
52//! `from_bytes_be` constructor is the recommended path for callers
53//! who don't want a transitive `crypto-bigint` dep.
54//!
55//! # `wasm32-unknown-unknown`
56//!
57//! Builds clean as of v0.4 W1. The crate is `no_std + alloc` only and
58//! does NOT pull `getrandom`'s `wasm_js` backend or `wasm-bindgen` /
59//! `js-sys` into its default dep graph. Wasm callers wire their own
60//! `rand_core::Rng` impl — see the workspace `README.md`.
61
62#![no_std]
63#![deny(missing_docs)]
64#![doc(html_root_url = "https://docs.rs/gmcrypto-core/1.0.0")]
65
66extern crate alloc;
67
68pub mod asn1;
69pub mod hmac;
70pub mod kdf;
71pub mod pem;
72pub mod pkcs8;
73pub mod sec1;
74pub mod sm2;
75pub mod sm3;
76pub mod sm4;
77pub mod spki;
78// Not public API / not SemVer — low-level in-crate trait surface kept pub for internal cross-module + dev-crate use; the public trait fit is the opt-in RustCrypto digest/cipher impls.
79#[doc(hidden)]
80pub mod traits;
81
82/// Internal helper: canonical 32-byte big-endian encoding of a `U256`.
83///
84/// `crypto-bigint`'s `Encoding::to_be_bytes` returns an `EncodedUint`
85/// wrapper, not a `[u8; 32]`. v0.22 reshaped the byte-adjacent public
86/// types (`asn1::sig` signatures, `asn1::ciphertext::Sm2Ciphertext`) to
87/// `[u8; 32]` so the public API names no `crypto-bigint` type; this pins
88/// the conversion in one place for the internal producers (sign / encrypt /
89/// raw-ciphertext). Not part of the public API.
90#[inline]
91pub(crate) fn u256_to_be32(v: &crypto_bigint::U256) -> [u8; 32] {
92 v.to_be_bytes().into()
93}
94
95/// Workspace-wide failure type (v0.5 W5).
96///
97/// Every fallible public surface in `gmcrypto-core` that does not
98/// return `Option` / `bool` / `subtle::CtOption` returns
99/// `Result<_, Error>`. The single `Failed` variant is deliberate per
100/// the **failure-mode invariant** (see `SECURITY.md`): distinguishing
101/// failure modes leaks information to padding-oracle / invalid-curve /
102/// password-oracle attackers.
103///
104/// Per-module aliases keep the established import paths working:
105/// `sm2::Error`, `pem::Error`, `pkcs8::Error` are type aliases for
106/// this one type. Prior to v0.5 these were separate per-module enums
107/// (`SignError`, `EncryptError`, `DecryptError`, `pem::Error`,
108/// `pkcs8::Error`) all with a single `Failed` variant; v0.5 unifies
109/// them per Q5.16 in `docs/v0.5-scope.md`.
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111#[non_exhaustive]
112pub enum Error {
113 /// The operation failed. No further information is exposed —
114 /// distinguishing failure modes leaks attacker-useful signal.
115 Failed,
116}
117
118impl core::fmt::Display for Error {
119 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
120 f.write_str("gmcrypto-core operation failed")
121 }
122}
123
124impl core::error::Error for Error {}