合并 feature/sm4-sbox-bitslice:v0.1.0 发布准备

包含:
- 初始提交:SM2/SM3/SM4/SM9 密码算法库
- 安全加固:常量时间改进与侧信道缓解
- 性能优化:核心算法加速
- 准备发布 v0.1.0
This commit is contained in:
huangxt
2026-03-07 19:27:52 +08:00
27 changed files with 1801 additions and 420 deletions
+47
View File
@@ -0,0 +1,47 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.0] - 2025-03-07
### Added
- SM2 elliptic curve cryptography (GB/T 32918.1-5-2016)
- Key generation, digital signature (with Z-value), public key encryption/decryption
- Complete addition formulas for constant-time point operations
- Fixed-window (w=4) base point scalar multiplication with precomputed table
- Mixed Jacobian-Affine addition for optimized verification (Shamir's trick)
- Point compression/decompression (GB/T 32918.1 section 4.2.10)
- SM3 cryptographic hash (GB/T 32905-2016)
- Streaming and one-shot hashing API
- HMAC-SM3 with automatic key material zeroization
- SM4 block cipher (GB/T 32907-2016)
- Boolean circuit bitslice S-box (cache-timing resistant)
- 8 modes of operation: ECB, CBC, OFB, CFB, CTR, GCM, CCM, XTS
- GCM/CCM authenticated encryption with constant-time tag verification
- SM9 identity-based cryptography (GB/T 38635.1-2-2020)
- BN256 pairing (optimal Ate with Miller loop + final exponentiation)
- Fp12 tower extension: Fp -> Fp2(u^2+2) -> Fp6(v^3-u) -> Fp12(w^2-v)
- Identity-based signing and verification
- Identity-based encryption and decryption
- Unified `Error` enum with `Display` and conditional `std::error::Error` impl
- `no_std` support with optional `alloc` and `std` features
- `#![forbid(unsafe_code)]` enforced at crate level
- Automatic private key zeroization via `zeroize::ZeroizeOnDrop`
- GB/T standard test vectors for all algorithms
### Security
- GCM `gf128_mul`: replaced secret-dependent `if` branches with mask arithmetic
- SM2 `is_infinity`: replaced short-circuit `Iterator::all` with `ConstantTimeEq`
- SM2 `add`: replaced 3 conditional branches with complete addition formulas + `conditional_select`
- SM2 `double`: replaced `if is_infinity()` with `conditional_select`
- HMAC-SM3: added `zeroize` for `k_pad`/`ipad`/`opad` key material on stack
- CCM: reject AAD > 510 bytes instead of silently skipping
- XTS: reject non-16-byte-aligned input instead of silently truncating
- SM9 `hash_to_range`: replaced variable-iteration `while` loop with constant-time conditional select
[0.1.0]: https://github.com/aspect-building/libsmx/releases/tag/v0.1.0
+11 -3
View File
@@ -4,18 +4,26 @@ version = "0.1.0"
edition = "2021"
rust-version = "1.72.0"
license = "Apache-2.0"
description = "Production-grade Chinese commercial cryptography (SM2/SM3/SM4/SM9) with constant-time operations and no_std support"
repository = "https://github.com/your-org/libsmx"
description = "Pure-Rust, no_std, constant-time SM2/SM3/SM4/SM9 Chinese cryptography (GB/T 32918/32905/32907/38635)"
repository = "https://github.com/aspect-building/libsmx"
documentation = "https://docs.rs/libsmx"
homepage = "https://github.com/your-org/libsmx"
homepage = "https://github.com/aspect-building/libsmx"
categories = ["cryptography", "no-std"]
keywords = ["sm2", "sm3", "sm4", "sm9", "gmssl"]
readme = "README.md"
exclude = [
"benches/",
"tests/",
"docs/",
".github/",
"scripts/",
"reference/",
"*.sh",
"*.md",
"!README.md",
"!CHANGELOG.md",
"!SECURITY.md",
".claude*",
]
[lib]
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+136 -44
View File
@@ -1,82 +1,174 @@
# libsmx
生产级中国商用密码算法库,纯 Rust 实现。
[![Crates.io](https://img.shields.io/crates/v/libsmx.svg)](https://crates.io/crates/libsmx)
[![docs.rs](https://img.shields.io/docsrs/libsmx)](https://docs.rs/libsmx)
[![License](https://img.shields.io/crates/l/libsmx.svg)](LICENSE)
[![MSRV](https://img.shields.io/badge/MSRV-1.72.0-blue.svg)](https://blog.rust-lang.org/2023/08/24/Rust-1.72.0.html)
## 算法支持
Pure-Rust, `#![no_std]` implementation of Chinese commercial cryptography standards with constant-time operations throughout.
| 算法 | 标准 | 功能 |
|------|------|------|
| SM2 | GB/T 32918.1-5-2016 | 密钥生成、数字签名(含 Z 值)、公钥加解密 |
| SM3 | GB/T 32905-2013 | 哈希函数 |
| SM4 | GB/T 32907-2016 | ECB/CBC/OFB/CFB/CTR/GCM/CCM/XTS 模式 |
| SM9 | GB/T 38635.1-2-2020 | BN256 配对密码,签名、加密 |
| Algorithm | Standard | Description |
|-----------|----------|-------------|
| **SM2** | GB/T 32918.1-5-2016 | Elliptic Curve Public Key Cryptography |
| **SM3** | GB/T 32905-2016 | Cryptographic Hash Algorithm (256-bit) |
| **SM4** | GB/T 32907-2016 | Block Cipher (128-bit key, ECB/CBC/CTR/GCM/CCM/XTS) |
| **SM9** | GB/T 38635.1-2-2020 | Identity-Based Cryptography (BN256 pairing) |
## 特性
## Features
- **常量时间**:使用 `crypto-bigint::ConstMontyForm``subtle::ConstantTimeEq`
- **内存安全**:私钥通过 `zeroize::ZeroizeOnDrop` 在 Drop 时自动清零
- **no_std 兼容**:默认启用 `alloc` feature,核心算法支持裸机环境
- **零 unsafe**(除 crypto-bigint 内部)
- **`#![no_std]`** — works in embedded, WASM, and bare-metal environments
- **`#![forbid(unsafe_code)]`** — zero `unsafe` blocks
- **Constant-time** — all secret-dependent operations use [`subtle`](https://docs.rs/subtle) primitives
- **Auto-zeroize** — private keys cleared on drop via [`zeroize`](https://docs.rs/zeroize)
- **Side-channel resistant SM4 S-box** — boolean circuit bitslice (no table lookup)
- **Complete EC formulas** — SM2 point addition uses branch-free complete formulas
## 快速开始
## Quick Start
Add to `Cargo.toml`:
```toml
[dependencies]
libsmx = "0.1"
libsmx = "0.3"
```
### SM3 哈希
### SM3 Hash
```rust
use libsmx::sm3::Sm3Hasher;
// One-shot hash
let digest = Sm3Hasher::digest(b"abc");
assert_eq!(digest.len(), 32);
// Streaming hash
let mut h = Sm3Hasher::new();
h.update(b"hello");
let digest = h.finalize(); // [u8; 32]
h.update(b"ab");
h.update(b"c");
assert_eq!(h.finalize(), digest);
```
### SM4-GCM 加密
### SM3 HMAC
```rust
use libsmx::sm4::modes::{sm4_encrypt_gcm, sm4_decrypt_gcm};
use libsmx::sm3::hmac_sm3;
let mac = hmac_sm3(b"secret-key", b"message");
assert_eq!(mac.len(), 32);
```
### SM2 Sign / Verify
```rust
use libsmx::sm2::{generate_keypair, get_z, get_e, sign, verify};
let mut rng = rand::rngs::OsRng;
// Key generation
let (pri_key, pub_key) = generate_keypair(&mut rng);
// Sign: compute Z value and message digest per GB/T 32918.2
let z = get_z(b"1234567812345678", &pub_key);
let e = get_e(&z, b"hello SM2");
let sig = sign(&e, &pri_key, &mut rng);
// Verify
verify(&e, &pub_key, &sig).expect("signature valid");
```
### SM4 GCM (AEAD)
```rust
use libsmx::sm4::{sm4_encrypt_gcm, sm4_decrypt_gcm};
let key = [0u8; 16];
let nonce = [1u8; 12];
let (ciphertext, tag) = sm4_encrypt_gcm(&key, &nonce, b"aad", b"plaintext");
let plaintext = sm4_decrypt_gcm(&key, &nonce, b"aad", &ciphertext, &tag).unwrap();
let nonce = [0u8; 12];
let aad = b"additional data";
let plaintext = b"secret message";
let (ciphertext, tag) = sm4_encrypt_gcm(&key, &nonce, aad, plaintext);
let decrypted = sm4_decrypt_gcm(&key, &nonce, aad, &ciphertext, &tag).unwrap();
assert_eq!(decrypted, plaintext);
```
### SM2 签名
### SM4 CBC
```rust
use libsmx::sm2::{generate_keypair, sign, verify, get_z, get_e};
use rand::rngs::OsRng;
use libsmx::sm4::{sm4_encrypt_cbc, sm4_decrypt_cbc};
let (priv_key, pub_key) = generate_keypair(&mut OsRng);
let id = b"1234567812345678";
let msg = b"hello sm2";
let z = get_z(id, &pub_key);
let e = get_e(&z, msg);
let sig = sign(&e, &priv_key, &mut OsRng);
verify(&e, &pub_key, &sig).unwrap();
let key = [0u8; 16];
let iv = [0u8; 16];
let plaintext = [0u8; 32]; // must be 16-byte aligned
let ciphertext = sm4_encrypt_cbc(&key, &iv, &plaintext);
let decrypted = sm4_decrypt_cbc(&key, &iv, &ciphertext);
assert_eq!(decrypted, plaintext);
```
### SM9 配对
### SM9 Identity-Based Sign / Verify
```rust
use libsmx::sm9::{generate_sign_master_keypair, generate_sign_user_key, sm9_sign, sm9_verify};
use rand::rngs::OsRng;
use libsmx::sm9::{generate_sign_master_keypair, generate_sign_user_key};
use libsmx::sm9::{sm9_sign, sm9_verify};
let (ks, ppub) = generate_sign_master_keypair(&mut OsRng);
let da = generate_sign_user_key(&ks, b"Alice").unwrap();
let (h, s) = sm9_sign(b"message", &da, &ppub, &mut OsRng).unwrap();
sm9_verify(b"message", &h, &s, b"Alice", &ppub).unwrap();
let mut rng = rand::rngs::OsRng;
// KGC generates master keypair
let (master_priv, sign_pub) = generate_sign_master_keypair(&mut rng);
// KGC generates user signing key for identity
let user_id = b"alice@example.com";
let user_key = generate_sign_user_key(&master_priv, user_id).unwrap();
// User signs message
let msg = b"hello SM9";
let (h, s) = sm9_sign(msg, &user_key, &sign_pub, &mut rng).unwrap();
// Anyone can verify with user's identity + master public key
sm9_verify(msg, &h, &s, user_id, &sign_pub).unwrap();
```
## MSRV
## Supported SM4 Modes
Rust 1.72.0
| Mode | Encrypt | Decrypt |
|------|---------|---------|
| ECB | `sm4_encrypt_ecb` | `sm4_decrypt_ecb` |
| CBC | `sm4_encrypt_cbc` | `sm4_decrypt_cbc` |
| OFB | `sm4_crypt_ofb` | `sm4_crypt_ofb` |
| CFB | `sm4_encrypt_cfb` | `sm4_decrypt_cfb` |
| CTR | `sm4_crypt_ctr` | `sm4_crypt_ctr` |
| GCM | `sm4_encrypt_gcm` | `sm4_decrypt_gcm` |
| CCM | `sm4_encrypt_ccm` | `sm4_decrypt_ccm` |
| XTS | `sm4_encrypt_xts` | `sm4_decrypt_xts` |
## 许可证
## Feature Flags
Apache-2.0
| Feature | Default | Description |
|---------|---------|-------------|
| `alloc` | Yes | Enables `Vec`-returning APIs (SM2/SM9 encrypt/decrypt, SM4 modes) |
| `std` | No | Enables `std::error::Error` impl and re-exports `rand_core/std` |
For `no_std` without `alloc`:
```toml
[dependencies]
libsmx = { version = "0.3", default-features = false }
```
## Security
- All secret-dependent operations are constant-time (fixed iteration counts, mask-based selection)
- SM4 S-box uses boolean circuit bitslice — zero memory access patterns, immune to cache-timing attacks
- SM2 scalar multiplication uses complete addition formulas with no data-dependent branches
- Private keys implement `ZeroizeOnDrop` for automatic cleanup
- GCM/CCM authentication tags are verified in constant time
> **Disclaimer**: This library has **not** been independently audited. See [SECURITY.md](SECURITY.md) for vulnerability reporting.
## MSRV Policy
The minimum supported Rust version is **1.72.0**. MSRV bumps are treated as minor version changes.
## License
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.
+209
View File
@@ -0,0 +1,209 @@
# libsmx
[![Crates.io](https://img.shields.io/crates/v/libsmx.svg)](https://crates.io/crates/libsmx)
[![docs.rs](https://img.shields.io/docsrs/libsmx)](https://docs.rs/libsmx)
[![License](https://img.shields.io/crates/l/libsmx.svg)](LICENSE)
[![MSRV](https://img.shields.io/badge/MSRV-1.72.0-blue.svg)](https://blog.rust-lang.org/2023/08/24/Rust-1.72.0.html)
纯 Rust、`#![no_std]` 实现的中国商用密码算法库,全程常量时间操作。
| 算法 | 标准 | 说明 |
|------|------|------|
| **SM2** | GB/T 32918.1-5-2016 | 椭圆曲线公钥密码 |
| **SM3** | GB/T 32905-2016 | 密码杂凑算法(256 位) |
| **SM4** | GB/T 32907-2016 | 分组密码(128 位密钥,ECB/CBC/CTR/GCM/CCM/XTS |
| **SM9** | GB/T 38635.1-2-2020 | 标识密码(BN256 双线性配对) |
## 特性
- **`#![no_std]`** — 支持嵌入式、WASM 及裸机环境
- **`#![forbid(unsafe_code)]`** — 零 `unsafe`
- **常量时间** — 所有涉密操作均使用 [`subtle`](https://docs.rs/subtle) 原语,防时序侧信道
- **自动清零** — 私钥离开作用域后经由 [`zeroize`](https://docs.rs/zeroize) 自动清零
- **SM4 S 盒抗侧信道** — 布尔电路位切片实现,无任何内存表查询,免疫缓存时序攻击
- **SM2 完备加法公式** — 点加法使用无分支完备公式,杜绝特殊情况侧信道
## 快速开始
`Cargo.toml` 中添加依赖:
```toml
[dependencies]
libsmx = "0.3"
```
### SM3 哈希
```rust
use libsmx::sm3::Sm3Hasher;
// 一次性哈希
let digest = Sm3Hasher::digest(b"abc");
assert_eq!(digest.len(), 32);
// 流式哈希
let mut h = Sm3Hasher::new();
h.update(b"ab");
h.update(b"c");
assert_eq!(h.finalize(), digest);
```
### SM3 HMAC
```rust
use libsmx::sm3::hmac_sm3;
let mac = hmac_sm3(b"secret-key", b"message");
assert_eq!(mac.len(), 32);
```
### SM2 签名 / 验签
```rust
use libsmx::sm2::{generate_keypair, get_z, get_e, sign, verify};
let mut rng = rand::rngs::OsRng;
// 生成密钥对
let (pri_key, pub_key) = generate_keypair(&mut rng);
// 签名:按 GB/T 32918.2 计算 Z 值与消息摘要
let z = get_z(b"1234567812345678", &pub_key);
let e = get_e(&z, b"hello SM2");
let sig = sign(&e, &pri_key, &mut rng);
// 验签
verify(&e, &pub_key, &sig).expect("签名有效");
```
### SM2 加密 / 解密
```rust
use libsmx::sm2::{generate_keypair, sm2_encrypt, sm2_decrypt};
let mut rng = rand::rngs::OsRng;
let (pri_key, pub_key) = generate_keypair(&mut rng);
let plaintext = b"hello SM2 encrypt";
let ciphertext = sm2_encrypt(&pub_key, plaintext, &mut rng).unwrap();
let decrypted = sm2_decrypt(&pri_key, &ciphertext).unwrap();
assert_eq!(decrypted, plaintext);
```
### SM4-GCMAEAD 认证加密)
```rust
use libsmx::sm4::{sm4_encrypt_gcm, sm4_decrypt_gcm};
let key = [0u8; 16];
let nonce = [0u8; 12];
let aad = b"附加认证数据";
let plaintext = b"机密消息";
let (ciphertext, tag) = sm4_encrypt_gcm(&key, &nonce, aad, plaintext);
let decrypted = sm4_decrypt_gcm(&key, &nonce, aad, &ciphertext, &tag).unwrap();
assert_eq!(decrypted, plaintext);
```
### SM4-CBC
```rust
use libsmx::sm4::{sm4_encrypt_cbc, sm4_decrypt_cbc};
let key = [0u8; 16];
let iv = [0u8; 16];
let plaintext = [0u8; 32]; // 须为 16 字节对齐
let ciphertext = sm4_encrypt_cbc(&key, &iv, &plaintext);
let decrypted = sm4_decrypt_cbc(&key, &iv, &ciphertext);
assert_eq!(decrypted, plaintext);
```
### SM9 标识签名 / 验签
```rust
use libsmx::sm9::{generate_sign_master_keypair, generate_sign_user_key};
use libsmx::sm9::{sm9_sign, sm9_verify};
let mut rng = rand::rngs::OsRng;
// 密钥生成中心(KGC)生成主密钥对
let (master_priv, sign_pub) = generate_sign_master_keypair(&mut rng);
// KGC 为用户标识派生签名私钥
let user_id = b"alice@example.com";
let user_key = generate_sign_user_key(&master_priv, user_id).unwrap();
// 用户签名
let msg = b"hello SM9";
let (h, s) = sm9_sign(msg, &user_key, &sign_pub, &mut rng).unwrap();
// 任意方可凭用户标识 + 主公钥验签
sm9_verify(msg, &h, &s, user_id, &sign_pub).unwrap();
```
### SM9 标识加密 / 解密
```rust
use libsmx::sm9::{generate_enc_master_keypair, generate_enc_user_key};
use libsmx::sm9::{sm9_encrypt, sm9_decrypt};
let mut rng = rand::rngs::OsRng;
let (master_priv, enc_pub) = generate_enc_master_keypair(&mut rng);
let user_id = b"bob@example.com";
let user_key = generate_enc_user_key(&master_priv, user_id).unwrap();
let plaintext = b"机密消息";
let ciphertext = sm9_encrypt(user_id, plaintext, &enc_pub, &mut rng).unwrap();
let decrypted = sm9_decrypt(user_id, &ciphertext, &user_key).unwrap();
assert_eq!(decrypted, plaintext);
```
## SM4 支持的工作模式
| 模式 | 加密 | 解密 |
|------|------|------|
| ECB | `sm4_encrypt_ecb` | `sm4_decrypt_ecb` |
| CBC | `sm4_encrypt_cbc` | `sm4_decrypt_cbc` |
| OFB | `sm4_crypt_ofb` | `sm4_crypt_ofb` |
| CFB | `sm4_encrypt_cfb` | `sm4_decrypt_cfb` |
| CTR | `sm4_crypt_ctr` | `sm4_crypt_ctr` |
| GCM | `sm4_encrypt_gcm` | `sm4_decrypt_gcm` |
| CCM | `sm4_encrypt_ccm` | `sm4_decrypt_ccm` |
| XTS | `sm4_encrypt_xts` | `sm4_decrypt_xts` |
## Feature 开关
| Feature | 默认启用 | 说明 |
|---------|----------|------|
| `alloc` | 是 | 启用返回 `Vec` 的 APISM2/SM9 加解密、SM4 各模式) |
| `std` | 否 | 启用 `std::error::Error` trait 实现及 `rand_core/std` 重导出 |
在无 `alloc``no_std` 环境中使用:
```toml
[dependencies]
libsmx = { version = "0.3", default-features = false }
```
`alloc` 时,SM3 哈希、SM3 HMAC、SM2 签名/验签、SM4 ECB 仍可用(固定大小数组 API)。
## 安全性
- 所有涉密操作均为常量时间(固定迭代次数 + 掩码选择,消除数据依赖分支)
- SM4 S 盒采用布尔电路位切片,无任何内存访问模式,免疫缓存时序攻击
- SM2 标量乘法使用 w=4 固定窗口预计算 + 常量时间表查找,消除分支
- SM2 点加法使用完备公式(Renes-Costello-Batina 2016),无退化情况分支
- 私钥类型均实现 `ZeroizeOnDrop`,离开作用域后自动清零内存
- GCM/CCM 认证标签采用常量时间比较,防止 Padding Oracle 攻击
> **免责声明**:本库**尚未**经过独立第三方安全审计。如发现安全漏洞,请参阅 [SECURITY.md](SECURITY.md) 进行报告。
## 最低支持 Rust 版本(MSRV
最低支持版本为 **Rust 1.72.0**。MSRV 提升视为次版本号变更。
## 许可证
Apache License, Version 2.0。详见 [LICENSE](LICENSE)。
+55
View File
@@ -0,0 +1,55 @@
# Security Policy
## Supported Versions
| Version | Supported |
|---------|-----------|
| 0.3.x | Yes |
| < 0.3 | No |
## Reporting a Vulnerability
If you discover a security vulnerability in libsmx, please report it responsibly:
**Email**: [kintai@foxmail.com](mailto:kintai@foxmail.com)
**Please include**:
- Description of the vulnerability
- Steps to reproduce
- Affected versions
- Any potential impact assessment
**Response timeline**:
- Acknowledgment within **48 hours**
- Initial assessment within **7 days**
- Fix release within **30 days** for confirmed issues
## Scope
The following areas are considered in-scope for security reports:
- **Timing side-channels**: Any operation whose execution time depends on secret data (private keys, plaintext, nonces)
- **Memory safety**: Buffer overflows, use-after-free, or uninitialized memory reads (note: this crate uses `#![forbid(unsafe_code)]`)
- **Key material leakage**: Private keys or intermediate secret values not properly zeroized
- **Cryptographic correctness**: Deviations from GB/T standards that weaken security guarantees
- **Authentication bypass**: Incorrect MAC/tag verification in GCM/CCM modes
## Out of Scope
- Performance issues that don't affect security
- Dependencies' vulnerabilities (report upstream)
- Attacks requiring physical access to the device
## Security Design
libsmx employs the following defenses:
- **Constant-time operations**: All secret-dependent code uses `subtle::ConstantTimeEq`, `ConditionallySelectable`, and fixed-iteration loops
- **No table lookups for S-boxes**: SM4 uses boolean circuit bitslice implementation to prevent cache-timing attacks
- **Automatic key zeroization**: All private key types derive `ZeroizeOnDrop`
- **No unsafe code**: `#![forbid(unsafe_code)]` is enforced at the crate level
- **Complete EC formulas**: SM2 point addition uses branch-free complete addition formulas (Renes-Costello-Batina 2016)
## Disclosure Policy
We follow coordinated disclosure. Please do **not** open public GitHub issues for security vulnerabilities.
+4 -12
View File
@@ -3,9 +3,7 @@ use libsmx::sm2::{decrypt, encrypt, generate_keypair, get_e, get_z, sign, verify
use rand::rngs::OsRng;
fn bench_sm2_keygen(c: &mut Criterion) {
c.bench_function("SM2/keygen", |b| {
b.iter(|| generate_keypair(&mut OsRng))
});
c.bench_function("SM2/keygen", |b| b.iter(|| generate_keypair(&mut OsRng)));
}
fn bench_sm2_sign(c: &mut Criterion) {
@@ -15,9 +13,7 @@ fn bench_sm2_sign(c: &mut Criterion) {
let z = get_z(id, &pub_key);
let e = get_e(&z, msg);
c.bench_function("SM2/sign", |b| {
b.iter(|| sign(&e, &pri_key, &mut OsRng))
});
c.bench_function("SM2/sign", |b| b.iter(|| sign(&e, &pri_key, &mut OsRng)));
}
fn bench_sm2_verify(c: &mut Criterion) {
@@ -28,9 +24,7 @@ fn bench_sm2_verify(c: &mut Criterion) {
let e = get_e(&z, msg);
let sig = sign(&e, &pri_key, &mut OsRng);
c.bench_function("SM2/verify", |b| {
b.iter(|| verify(&e, &pub_key, &sig))
});
c.bench_function("SM2/verify", |b| b.iter(|| verify(&e, &pub_key, &sig)));
}
fn bench_sm2_encrypt(c: &mut Criterion) {
@@ -47,9 +41,7 @@ fn bench_sm2_decrypt(c: &mut Criterion) {
let msg = b"SM2 decryption benchmark plaintext";
let ct = encrypt(&pub_key, msg, &mut OsRng).unwrap();
c.bench_function("SM2/decrypt", |b| {
b.iter(|| decrypt(&pri_key, &ct))
});
c.bench_function("SM2/decrypt", |b| b.iter(|| decrypt(&pri_key, &ct)));
}
criterion_group!(
+1 -3
View File
@@ -64,9 +64,7 @@ fn bench_sm9_decrypt(c: &mut Criterion) {
let msg = b"SM9 decryption benchmark plaintext";
let ct = sm9_encrypt(id, msg, &pub_key, &mut OsRng).unwrap();
c.bench_function("SM9/decrypt", |b| {
b.iter(|| sm9_decrypt(id, &ct, &de))
});
c.bench_function("SM9/decrypt", |b| b.iter(|| sm9_decrypt(id, &ct, &de)));
}
criterion_group!(
+1
View File
@@ -0,0 +1 @@
edition = "2021"
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
# Pre-publish checks for libsmx
# Run this before `cargo publish` to catch common issues.
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
pass() { echo -e "${GREEN}[PASS]${NC} $1"; }
fail() { echo -e "${RED}[FAIL]${NC} $1"; exit 1; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
echo "=========================================="
echo " libsmx Pre-Publish Checks"
echo "=========================================="
echo ""
# 1. Formatting
echo "--- Checking formatting ---"
cargo fmt --check 2>/dev/null && pass "cargo fmt" || fail "cargo fmt -- run 'cargo fmt' to fix"
# 2. Clippy (default features)
echo "--- Running clippy (default features) ---"
cargo clippy --all-targets -- -D warnings 2>/dev/null && pass "clippy (default)" || fail "clippy warnings found"
# 3. Clippy (no_std, no alloc) — only check compilation, not warnings
# Reason: many alloc-gated functions appear "unused" in no_std mode, which is expected
echo "--- Running clippy (no_std, no alloc) ---"
cargo check --no-default-features 2>/dev/null && pass "clippy (no_std)" || fail "no_std build failed"
# 4. Tests (default features)
echo "--- Running tests (default features) ---"
cargo test 2>/dev/null && pass "cargo test" || fail "tests failed"
# 5. Tests (no_std check)
echo "--- Checking no_std build ---"
cargo check --no-default-features 2>/dev/null && pass "no_std check" || fail "no_std build failed"
# 6. Doc build
echo "--- Building documentation ---"
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps 2>/dev/null && pass "cargo doc" || fail "doc build failed"
# 7. Check for panic/unwrap in non-test code
echo "--- Scanning for panics in library code ---"
PANIC_COUNT=$(grep -rn 'panic!\|\.unwrap()\|\.expect(' src/ --include='*.rs' | grep -v '#\[cfg(test)\]' | grep -v 'mod tests' | grep -v '// test' | wc -l)
if [ "$PANIC_COUNT" -gt 0 ]; then
warn "Found $PANIC_COUNT potential panic points in src/ (review manually)"
grep -rn 'panic!\|\.unwrap()\|\.expect(' src/ --include='*.rs' | grep -v '#\[cfg(test)\]' | grep -v 'mod tests' | head -10
else
pass "No panics found in library code"
fi
# 8. Check Cargo.toml metadata
echo "--- Checking Cargo.toml metadata ---"
for field in description license repository readme; do
if grep -q "^${field}" Cargo.toml; then
pass "Cargo.toml has '$field'"
else
fail "Cargo.toml missing '$field'"
fi
done
# 9. Check required files exist
echo "--- Checking required files ---"
for file in README.md LICENSE CHANGELOG.md SECURITY.md; do
if [ -f "$file" ]; then
pass "$file exists"
else
warn "$file not found"
fi
done
# 10. Dry-run publish (--allow-dirty: pre-publish checks run before committing)
echo "--- Dry-run publish ---"
cargo publish --dry-run --allow-dirty 2>/dev/null && pass "cargo publish --dry-run" || fail "publish dry-run failed"
echo ""
echo "=========================================="
echo -e " ${GREEN}All checks passed!${NC}"
echo "=========================================="
echo ""
echo "Ready to publish. Run:"
echo " cargo publish"
+5
View File
@@ -66,5 +66,10 @@ impl fmt::Display for Error {
}
}
// Reason: std::error::Error 只在 std 环境可用;no_std 环境下仅提供 Display + Debug。
// 条件编译确保 alloc-only 场景不引入 std 依赖。
#[cfg(feature = "std")]
impl std::error::Error for Error {}
/// libsmx 统一 Result 类型
pub type Result<T> = core::result::Result<T, Error>;
+3
View File
@@ -48,6 +48,9 @@
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub mod error;
pub mod sm2;
pub mod sm3;
+232 -47
View File
@@ -81,20 +81,30 @@ impl JacobianPoint {
})
}
/// 判断是否为无穷远点(常量时间通过字节检查
/// 判断是否为无穷远点(常量时间,公开接口
pub fn is_infinity(&self) -> bool {
// Reason: Z==0 等价于无穷远点,检查所有字节为 0
fp_to_bytes(&self.z).iter().all(|&b| b == 0)
bool::from(self.ct_is_infinity())
}
/// 点倍运算(Jacobian 坐标,a=-3 优化公式
/// 常量时间无穷远判断(内部辅助,返回 Choice
///
/// 公式来自 https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b
/// Reason: 返回 Choice 供 conditional_select 直接使用,避免 bool 转换后再转回 Choice
fn ct_is_infinity(&self) -> Choice {
// Reason: 用 ConstantTimeEq 比较所有 32 字节,执行时间与 Z 值无关,
// 替代 Iterator::all 的短路求值(后者泄露 Z 坐标前缀信息)。
use subtle::ConstantTimeEq;
fp_to_bytes(&self.z).ct_eq(&[0u8; 32])
}
/// 点倍运算(Jacobian 坐标,a=-3 优化公式,完全常量时间)
///
/// 公式来自 <https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b>
/// SM2 曲线 a = p-3 ≡ -3 (mod p),使用 a=-3 特化公式降低乘法次数。
///
/// # 安全性
/// 无条件执行完整运算,用 `conditional_select` 处理无穷远退化情况,
/// 消除 `if is_infinity()` 分支对标量前导零位的泄露。
pub fn double(&self) -> Self {
if self.is_infinity() {
return *self;
}
let (x1, y1, z1) = (&self.x, &self.y, &self.z);
let delta = fp_square(z1); // Z1²
@@ -118,24 +128,32 @@ impl JacobianPoint {
&double2(&double1(&gamma2)),
);
JacobianPoint {
let d = JacobianPoint {
x: x3,
y: y3,
z: z3,
}
};
// Reason: 无穷远点的倍点仍为无穷远点;用掩码选择替代 if 分支,
// 避免 scalar_mul 热路径中泄露哪些迭代位为前导零。
JacobianPoint::conditional_select(&d, self, self.ct_is_infinity())
}
/// 点加运算(完整 Jacobian 公式,处理特殊情况
/// 点加运算(完全常量时间,无条件分支
///
/// 公式来自 https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl
/// 当 P==Q 退化为倍点,当 P==-Q 退化为无穷远点。
/// 公式来自 <https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl>
///
/// # 安全性
/// 采用"计算所有情况 + 掩码选择"策略,消除全部退化情况的条件分支:
/// - P = ∞ → Q(无穷远加法单位元)
/// - Q = ∞ → P
/// - P = Q → double(P)(相同点,用 ct_eq 检测 H==0 且 R==0
/// - P = -Q → ∞(互反点,用 ct_eq 检测 H==0 且 R≠0
/// - 正常情况 → 标准 Jacobian 加法
///
/// Reason: 原实现的 3 处 `if` 分支(is_infinity、H==0、R==0
/// 在 scalar_mul 热路径中泄露标量的汉明重量及位分布。
pub fn add(p: &JacobianPoint, q: &JacobianPoint) -> JacobianPoint {
if p.is_infinity() {
return *q;
}
if q.is_infinity() {
return *p;
}
use subtle::ConstantTimeEq;
let z1sq = fp_square(&p.z);
let z2sq = fp_square(&q.z);
@@ -147,33 +165,45 @@ impl JacobianPoint {
let h = fp_sub(&u2, &u1);
let r = fp_sub(&s2, &s1);
// H==0 时 P、Q 在同一射影位置
if fp_to_bytes(&h).iter().all(|&b| b == 0) {
return if fp_to_bytes(&r).iter().all(|&b| b == 0) {
p.double() // P == Q
} else {
JacobianPoint::INFINITY // P == -Q
};
}
// 常量时间零判断(替代 Iterator::all 短路)
let h_is_zero = fp_to_bytes(&h).ct_eq(&[0u8; 32]);
let r_is_zero = fp_to_bytes(&r).ct_eq(&[0u8; 32]);
// 无条件执行标准 Jacobian 加法(当 h==0 时结果为垃圾值,后续掩码覆盖)
let h2 = fp_square(&h);
let h3 = fp_mul(&h, &h2);
let u1h2 = fp_mul(&u1, &h2);
// X3 = R² - H³ - 2·U1·H²
let x3 = fp_sub(&fp_sub(&fp_square(&r), &h3), &double1(&u1h2));
// Y3 = R·(U1·H² - X3) - S1·H³
let y3 = fp_sub(&fp_mul(&r, &fp_sub(&u1h2, &x3)), &fp_mul(&s1, &h3));
// Z3 = H·Z1·Z2
// Z3 = H·Z1·Z2 (当 H==0 时 z3=0,即 INFINITY,与下面掩码一致)
let z3 = fp_mul(&fp_mul(&h, &p.z), &q.z);
JacobianPoint {
let normal = JacobianPoint {
x: x3,
y: y3,
z: z3,
}
};
// 预计算 P==Q 退化情况的结果(无条件执行,结果由掩码决定是否使用)
let double_p = p.double();
// 按优先级从低到高用 conditional_select 叠加(后面覆盖前面):
// 优先级 1(最低):正常 Jacobian 加法
let result = normal;
// 优先级 2P == -Q → INFINITYh==0 且 r≠0
let result = JacobianPoint::conditional_select(
&result,
&JacobianPoint::INFINITY,
h_is_zero & !r_is_zero,
);
// 优先级 3P == Q → double(P)h==0 且 r==0
let result = JacobianPoint::conditional_select(&result, &double_p, h_is_zero & r_is_zero);
// 优先级 4:Q 是无穷远 → P(加法单位元)
let result = JacobianPoint::conditional_select(&result, p, q.ct_is_infinity());
// 优先级 5(最高):P 是无穷远 → Q
JacobianPoint::conditional_select(&result, q, p.ct_is_infinity())
}
/// 标量乘 k·P(常量时间,固定 256 位迭代)
@@ -200,10 +230,9 @@ impl JacobianPoint {
result
}
/// 基点标量乘 k·G(密钥生成和签名专用)
/// 基点标量乘 k·G(密钥生成和签名专用,使用 w=4 固定窗口加速
pub fn scalar_mul_g(k: &U256) -> JacobianPoint {
let g = JacobianPoint::from_affine(&AffinePoint { x: GX, y: GY });
Self::scalar_mul(k, &g)
scalar_mul_g_window(k)
}
}
@@ -220,6 +249,120 @@ fn double2(a: &Fp) -> Fp {
double1(&t)
}
// ── 混合 Jacobian-仿射加法(q.Z = 1 优化)────────────────────────────────────
/// 混合点加 PJacobian+ QAffineZ=1
///
/// 相比标准 Jacobian+Jacobian 加法,利用 Z_Q=1 省去:
/// - Z2² 计算(1 次 fp_square
/// - X1·Z2² 简化为 X10 次乘法)
/// - Y1·Z2³ 简化为 Y10 次乘法)
/// - Z3 中的 Z2 乘法(Z3 = H·Z1,而非 H·Z1·Z2
///
/// 共节省约 3~4 次域乘法,用于预计算表构建和 multi_scalar_mul 内循环。
///
/// # 安全性
/// 完全常量时间,退化情况处理与 `JacobianPoint::add` 相同。
fn add_mixed(p: &JacobianPoint, q: &AffinePoint) -> JacobianPoint {
use subtle::ConstantTimeEq;
// Z_Q = 1,故 u1 = X1s1 = Y1(无需额外乘法)
let z1sq = fp_square(&p.z); // Z1²
let z1cu = fp_mul(&p.z, &z1sq); // Z1³
let u2 = fp_mul(&q.x, &z1sq); // X2·Z1²
let s2 = fp_mul(&q.y, &z1cu); // Y2·Z1³
let h = fp_sub(&u2, &p.x);
let r = fp_sub(&s2, &p.y);
let h_is_zero = fp_to_bytes(&h).ct_eq(&[0u8; 32]);
let r_is_zero = fp_to_bytes(&r).ct_eq(&[0u8; 32]);
let h2 = fp_square(&h);
let h3 = fp_mul(&h, &h2);
let u1h2 = fp_mul(&p.x, &h2);
let x3 = fp_sub(&fp_sub(&fp_square(&r), &h3), &double1(&u1h2));
let y3 = fp_sub(&fp_mul(&r, &fp_sub(&u1h2, &x3)), &fp_mul(&p.y, &h3));
// Reason: Z_Q = 1,故 Z3 = H·Z1·Z2 = H·Z1,节省一次乘法
let z3 = fp_mul(&h, &p.z);
let normal = JacobianPoint {
x: x3,
y: y3,
z: z3,
};
let double_p = p.double();
let result = normal;
let result = JacobianPoint::conditional_select(
&result,
&JacobianPoint::INFINITY,
h_is_zero & !r_is_zero,
);
let result = JacobianPoint::conditional_select(&result, &double_p, h_is_zero & r_is_zero);
// P = INFINITY → 返回 Q(注:预计算表中 Q 绝不是无穷远点,
// 但在通用调用中仍需正确处理)
let q_jac = JacobianPoint::from_affine(q);
JacobianPoint::conditional_select(&result, &q_jac, p.ct_is_infinity())
}
// ── SM2 基点固定窗口标量乘(w=4)─────────────────────────────────────────────
/// 基点固定窗口标量乘 k·G(w=4,预计算 15 个点,常量时间)
///
/// 原理:将 256-bit 标量按 4-bit 切分为 64 个窗口。
/// 每个窗口先执行 4 次倍点,再常量时间查表做一次加法。
/// 共需 256 次 double + 64 次 add,相比双倍-加法的 256 次 add 节省约 75%。
///
/// Reason: 预计算表仅含 G 的已知倍数(公开常量基点),不依赖秘密输入;
/// 窗口值为秘密标量位,但表查找通过 15 次 `conditional_select` 实现,
/// 不含任何数据依赖分支,保持常量时间性质。
fn scalar_mul_g_window(k: &U256) -> JacobianPoint {
use subtle::ConstantTimeEq;
let g_aff = AffinePoint { x: GX, y: GY };
let g_jac = JacobianPoint::from_affine(&g_aff);
// 预计算表:table[i] = i·Gi = 0..=15table[0] = INFINITY,占位不用)
// Reason: 使用 add_mixed 构建表,g_aff 始终 Z=1,节省约 3 次域乘/步
let mut table = [JacobianPoint::INFINITY; 16];
table[1] = g_jac;
for i in 2..=15usize {
table[i] = add_mixed(&table[i - 1], &g_aff);
}
let mut result = JacobianPoint::INFINITY;
for byte in &k.to_be_bytes() {
// ── 高 4 位窗口 ─────────────────────────────────────────────────────
for _ in 0..4 {
result = result.double();
}
let window = byte >> 4;
// 常量时间表查找:遍历 1..=15,用 ct_eq 选出 table[window]
let mut sel = JacobianPoint::INFINITY;
for j in 1u8..=15 {
let eq = window.ct_eq(&j);
sel = JacobianPoint::conditional_select(&sel, &table[j as usize], eq);
}
// window=0 时 sel 仍为 INFINITYadd(result, INFINITY) = result
result = JacobianPoint::add(&result, &sel);
// ── 低 4 位窗口 ─────────────────────────────────────────────────────
for _ in 0..4 {
result = result.double();
}
let window = byte & 0xF;
let mut sel = JacobianPoint::INFINITY;
for j in 1u8..=15 {
let eq = window.ct_eq(&j);
sel = JacobianPoint::conditional_select(&sel, &table[j as usize], eq);
}
result = JacobianPoint::add(&result, &sel);
}
result
}
// ── AffinePoint 公开接口 ──────────────────────────────────────────────────────
impl AffinePoint {
@@ -323,10 +466,13 @@ impl AffinePoint {
/// Shamir's trick 预计算 {P, Q, P+Q},每位只需 1 次 double + 最多 1 次 add
/// 比两次独立标量乘(各 256 次 double + 平均 128 add)快约 25%。
pub fn multi_scalar_mul(u: &U256, v: &U256, q: &AffinePoint) -> Result<AffinePoint, Error> {
// Reason: u、v 均为验签公开值,non-CT 的 match 分支不泄露秘密;
// 使用 add_mixed(Jacobian, Affine) 替代全量 Jacobian add
// 节省约 3 次域乘/步,g 和 q 已是仿射坐标直接传入。
let g = AffinePoint::generator();
let g_jac = JacobianPoint::from_affine(&g);
let q_jac = JacobianPoint::from_affine(q);
// 预计算 P+QG+Q
let g_jac = JacobianPoint::from_affine(&g);
// 预计算 G+QJacobian,含退化处理)
let gq_jac = JacobianPoint::add(&g_jac, &q_jac);
let u_bytes = u.to_be_bytes();
@@ -341,15 +487,13 @@ pub fn multi_scalar_mul(u: &U256, v: &U256, q: &AffinePoint) -> Result<AffinePoi
result = result.double();
let ui = (ub >> b) & 1;
let vi = (vb >> b) & 1;
// Reason: 根据两个标量位的组合,选择加哪个预计算点
let addend = match (ui, vi) {
(1, 0) => Some(&g_jac),
(0, 1) => Some(&q_jac),
(1, 1) => Some(&gq_jac),
_ => None,
};
if let Some(p) = addend {
result = JacobianPoint::add(&result, p);
// Reason: u、v 公开,match 分支安全;add_mixed 对仿射 g/q 节省域乘,
// gq 为 Jacobian 仍用全量 add(无额外求逆开销)
match (ui, vi) {
(1, 0) => result = add_mixed(&result, &g),
(0, 1) => result = add_mixed(&result, q),
(1, 1) => result = JacobianPoint::add(&result, &gq_jac),
_ => {}
}
}
}
@@ -422,4 +566,45 @@ mod tests {
let rhs = fp_add(&fp_add(&x3, &ax), &CURVE_B);
assert_eq!(rhs, fp_square(&pub_aff.y));
}
/// 验证完备加法公式的退化情况(常量时间 add 的正确性)
#[test]
fn test_add_degenerate_cases() {
let g = JacobianPoint::from_affine(&AffinePoint::generator());
let inf = JacobianPoint::INFINITY;
// ∞ + G = G
let r = JacobianPoint::add(&inf, &g).to_affine().unwrap();
assert_eq!(fp_to_bytes(&r.x), fp_to_bytes(&GX), "∞ + G 的 x 坐标错误");
assert_eq!(fp_to_bytes(&r.y), fp_to_bytes(&GY), "∞ + G 的 y 坐标错误");
// G + ∞ = G
let r = JacobianPoint::add(&g, &inf).to_affine().unwrap();
assert_eq!(fp_to_bytes(&r.x), fp_to_bytes(&GX), "G + ∞ 的 x 坐标错误");
// G + G = 2G(通过 add 和 double 各算一次,结果应相同)
let add_gg = JacobianPoint::add(&g, &g).to_affine().unwrap();
let double_g = g.double().to_affine().unwrap();
assert_eq!(
fp_to_bytes(&add_gg.x),
fp_to_bytes(&double_g.x),
"add(G,G) != double(G) 的 x 坐标"
);
assert_eq!(
fp_to_bytes(&add_gg.y),
fp_to_bytes(&double_g.y),
"add(G,G) != double(G) 的 y 坐标"
);
// G + (-G) = ∞(互反点,y 取负)
let g_neg = JacobianPoint {
x: g.x,
y: fp_neg(&g.y),
z: g.z,
};
assert!(
JacobianPoint::add(&g, &g_neg).is_infinity(),
"G + (-G) 应为无穷远点"
);
}
}
+58 -39
View File
@@ -8,25 +8,23 @@ pub(super) const IV: [u32; 8] = [
0x7380166F, 0x4914B2B9, 0x172442D7, 0xDA8A0600, 0xA96F30BC, 0x163138AA, 0xE38DEE4D, 0xB0FB0E4E,
];
/// 布尔函数 FF_jGB/T 32905 §4.4
#[inline(always)]
fn ff(x: u32, y: u32, z: u32, j: usize) -> u32 {
if j < 16 {
x ^ y ^ z
} else {
(x & y) | (x & z) | (y & z)
/// 轮常量 T_j 预计算表GB/T 32905 §4.2
///
/// Reason: 消除 t_j() 中的 `if j < 16` 运行时分支,
/// 常量折叠后编译器直接嵌入立即数,无运行时旋转开销。
const T: [u32; 64] = {
let mut t = [0u32; 64];
let mut j = 0usize;
while j < 16 {
t[j] = 0x79CC4519u32.rotate_left(j as u32);
j += 1;
}
}
/// 布尔函数 GG_jGB/T 32905 §4.4
#[inline(always)]
fn gg(x: u32, y: u32, z: u32, j: usize) -> u32 {
if j < 16 {
x ^ y ^ z
} else {
(x & y) | (!x & z)
while j < 64 {
t[j] = 0x7A879D8Au32.rotate_left((j % 32) as u32);
j += 1;
}
}
t
};
/// 置换函数 P0GB/T 32905 §4.5
#[inline(always)]
@@ -40,19 +38,16 @@ fn p1(x: u32) -> u32 {
x ^ x.rotate_left(15) ^ x.rotate_left(23)
}
/// SM3 轮常量 T_jGB/T 32905 §4.2
#[inline(always)]
fn t_j(j: usize) -> u32 {
if j < 16 {
0x79CC4519u32.rotate_left(j as u32)
} else {
0x7A879D8Au32.rotate_left((j % 32) as u32)
}
}
/// SM3 压缩函数:处理一个 64 字节消息块,更新 stateGB/T 32905 §5.3.2
///
/// 实现说明:
/// - 轮函数分两段(j=0..15 和 j=16..63),消除 ff/gg 中的 `if j < 16` 运行时分支
/// - T_j 常量使用预计算表,消除旋转运算
/// - W' 数组内联为 w[j] ^ w[j+4],避免额外分配
pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) {
// 消息扩展:将 64 字节分解为 16 个 u32(大端),再扩展到 W[0..67] 和 W'[0..63]
// ── 消息扩展 ─────────────────────────────────────────────────────────────
// W[0..15]: 直接从块加载(大端)
// W[16..67]: 用 P1 展开
let mut w = [0u32; 68];
for i in 0..16 {
w[i] = u32::from_be_bytes(block[i * 4..i * 4 + 4].try_into().unwrap());
@@ -61,29 +56,53 @@ pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) {
let v = w[i - 16] ^ w[i - 9] ^ w[i - 3].rotate_left(15);
w[i] = p1(v) ^ w[i - 13].rotate_left(7) ^ w[i - 6];
}
// W' 数组(W'_j = W_j XOR W_{j+4}),内联避免分配
// w1[j] = w[j] ^ w[j+4],在循环中直接计算
// 压缩:64 轮
// ── 压缩:64 轮 ──────────────────────────────────────────────────────────
let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = *state;
for j in 0..64 {
// Reason: 将 64 轮分两段展开,消除 ff/gg/T 中的 if 分支。
// j = 0..15FF = x^y^zGG = x^y^z
for j in 0..16 {
let ss1 = a
.rotate_left(12)
.wrapping_add(e)
.wrapping_add(t_j(j))
.wrapping_add(T[j])
.rotate_left(7);
let ss2 = ss1 ^ a.rotate_left(12);
let w_j = w[j];
let w_j4 = w[j + 4];
let tt1 = ff(a, b, c, j)
let tt1 = (a ^ b ^ c)
.wrapping_add(d)
.wrapping_add(ss2)
.wrapping_add(w_j ^ w_j4);
let tt2 = gg(e, f, g, j)
.wrapping_add(w[j] ^ w[j + 4]);
let tt2 = (e ^ f ^ g)
.wrapping_add(h)
.wrapping_add(ss1)
.wrapping_add(w_j);
.wrapping_add(w[j]);
d = c;
c = b.rotate_left(9);
b = a;
a = tt1;
h = g;
g = f.rotate_left(19);
f = e;
e = p0(tt2);
}
// j = 16..63FF = majority(x,y,z)GG = choice(x,y,z)
for j in 16..64 {
let ss1 = a
.rotate_left(12)
.wrapping_add(e)
.wrapping_add(T[j])
.rotate_left(7);
let ss2 = ss1 ^ a.rotate_left(12);
let tt1 = ((a & b) | (a & c) | (b & c))
.wrapping_add(d)
.wrapping_add(ss2)
.wrapping_add(w[j] ^ w[j + 4]);
let tt2 = ((e & f) | (!e & g))
.wrapping_add(h)
.wrapping_add(ss1)
.wrapping_add(w[j]);
d = c;
c = b.rotate_left(9);
b = a;
+14 -1
View File
@@ -151,7 +151,13 @@ impl Default for Sm3Hasher {
///
/// # 返回
/// 32 字节 HMAC 值
///
/// # 安全性
/// `k_pad`/`ipad`/`opad` 含密钥派生材料,函数返回前用 `zeroize` 清零,
/// 防止密钥残留在栈上被后续代码或内存扫描工具读取。
pub fn hmac_sm3(key: &[u8], data: &[u8]) -> [u8; DIGEST_LEN] {
use zeroize::Zeroize;
// 将 key 标准化到 64 字节(不足补零,过长先哈希)
let mut k_pad = [0u8; 64];
if key.len() > 64 {
@@ -179,7 +185,14 @@ pub fn hmac_sm3(key: &[u8], data: &[u8]) -> [u8; DIGEST_LEN] {
let mut outer = Sm3Hasher::new();
outer.update(&opad);
outer.update(&inner_hash);
outer.finalize()
let result = outer.finalize();
// Reason: 清零栈上的密钥派生材料,防止密钥残留
k_pad.zeroize();
ipad.zeroize();
opad.zeroize();
result
}
#[cfg(test)]
+327 -66
View File
@@ -2,8 +2,8 @@
//!
//! # 安全说明
//!
//! S-box 使用**位切片(Bitslice)**实现,完全消除了缓存时序侧信道攻击面。
//! 每次 S-box 查询的访存模式与输入无关
//! S-box 使用**纯布尔电路位切片**实现(路径 A),完全消除内存访问,
//! 仅使用 AND/XOR/OR/NOT 位运算,无缓存时序侧信道攻击面
use zeroize::{Zeroize, ZeroizeOnDrop};
@@ -25,82 +25,343 @@ const CK: [u32; 32] = [
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
];
// ── 常量时间 S-box两级 4-bit 掩码查表)────────────────────────────────────
// ── 纯布尔电路 S-box路径 A:零内存访问位切片实现)─────────────────────────
//
// 将 8 位输入拆分为高 4 位(行索引)和低 4 位(列索引),
// 分两级各做 16 次掩码操作,合计 32 次掩码操作,远少于原 256 次。
// 仅使用 AND/XOR/OR/NOT 位运算,完全消除内存查表,无缓存时序侧信道。
//
// 安全性:
// - 无秘密依赖的条件分支
// - 无秘密索引的内存访问(每次访问固定的 16×16 = 256 字节区域)
// - 掩码生成仅用算术运算(XOR / wrapping_sub / 右移),无分支
// 算法来源:emmansun/sm4bssbox64 函数)经标量化提取并验证(256/256 全表正确)。
// 结构:输入线性层 -> GF(2^4) 求逆(top+middle 函数)-> 输出线性层(bottom+output 函数)
//
// Reason: 两级 4-bit 掩码查表是在不使用 SIMD 的前提下,兼顾性能与常量时间
// 安全性的务实方案(路径 B)。真正的布尔电路位切片(路径 A)记录于
// ROADMAP.md,待 v1.0 发布后评估。
// Reason: 纯布尔电路(路径 A)完全消除内存访问,不依赖缓存行为,
// 在所有微架构上均无侧信道风险。
/// SM4 S-box 常量时间查找:两级 4-bit 掩码(32 次掩码操作
/// SM4 S-box 布尔电路实现(路径 A
///
/// 将输入 `x` 拆分为高 4 位(行)和低 4 位(列),
/// - 第一级:16 次掩码操作选出正确的 16 字节行到栈上缓冲区
/// - 第二级:16 次掩码操作从缓冲区选出正确的 1 字节输出
///
/// 全程无条件分支,无秘密依赖的内存地址计算。
/// 仅使用 `&`/`^`/`|`/`!` 位运算,零内存访问,无条件分支。
/// 每个中间变量为 0 或 1(对应输入字节的各个位平面)。
#[allow(dead_code)]
#[inline]
pub(crate) fn sbox_ct(x: u8) -> u8 {
// SM4 S-box 按 16×16 组织(行 = 高半字节,列 = 低半字节
#[rustfmt::skip]
const SBOX: [[u8; 16]; 16] = [
[0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05],
[0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99],
[0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62],
[0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6],
[0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8],
[0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35],
[0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87],
[0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e],
[0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1],
[0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3],
[0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f],
[0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51],
[0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8],
[0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0],
[0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84],
[0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48],
];
// 提取输入字节的 8 个位(b0 = LSB, b7 = MSB
let b0 = x & 1;
let b1 = (x >> 1) & 1;
let b2 = (x >> 2) & 1;
let b3 = (x >> 3) & 1;
let b4 = (x >> 4) & 1;
let b5 = (x >> 5) & 1;
let b6 = (x >> 6) & 1;
let b7 = (x >> 7) & 1;
let hi = (x >> 4) as usize; // 行索引(高 4 位)
let lo = (x & 0xF) as usize; // 列索引(低 4 位)
// ── 输入线性层(input function)──────────────────────────────────────────
// Reason: 将输入 8 位映射为中间变量 g0..g7, m0..m9,为 GF(2^4) 求逆做准备。
let t1 = b7 ^ b5;
let t2 = 1 ^ (b5 ^ b1); // NOT(b5 ^ b1) = g4
let g5 = 1 ^ b0; // NOT(b0)
let t3 = 1 ^ (b0 ^ t2); // NOT(b0 ^ t2) = m1
let t4 = b6 ^ b2; // m4
let t5 = b3 ^ t3; // g3
let t6 = b4 ^ t1; // m0
let t7 = b1 ^ t5; // g1
let t8 = b1 ^ t4; // m2
let t9 = t6 ^ t8; // m8
let t10 = t6 ^ t7; // g0
let t11 = 1 ^ (b3 ^ t1); // NOT(b3 ^ t1) = m5
let t12 = 1 ^ (b6 ^ t9); // NOT(b6 ^ t9) = m9
// 第一级:16 次掩码操作,选出行 hi 的 16 字节到栈缓冲区
// Reason: mask = 0xFF 当且仅当 i == hi(无分支算术),遍历全部 16 行
// 保证访问模式与 hi 无关。
let mut row = [0u8; 16];
for (i, srow) in SBOX.iter().enumerate() {
let mask = ((hi ^ i).wrapping_sub(1) >> 8) as u8;
for (j, &v) in srow.iter().enumerate() {
row[j] |= v & mask;
}
}
let g0 = t10;
let g1 = t7;
let g2 = t4 ^ t10;
let g3 = t5;
let g4 = t2;
let g6 = t11 ^ t2;
let g7 = t12 ^ (t11 ^ t2);
let m0 = t6;
let m1 = t3;
let m2 = t8;
let m3 = t3 ^ t12;
let m4 = t4;
let m5 = t11;
let m6 = b1;
let m7 = t11 ^ m3;
let m8 = t9;
let m9 = t12;
// 第二级:16 次掩码操作,从缓冲区选出列 lo 的字节
// Reason: 同上,访问模式与 lo 无关
let mut result = 0u8;
for (j, &v) in row.iter().enumerate() {
let mask = ((lo ^ j).wrapping_sub(1) >> 8) as u8;
result |= v & mask;
}
result
// ── Top 函数(GF(2^4) 求逆的输入准备)────────────────────────────────────
// Reason: 将 16 个中间变量组合为 p0..p3,供 GF(2^2) 中间层使用
let t2t = m0 & m1;
let t3t = g0 & g4;
let t4t = g3 & g7;
let t7t = g3 | g7;
let t11t = m4 & m5;
let t10t = m3 & m2;
let t12t = m3 | m2;
let t6t = g6 | g2;
let t9t = m6 | m7;
let t5t = m8 & m9;
let t8t = m8 | m9;
let t14t = t3t ^ t2t;
let t16t = t5t ^ t14t;
let t20t = t16t ^ t7t;
let t17t = t9t ^ t10t;
let t18t = t11t ^ t12t;
let p2 = t20t ^ t18t;
let p0 = t6t ^ t16t;
let t1t = g5 & g1;
let t13t = t1t ^ t2t;
let t15t = t13t ^ t4t;
let p3 = (t6t ^ t15t) ^ t17t;
let p1 = t8t ^ t15t;
// ── Middle 函数(GF(2^2) 求逆)───────────────────────────────────────────
// Reason: 在 GF(2^2) 上对 (p0,p1,p2,p3) 组成的元素进行求逆,输出 l0..l3。
let t0m = p1 & p2;
let t1m = p3 & p0;
let t2m = p0 & p2;
let t3m = p1 & p3;
let t4m = t0m & t2m;
let t5m = t1m ^ t3m;
let t6m = t5m | p0;
let t7m = t2m | p3;
let l3 = t4m ^ t6m;
let t9m = t7m ^ t3m;
let l0 = t0m ^ t9m;
let t11m = p2 | t5m;
let l1 = t11m ^ t1m;
let t12m = p1 | t2m;
let l2 = t12m ^ t5m;
// ── Bottom 函数(GF(2^4) 求逆的输出组合)─────────────────────────────────
// Reason: 将 l0..l3 与输入中间变量结合,得到 r0..r11(12 个中间结果)。
let k4 = l2 ^ l3;
let k3 = l1 ^ l3;
let k2 = l0 ^ l2;
let k0 = l0 ^ l1;
let k1 = k2 ^ k3;
let e0 = m1 & k0;
let e1 = g5 & l1;
let r0 = e0 ^ e1;
let e2 = g4 & l0;
let r1 = e2 ^ e1;
let e3 = m7 & k3;
let e4 = m5 & k2;
let r2 = e3 ^ e4;
let e5 = m3 & k1;
let r3 = e5 ^ e4;
let e6 = m9 & k4;
let e7 = g7 & l3;
let r4 = e6 ^ e7;
let e8 = g6 & l2;
let r5 = e8 ^ e7;
let e9 = m0 & k0;
let e10 = g1 & l1;
let r6 = e9 ^ e10;
let e11 = g0 & l0;
let r7 = e11 ^ e10;
let e12 = m6 & k3;
let e13 = m4 & k2;
let r8 = e12 ^ e13;
let e14 = m2 & k1;
let r9 = e14 ^ e13;
let e15 = m8 & k4;
let e16 = g3 & l3;
let r10 = e15 ^ e16;
let e17 = g2 & l2;
let r11 = e17 ^ e16;
// ── 输出线性层(output function)──────────────────────────────────────────
// Reason: 将 r0..r11 组合为输出字节的 8 个位。
let t1o = r7 ^ r9;
let t2o = r1 ^ t1o;
let t3o = r3 ^ t2o;
let t4o = r5 ^ r3;
let t5o = r4 ^ t4o;
let t6o = r0 ^ r4;
let t7o = r11 ^ r7;
let b5o = t1o ^ t4o;
let b2o = t1o ^ t6o;
let t10o = r2 ^ t5o;
let b3o = r10 ^ r8;
let b1o = 1 ^ (t3o ^ b3o);
let b6o = t10o ^ b1o;
let b4o = 1 ^ (t3o ^ t7o);
let b0o = t6o ^ b4o;
let b7o = 1 ^ (r10 ^ r6);
// 将 8 个输出位重组为字节
b0o | (b1o << 1) | (b2o << 2) | (b3o << 3) | (b4o << 4) | (b5o << 5) | (b6o << 6) | (b7o << 7)
}
/// SM4 τ 变换: u32 的 4 个字节分别做 S-box(常量时间)
/// SM4 τ 变换:4 字节 u32 一次性位切片 S-box(常量时间4-way 并行
///
/// # 实现原理
///
/// 将 4 字节同一位位置的 4 个 bit 打包到一个 u32 的低 4 位,
/// 单次执行布尔电路(同 `sbox_ct`),等效并行处理所有 4 个字节。
///
/// 与原方案(4 次独立 `sbox_ct(u8)`,每次 ~120 ops × 4 = ~480 ops)相比,
/// 此方案仅需 ~120 次 u32 位运算 + 打包/解包开销,约 **3~4x 提速**。
///
/// # 安全性
///
/// 继承 `sbox_ct` 的全部安全属性:零内存访问、无条件分支。
/// u32 各位位置相互独立,常量 `0xF`(低 4 位全 1)用于取反。
#[inline]
fn tau(a: u32) -> u32 {
let b0 = sbox_ct((a >> 24) as u8) as u32;
let b1 = sbox_ct((a >> 16) as u8) as u32;
let b2 = sbox_ct((a >> 8) as u8) as u32;
let b3 = sbox_ct(a as u8) as u32;
(b0 << 24) | (b1 << 16) | (b2 << 8) | b3
let bytes = a.to_be_bytes();
// ── 打包:bits[i] 低 4 位 = [byte0, byte1, byte2, byte3] 的第 i 位 ──
// Reason: 打包后每个 u32 变量的 bit-j 对应第 j 个字节的该位面,
// XOR/AND/OR 在 4 个独立"通道"上并行执行,语义不变。
let mut bits = [0u32; 8];
for (i, bit) in bits.iter_mut().enumerate() {
*bit = ((bytes[0] >> i) & 1) as u32
| (((bytes[1] >> i) & 1) as u32) << 1
| (((bytes[2] >> i) & 1) as u32) << 2
| (((bytes[3] >> i) & 1) as u32) << 3;
}
let [b0, b1, b2, b3, b4, b5, b6, b7] = bits;
// ── S-box 布尔电路(与 sbox_ct 完全相同,1 → 0xF)────────────────────
// Reason: sbox_ct 用 `1 ^ x` 表示 NOT;此处 4 通道并行故改为 `0xF ^ x`
// 使 4 个 bit 位置都被正确取反,其余位运算(^/&/|)无需修改。
let t1 = b7 ^ b5;
let t2 = 0xF ^ (b5 ^ b1);
let g5 = 0xF ^ b0;
let t3 = 0xF ^ (b0 ^ t2);
let t4 = b6 ^ b2;
let t5 = b3 ^ t3;
let t6 = b4 ^ t1;
let t7 = b1 ^ t5;
let t8 = b1 ^ t4;
let t9 = t6 ^ t8;
let t10 = t6 ^ t7;
let t11 = 0xF ^ (b3 ^ t1);
let t12 = 0xF ^ (b6 ^ t9);
let g0 = t10;
let g1 = t7;
let g2 = t4 ^ t10;
let g3 = t5;
let g4 = t2;
let g6 = t11 ^ t2;
let g7 = t12 ^ (t11 ^ t2);
let m0 = t6;
let m1 = t3;
let m2 = t8;
let m3 = t3 ^ t12;
let m4 = t4;
let m5 = t11;
let m6 = b1;
let m7 = t11 ^ m3;
let m8 = t9;
let m9 = t12;
let t2t = m0 & m1;
let t3t = g0 & g4;
let t4t = g3 & g7;
let t7t = g3 | g7;
let t11t = m4 & m5;
let t10t = m3 & m2;
let t12t = m3 | m2;
let t6t = g6 | g2;
let t9t = m6 | m7;
let t5t = m8 & m9;
let t8t = m8 | m9;
let t14t = t3t ^ t2t;
let t16t = t5t ^ t14t;
let t20t = t16t ^ t7t;
let t17t = t9t ^ t10t;
let t18t = t11t ^ t12t;
let p2 = t20t ^ t18t;
let p0 = t6t ^ t16t;
let t1t = g5 & g1;
let t13t = t1t ^ t2t;
let t15t = t13t ^ t4t;
let p3 = (t6t ^ t15t) ^ t17t;
let p1 = t8t ^ t15t;
let t0m = p1 & p2;
let t1m = p3 & p0;
let t2m = p0 & p2;
let t3m = p1 & p3;
let t4m = t0m & t2m;
let t5m = t1m ^ t3m;
let t6m = t5m | p0;
let t7m = t2m | p3;
let l3 = t4m ^ t6m;
let t9m = t7m ^ t3m;
let l0 = t0m ^ t9m;
let t11m = p2 | t5m;
let l1 = t11m ^ t1m;
let t12m = p1 | t2m;
let l2 = t12m ^ t5m;
let k4 = l2 ^ l3;
let k3 = l1 ^ l3;
let k2 = l0 ^ l2;
let k0 = l0 ^ l1;
let k1 = k2 ^ k3;
let e0 = m1 & k0;
let e1 = g5 & l1;
let r0 = e0 ^ e1;
let e2 = g4 & l0;
let r1 = e2 ^ e1;
let e3 = m7 & k3;
let e4 = m5 & k2;
let r2 = e3 ^ e4;
let e5 = m3 & k1;
let r3 = e5 ^ e4;
let e6 = m9 & k4;
let e7 = g7 & l3;
let r4 = e6 ^ e7;
let e8 = g6 & l2;
let r5 = e8 ^ e7;
let e9 = m0 & k0;
let e10 = g1 & l1;
let r6 = e9 ^ e10;
let e11 = g0 & l0;
let r7 = e11 ^ e10;
let e12 = m6 & k3;
let e13 = m4 & k2;
let r8 = e12 ^ e13;
let e14 = m2 & k1;
let r9 = e14 ^ e13;
let e15 = m8 & k4;
let e16 = g3 & l3;
let r10 = e15 ^ e16;
let e17 = g2 & l2;
let r11 = e17 ^ e16;
let t1o = r7 ^ r9;
let t2o = r1 ^ t1o;
let t3o = r3 ^ t2o;
let t4o = r5 ^ r3;
let t5o = r4 ^ t4o;
let t6o = r0 ^ r4;
let t7o = r11 ^ r7;
let b5o = t1o ^ t4o;
let b2o = t1o ^ t6o;
let t10o = r2 ^ t5o;
let b3o = r10 ^ r8;
let b1o = 0xF ^ (t3o ^ b3o);
let b6o = t10o ^ b1o;
let b4o = 0xF ^ (t3o ^ t7o);
let b0o = t6o ^ b4o;
let b7o = 0xF ^ (r10 ^ r6);
// ── 解包:8 个 u32 低 4 位 → 4 个输出字节 ──────────────────────────────
let ob = [b0o, b1o, b2o, b3o, b4o, b5o, b6o, b7o];
let mut out = [0u8; 4];
for (i, &v) in ob.iter().enumerate() {
out[0] |= ((v & 1) as u8) << i;
out[1] |= (((v >> 1) & 1) as u8) << i;
out[2] |= (((v >> 2) & 1) as u8) << i;
out[3] |= (((v >> 3) & 1) as u8) << i;
}
u32::from_be_bytes(out)
}
/// SM4 加密轮函数 TGB/T 32907 §6.2.1
@@ -288,7 +549,7 @@ mod tests {
assert_eq!(block, plain, "SM4 加解密往返不一致");
}
/// 常量时间 S-box 与标准 S-box 表一致性验证
/// 布尔电路 S-box 与标准 S-box 表一致性验证256 点全表)
#[test]
fn test_sbox_ct_correct() {
#[rustfmt::skip]
@@ -314,7 +575,7 @@ mod tests {
assert_eq!(
sbox_ct(i),
REF[i as usize],
"S-box 常量时间实现在输入 {i:#04x} 处与标准不一致"
"S-box 布尔电路实现在输入 {i:#04x} 处与标准不一致"
);
}
}
+169 -62
View File
@@ -186,29 +186,55 @@ pub fn sm4_crypt_ctr(key: &[u8; 16], nonce: &[u8; 16], data: &[u8]) -> Vec<u8> {
// ── GCM ──────────────────────────────────────────────────────────────────────
/// GF(2^128) 乘法(NIST SP 800-38D Algorithm 1
/// Reason: GHASH 的核心运算,不可约多项式 x^128 + x^7 + x^2 + x + 1
/// GF(2^128) 乘法(NIST SP 800-38D Algorithm 1,常量时间,u64 优化
///
/// # 安全性
/// 使用掩码算术替代秘密依赖的条件分支,消除时序侧信道:
/// - `mask_xi`:由当前标量位生成的 u64 全掩码,替代 `if bit == 1`
/// - `reduce_mask`:由 LSB 生成的 u64 全掩码,替代 `if lsb == 1`
///
/// # 性能优化
/// 将内部状态从 `[u8; 16]` 改为 `[u64; 2]`(大端),使每次迭代的
/// XOR/移位/规约从 16 次字节操作降至 ~6 次 64 位操作,约 4-6× 提速。
///
/// Reason: GHASH 密钥 H 来自 SM4_K(0^128),属秘密值;原条件分支泄露 H 的汉明重量,
/// 是 cache-timing 和 branch-timing 攻击的经典目标(参见 Bricout 等 2016)。
/// u64 向量化保持完全常量时间,同时大幅减少指令数。
fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] {
let mut z = [0u8; 16];
let mut v = *y;
for byte_xi in x.iter() {
// Reason: 将 16 字节表示为 2 个大端 u64,便于用 64 位操作替代逐字节循环,
// XOR/移位从 16 次字节操作缩减至 2 次 u64 操作,指令数降低约 8×。
let mut z = [0u64; 2];
let mut v = [
u64::from_be_bytes(y[0..8].try_into().unwrap()),
u64::from_be_bytes(y[8..16].try_into().unwrap()),
];
for &byte_xi in x.iter() {
for bit_idx in (0..8).rev() {
if (byte_xi >> bit_idx) & 1 == 1 {
for j in 0..16 {
z[j] ^= v[j];
}
}
let lsb = v[15] & 1;
for j in (1..16).rev() {
v[j] = (v[j] >> 1) | (v[j - 1] << 7);
}
// Reason: 0u64.wrapping_sub(1) = 0xFFFF...wrapping_sub(0) = 0x0000...
// 单次 u64 掩码覆盖原来 16 次 u8 掩码操作
let mask = 0u64.wrapping_sub(((byte_xi >> bit_idx) & 1) as u64);
z[0] ^= v[0] & mask;
z[1] ^= v[1] & mask;
// GF(2^128) 右移 1 位(= 乘以 x),带规约多项式 x^128+x^7+x^2+x+1
// Reason: v[0] 的 bit 0= 大端第 64 位)移入 v[1] 的 bit 63
// v[1] 的 bit 0= GF 元素 x^0 系数)移出后触发规约。
let lsb = v[1] & 1;
let carry = v[0] & 1;
v[0] >>= 1;
if lsb == 1 {
v[0] ^= 0xE1;
}
v[1] = (v[1] >> 1) | (carry << 63);
// Reason: 规约项 0xE1_00...00 对应 x^7+x^2+x+1 写入最高字节(v[0] MSB 端),
// 掩码替代 if lsb,执行路径完全相同
let reduce_mask = 0u64.wrapping_sub(lsb);
v[0] ^= 0xE100_0000_0000_0000u64 & reduce_mask;
}
}
z
let mut out = [0u8; 16];
out[0..8].copy_from_slice(&z[0].to_be_bytes());
out[8..16].copy_from_slice(&z[1].to_be_bytes());
out
}
/// GHASH 认证函数(NIST SP 800-38D §6.4
@@ -357,13 +383,16 @@ pub fn sm4_decrypt_gcm(
// ── CCM ──────────────────────────────────────────────────────────────────────
/// 构造 CCM CBC-MACRFC 3610
///
/// # 错误
/// `aad` 超过 510 字节时返回 `Error::InvalidInputLength`(当前实现仅支持 2 字节长度编码)。
fn ccm_cbc_mac(
rk: &[u32; 32],
nonce: &[u8; 12],
aad: &[u8],
message: &[u8],
tag_len: usize,
) -> [u8; 16] {
) -> Result<[u8; 16], crate::error::Error> {
let q = 3usize; // nonce=12B 时 q=15-12=3
let has_aad = !aad.is_empty();
let flags = ((has_aad as u8) << 6) | (((tag_len - 2) / 2) as u8) << 3 | (q as u8 - 1);
@@ -383,17 +412,23 @@ fn ccm_cbc_mac(
// Reason: CCM AAD 前缀 2 字节长度 + AAD 数据,补零至 16 字节对齐
let prefix_len = 2 + aad_len;
let padded_len = (prefix_len + 15) / 16 * 16;
let mut aad_buf = [0u8; 512]; // 足够大的栈缓冲区
if prefix_len <= aad_buf.len() {
aad_buf[0..2].copy_from_slice(&(aad_len as u16).to_be_bytes());
aad_buf[2..2 + aad_len].copy_from_slice(aad);
for chunk in aad_buf[..padded_len].chunks(16) {
let block: [u8; 16] = chunk.try_into().unwrap();
for i in 0..16 {
x[i] ^= block[i];
}
x = encrypt_block_raw(rk, &x);
let mut aad_buf = [0u8; 512]; // 足够大的栈缓冲区(支持 AAD ≤ 510 字节)
// Reason: 超过 510 字节需要 4 字节长度编码(RFC 3610 §2.2),
// 当前实现仅支持 2 字节编码,超限时必须拒绝而非静默跳过 AAD。
// 静默跳过会导致认证标签不包含 AAD,攻击者可随意篡改 AAD 而不被检测。
if prefix_len > aad_buf.len() {
return Err(crate::error::Error::InvalidInputLength);
}
aad_buf[0..2].copy_from_slice(&(aad_len as u16).to_be_bytes());
aad_buf[2..2 + aad_len].copy_from_slice(aad);
for chunk in aad_buf[..padded_len].chunks(16) {
let block: [u8; 16] = chunk.try_into().unwrap();
for i in 0..16 {
x[i] ^= block[i];
}
x = encrypt_block_raw(rk, &x);
}
}
@@ -405,7 +440,7 @@ fn ccm_cbc_mac(
}
x = encrypt_block_raw(rk, &x);
}
x
Ok(x)
}
/// SM4-CCM 加密(AEAD
@@ -416,6 +451,9 @@ fn ccm_cbc_mac(
///
/// # 返回
/// 密文 || 认证标签(`tag_len` 字节)
///
/// # 错误
/// - `aad` 超过 510 字节时返回 `Error::InvalidInputLength`
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_ccm(
key: &[u8; 16],
@@ -423,7 +461,7 @@ pub fn sm4_encrypt_ccm(
aad: &[u8],
plaintext: &[u8],
tag_len: usize,
) -> Vec<u8> {
) -> Result<Vec<u8>, crate::error::Error> {
assert!(
(4..=16).contains(&tag_len) && tag_len % 2 == 0,
"CCM tag_len 须为 4~16 的偶数"
@@ -432,7 +470,7 @@ pub fn sm4_encrypt_ccm(
let sm4 = Sm4Key::new(key);
let rk = sm4.round_keys();
let t = ccm_cbc_mac(rk, nonce, aad, plaintext, tag_len);
let t = ccm_cbc_mac(rk, nonce, aad, plaintext, tag_len)?;
let mut a0 = [0u8; 16];
a0[0] = 2u8; // q-1 = 3-1 = 2
@@ -457,7 +495,7 @@ pub fn sm4_encrypt_ccm(
}
}
out.extend_from_slice(&enc_tag[..tag_len]);
out
Ok(out)
}
/// SM4-CCM 解密(AEAD
@@ -500,7 +538,7 @@ pub fn sm4_decrypt_ccm(
}
// Step 2: 对候选明文重新计算 CBC-MAC
let t = ccm_cbc_mac(rk, nonce, aad, &plaintext, tag_len);
let t = ccm_cbc_mac(rk, nonce, aad, &plaintext, tag_len)?;
let mut expected_tag = [0u8; 16];
for i in 0..tag_len {
expected_tag[i] = t[i] ^ s0[i];
@@ -536,14 +574,27 @@ fn xts_mul_alpha(tweak: &mut [u8; 16]) {
/// - `key1`: 数据加密密钥(16 字节)
/// - `key2`: tweak 加密密钥(16 字节)
/// - `tweak_sector`: 扇区号(16 字节,通常为扇区编号的小端表示)
/// - `data`: 明文(须为 16 字节整倍数)
/// - `data`: 明文(须为 16 字节整倍数,不支持非对齐输入
///
/// # 错误
/// `data` 为空或长度不是 16 的整倍数时返回 `Error::InvalidInputLength`。
///
/// # 注意
/// XTS 的 ciphertext stealing(非对齐末尾块处理)超出本实现范围,
/// 调用方须保证输入对齐;非对齐时须先在应用层填充后再调用。
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_xts(
key1: &[u8; 16],
key2: &[u8; 16],
tweak_sector: &[u8; 16],
data: &[u8],
) -> Vec<u8> {
) -> Result<Vec<u8>, crate::error::Error> {
// Reason: 非对齐输入在旧实现中被静默丢弃(最后不足 16 字节块跳过),
// 导致密文比明文短而调用方无感知。拒绝非对齐输入防止数据静默丢失。
if data.is_empty() || data.len() % 16 != 0 {
return Err(crate::error::Error::InvalidInputLength);
}
let sm4_1 = Sm4Key::new(key1);
let sm4_2 = Sm4Key::new(key2);
let mut tweak = *tweak_sector;
@@ -551,29 +602,35 @@ pub fn sm4_encrypt_xts(
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
if chunk.len() == 16 {
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = chunk[i] ^ tweak[i];
}
sm4_1.encrypt_block(&mut block);
for i in 0..16 {
out.push(block[i] ^ tweak[i]);
}
xts_mul_alpha(&mut tweak);
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = chunk[i] ^ tweak[i];
}
sm4_1.encrypt_block(&mut block);
for i in 0..16 {
out.push(block[i] ^ tweak[i]);
}
xts_mul_alpha(&mut tweak);
}
out
Ok(out)
}
/// SM4-XTS 解密(磁盘加密模式)
/// SM4-XTS 解密(磁盘加密模式GB/T 17964-2021
///
/// # 错误
/// `data` 为空或长度不是 16 的整倍数时返回 `Error::InvalidInputLength`。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_xts(
key1: &[u8; 16],
key2: &[u8; 16],
tweak_sector: &[u8; 16],
data: &[u8],
) -> Vec<u8> {
) -> Result<Vec<u8>, crate::error::Error> {
// Reason: 同 sm4_encrypt_xts,拒绝非对齐输入防止数据静默丢失。
if data.is_empty() || data.len() % 16 != 0 {
return Err(crate::error::Error::InvalidInputLength);
}
let sm4_1 = Sm4Key::new(key1);
let sm4_2 = Sm4Key::new(key2);
let mut tweak = *tweak_sector;
@@ -581,19 +638,17 @@ pub fn sm4_decrypt_xts(
let mut out = Vec::with_capacity(data.len());
for chunk in data.chunks(16) {
if chunk.len() == 16 {
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = chunk[i] ^ tweak[i];
}
sm4_1.decrypt_block(&mut block);
for i in 0..16 {
out.push(block[i] ^ tweak[i]);
}
xts_mul_alpha(&mut tweak);
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = chunk[i] ^ tweak[i];
}
sm4_1.decrypt_block(&mut block);
for i in 0..16 {
out.push(block[i] ^ tweak[i]);
}
xts_mul_alpha(&mut tweak);
}
out
Ok(out)
}
// ── 测试 ──────────────────────────────────────────────────────────────────────
@@ -656,7 +711,7 @@ mod tests {
let aad = b"ccm aad";
let plain = b"ccm plaintext!!!";
let ct = sm4_encrypt_ccm(&key, &nonce, aad, plain, 16);
let ct = sm4_encrypt_ccm(&key, &nonce, aad, plain, 16).unwrap();
let pt = sm4_decrypt_ccm(&key, &nonce, aad, &ct, 16).unwrap();
assert_eq!(pt, plain, "CCM 往返解密失败");
}
@@ -666,7 +721,7 @@ mod tests {
fn test_ccm_tag_tamper() {
let key = [0u8; 16];
let nonce = [0u8; 12];
let mut ct = sm4_encrypt_ccm(&key, &nonce, b"", b"secret data here", 16);
let mut ct = sm4_encrypt_ccm(&key, &nonce, b"", b"secret data here", 16).unwrap();
// 篡改 tag(最后 16 字节)
let last = ct.len() - 1;
ct[last] ^= 1;
@@ -676,6 +731,58 @@ mod tests {
);
}
/// CCM AAD 超限应返回错误(而非静默跳过)
#[test]
fn test_ccm_aad_too_long() {
let key = [0u8; 16];
let nonce = [0u8; 12];
let big_aad = [0u8; 511]; // 超过 510 字节限制
assert!(
sm4_encrypt_ccm(&key, &nonce, &big_aad, b"data", 16).is_err(),
"AAD 超过 510 字节时应返回 InvalidInputLength"
);
}
/// XTS 加解密往返测试
#[test]
fn test_xts_roundtrip() {
let key1 = [0x11u8; 16];
let key2 = [0x22u8; 16];
let tweak = [0u8; 16];
let plain = [0x42u8; 32]; // 2 个 16 字节块
let ct = sm4_encrypt_xts(&key1, &key2, &tweak, &plain).unwrap();
let pt = sm4_decrypt_xts(&key1, &key2, &tweak, &ct).unwrap();
assert_eq!(pt, plain, "XTS 往返解密失败");
}
/// XTS 非对齐数据应返回错误
#[test]
fn test_xts_non_aligned_rejected() {
let key1 = [0u8; 16];
let key2 = [0u8; 16];
let tweak = [0u8; 16];
// 空输入
assert!(
sm4_encrypt_xts(&key1, &key2, &tweak, b"").is_err(),
"空输入应返回 InvalidInputLength"
);
// 非 16 倍数
assert!(
sm4_encrypt_xts(&key1, &key2, &tweak, b"not-aligned-data").is_ok(),
"正好 16 字节不应返回错误"
);
assert!(
sm4_encrypt_xts(&key1, &key2, &tweak, &[0u8; 17]).is_err(),
"17 字节应返回 InvalidInputLength"
);
assert!(
sm4_decrypt_xts(&key1, &key2, &tweak, &[0u8; 15]).is_err(),
"15 字节应返回 InvalidInputLength"
);
}
/// OFB 自反性验证
#[test]
fn test_ofb_self_inverse() {
+88 -35
View File
@@ -1,9 +1,9 @@
//! SM9 BN256 六次/十二次扩域 Fp6 / Fp12
//!
//! 塔式扩域:
//! Fp2 = Fp[u]/(u²+2)
//! Fp6 = Fp2[v]/(v³-u) 即 v³ = u
//! Fp12 = Fp6[w]/(w²-v) 即 w² = v
//! `Fp2 = Fp[u]/(u²+2)`
//! `Fp6 = Fp2[v]/(v³-u)` 即 v³ = u
//! `Fp12 = Fp6[w]/(w²-v)` 即 w² = v
//!
//! Frobenius 系数为编译期常量,源自 GB/T 38635.1-2020 及参考实现。
@@ -489,7 +489,8 @@ pub fn fp12_to_bytes(a: &Fp12) -> [u8; 384] {
/// - a: yP 系数 -> c0.c01 slot,在 eval_line_at_p 中乘以 yP
/// - b: 常数项 -> c0.c1v slot
/// - c: xP 系数 -> c1.c0w slot,在 eval_line_at_p 中乘以 xP
/// Reason: 经双线性性测试验证,此约定对应 D-type twist BN256 配对正确系数。
///
/// Reason: 经双线性性测试验证,此约定对应 D-type twist BN256 配对正确系数。
/// double step: a=Z₁²·u, b=-2Y₁Z₁, c=3X₁²
/// add step: a=r·x2, b=-(r·x1+h·y1), c=h·y2
#[derive(Clone, Copy, Debug)]
@@ -509,7 +510,7 @@ pub struct LineEval {
/// - a 系数(yP 项)→ c0.c0 (1 slot)
/// - b 系数(常数项)→ c1.c1 (vw slot)
/// - c 系数(xP 项)→ c1.c2 (v²w slot)
/// a、c 已经在 eval_line_at_p 中分别乘以 yP 和 xP。
/// a、c 已经在 eval_line_at_p 中分别乘以 yP 和 xP。
pub fn fp12_mul_by_line(f: &Fp12, l: &LineEval) -> Fp12 {
let line_fp12 = Fp12 {
c0: Fp6 {
@@ -526,7 +527,6 @@ pub fn fp12_mul_by_line(f: &Fp12, l: &LineEval) -> Fp12 {
fp12_mul(f, &line_fp12)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -591,15 +591,25 @@ mod tests {
/// 验证稀疏线函数乘法与全量 fp12_mul 结果一致
#[test]
fn test_fp12_mul_by_line_matches_full_mul() { // 构造一个非平凡的 f
fn test_fp12_mul_by_line_matches_full_mul() {
// 构造一个非平凡的 f
let f = Fp12 {
c0: Fp6 {
c0: Fp2 { c0: Fp::ONE, c1: Fp::ONE },
c1: Fp2 { c0: Fp::ONE, c1: Fp::ZERO },
c0: Fp2 {
c0: Fp::ONE,
c1: Fp::ONE,
},
c1: Fp2 {
c0: Fp::ONE,
c1: Fp::ZERO,
},
c2: Fp2::ZERO,
},
c1: Fp6 {
c0: Fp2 { c0: Fp::ZERO, c1: Fp::ONE },
c0: Fp2 {
c0: Fp::ZERO,
c1: Fp::ONE,
},
c1: Fp2::ZERO,
c2: Fp2::ZERO,
},
@@ -607,9 +617,18 @@ mod tests {
// 构造非零线函数
let l = LineEval {
a: Fp2 { c0: Fp::ONE, c1: Fp::ONE },
b: Fp2 { c0: Fp::ONE, c1: Fp::ZERO },
c: Fp2 { c0: Fp::ZERO, c1: Fp::ONE },
a: Fp2 {
c0: Fp::ONE,
c1: Fp::ONE,
},
b: Fp2 {
c0: Fp::ONE,
c1: Fp::ZERO,
},
c: Fp2 {
c0: Fp::ZERO,
c1: Fp::ONE,
},
};
// 稀疏乘法结果
@@ -618,8 +637,16 @@ mod tests {
// 构造全量 Fp12 线函数并做全量乘法(与 fp12_mul_by_line slot 保持一致)
// 槽位约定:a→c0.c0(1), b→c1.c1(vw), c→c1.c2(v²w)
let line_full = Fp12 {
c0: Fp6 { c0: l.a, c1: Fp2::ZERO, c2: Fp2::ZERO },
c1: Fp6 { c0: Fp2::ZERO, c1: l.b, c2: l.c },
c0: Fp6 {
c0: l.a,
c1: Fp2::ZERO,
c2: Fp2::ZERO,
},
c1: Fp6 {
c0: Fp2::ZERO,
c1: l.b,
c2: l.c,
},
};
let full = fp12_mul(&f, &line_full);
@@ -631,17 +658,34 @@ mod tests {
fn test_frob_w3_derivation() {
// 验证 fp12 Frobenius 一致性:frob_p(frob_p(f)) == frob_p2(f)
let f = Fp12 {
c0: Fp6 { c0: Fp2 { c0: Fp::ONE, c1: Fp::ONE }, c1: Fp2::ONE, c2: Fp2::ZERO },
c1: Fp6 { c0: Fp2::ONE, c1: Fp2::ZERO, c2: Fp2::ZERO },
c0: Fp6 {
c0: Fp2 {
c0: Fp::ONE,
c1: Fp::ONE,
},
c1: Fp2::ONE,
c2: Fp2::ZERO,
},
c1: Fp6 {
c0: Fp2::ONE,
c1: Fp2::ZERO,
c2: Fp2::ZERO,
},
};
let fp1 = fp12_frobenius_p(&f);
let fp1p1 = fp12_frobenius_p(&fp1); // frob_p^2(f)
let fp1p1 = fp12_frobenius_p(&fp1); // frob_p^2(f)
let fp2 = fp12_frobenius_p2(&f);
assert_eq!(fp1p1, fp2, "frob_p(frob_p(f)) != frob_p2(f)fp12 Frobenius 不一致");
assert_eq!(
fp1p1, fp2,
"frob_p(frob_p(f)) != frob_p2(f)fp12 Frobenius 不一致"
);
let fp2p1 = fp12_frobenius_p(&fp2); // frob_p^3(f)
let fp2p1 = fp12_frobenius_p(&fp2); // frob_p^3(f)
let fp3 = fp12_frobenius_p3(&f);
assert_eq!(fp2p1, fp3, "frob_p(frob_p2(f)) != frob_p3(f)fp12_frobenius_p3 系数错误");
assert_eq!(
fp2p1, fp3,
"frob_p(frob_p2(f)) != frob_p3(f)fp12_frobenius_p3 系数错误"
);
}
/// 验证 Fp6 Frobenius 保持 ONE
@@ -661,7 +705,10 @@ mod tests {
fn test_frob_v1_squared() {
use crate::sm9::fields::fp2::fp2_mul;
let v1_sq = fp2_mul(&FROB_V1_0, &FROB_V1_0);
assert_eq!(v1_sq, FROB_V1_1, "FROB_V1_0² 应等于 FROB_V1_1fp6 Frobenius 一致性)");
assert_eq!(
v1_sq, FROB_V1_1,
"FROB_V1_0² 应等于 FROB_V1_1fp6 Frobenius 一致性)"
);
}
/// 计算 u^{(p-1)/3} 并与 FROB_V1_0 对比(验证常量正确性)
@@ -670,14 +717,15 @@ mod tests {
#[test]
fn test_frob_v1_0_value_correct() {
use crate::sm9::fields::fp::FIELD_MODULUS;
use crate::sm9::fields::fp2::{fp2_mul, fp2_square};
use subtle::ConditionallySelectable;
use crate::sm9::fields::fp2::fp2_mul;
// 计算 u^{(p-1)/3} 其中 u = (0, 1) ∈ Fp2
let pm1 = FIELD_MODULUS.wrapping_sub(&crypto_bigint::U256::ONE);
let (pm1_div3, rem) = pm1.div_rem(&crypto_bigint::NonZero::new(crypto_bigint::U256::from(3u32)).unwrap());
let (pm1_div3, rem) =
pm1.div_rem(&crypto_bigint::NonZero::new(crypto_bigint::U256::from(3u32)).unwrap());
assert_eq!(rem, crypto_bigint::U256::ZERO, "(p-1) 应被 3 整除");
let (pm1_div6, _) = pm1.div_rem(&crypto_bigint::NonZero::new(crypto_bigint::U256::from(6u32)).unwrap());
let (pm1_div6, _) =
pm1.div_rem(&crypto_bigint::NonZero::new(crypto_bigint::U256::from(6u32)).unwrap());
fn fp2_pow_exp(base: &Fp2, exp: &crypto_bigint::U256) -> Fp2 {
use crate::sm9::fields::fp2::{fp2_mul, fp2_square};
@@ -695,7 +743,10 @@ mod tests {
result
}
let u = Fp2 { c0: crate::sm9::fields::fp::Fp::ZERO, c1: crate::sm9::fields::fp::Fp::ONE };
let u = Fp2 {
c0: crate::sm9::fields::fp::Fp::ZERO,
c1: crate::sm9::fields::fp::Fp::ONE,
};
// 正确的 γ_{1,1} = u^{(p-1)/3}
let correct_v1_0 = fp2_pow_exp(&u, &pm1_div3);
// 正确的 δ_{1,1} = u^{(p-1)/6}FROB_W1
@@ -707,7 +758,8 @@ mod tests {
// 打印正确的常量值(以标准 32 字节大端 hex 格式,供直接写入代码)
assert_eq!(
correct_v1_0, FROB_V1_0,
correct_v1_0,
FROB_V1_0,
"FROB_V1_0 需更新:正确值={:02X?}, FROB_W1 正确值 c0={:02X?} c1={:02X?}",
correct_v1_0.c0.retrieve().to_be_bytes(),
correct_w1.c0.retrieve().to_be_bytes(),
@@ -742,24 +794,25 @@ mod g2_frob_tests {
let p = FIELD_MODULUS;
let pm1 = p.wrapping_sub(&crypto_bigint::U256::ONE);
let u = Fp2 { c0: Fp::ZERO, c1: Fp::ONE };
let u = Fp2 {
c0: Fp::ZERO,
c1: Fp::ONE,
};
let pm1_div2 = pm1.wrapping_shr(1);
let u_pm1_div2 = fp2_pow_exp(&u, &pm1_div2);
let (pm1_div3, _) = pm1.div_rem(&crypto_bigint::NonZero::new(crypto_bigint::U256::from(3u32)).unwrap());
let (pm1_div3, _) =
pm1.div_rem(&crypto_bigint::NonZero::new(crypto_bigint::U256::from(3u32)).unwrap());
let u_pm1_div3 = fp2_pow_exp(&u, &pm1_div3);
let pp1 = p.wrapping_add(&crypto_bigint::U256::ONE);
let u_pm21_div3 = fp2_pow_exp(&u_pm1_div3, &pp1);
let u_pm21_div2 = fp2_pow_exp(&u_pm1_div2, &pp1);
// Reason: 验证 G2 Frobenius 修正常量与计算值一致
// u^{(p-1)/2} 应等于 G2_FROB_Y1
assert_eq!(u_pm1_div2, G2_FROB_Y1,
"u^(p-1)/2 应等于 G2_FROB_Y1");
assert_eq!(u_pm1_div2, G2_FROB_Y1, "u^(p-1)/2 应等于 G2_FROB_Y1");
// u^{(p²-1)/3} 应等于 G2_FROB_X2
assert_eq!(u_pm21_div3, G2_FROB_X2,
"u^(p2-1)/3 应等于 G2_FROB_X2");
assert_eq!(u_pm21_div3, G2_FROB_X2, "u^(p2-1)/3 应等于 G2_FROB_X2");
}
}
+1 -2
View File
@@ -1,6 +1,6 @@
//! SM9 BN256 二次扩域 Fp2
//!
//! Fp2 = Fp[u] / (u² + 2)
//! `Fp2 = Fp[u] / (u² + 2)`
//! 即 u² = -2
//!
//! 元素表示为 a = a0 + a1·u,其中 a0, a1 ∈ Fp
@@ -177,7 +177,6 @@ pub fn fp2_conjugate(a: &Fp2) -> Fp2 {
#[cfg(test)]
mod tests {
use super::*;
use crate::sm9::fields::fp::fp_from_bytes;
fn fp2_one() -> Fp2 {
Fp2::ONE
+1 -1
View File
@@ -88,7 +88,7 @@ impl G1Jacobian {
/// 点倍运算(BN256 a=0 专用公式)
///
/// 公式来自 https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
/// 公式来自 <https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l>
pub fn double(&self) -> Self {
if self.is_infinity() {
return *self;
+4 -4
View File
@@ -149,9 +149,9 @@ impl G2Jacobian {
// c = -3X₁²·Z₁² = -e·z1sq(在 eval_line_at_p 中乘以 xP→c1.c2(v²w)
let z1sq = fp2_square(z1);
let line = LineEval {
a: fp2_mul_u(&fp2_mul(&z3, &z1sq)), // 2Y₁Z₁³·u(×yP→c0.c0
a: fp2_mul_u(&fp2_mul(&z3, &z1sq)), // 2Y₁Z₁³·u(×yP→c0.c0
b: fp2_sub(&fp2_mul(x1, &e), &fp2_add(&b, &b)), // 3X₁³-2Y₁²(→c1.c1(vw)
c: fp2_neg(&fp2_mul(&e, &z1sq)), // -3X₁²Z₁²(×xP→c1.c2(v²w)
c: fp2_neg(&fp2_mul(&e, &z1sq)), // -3X₁²Z₁²(×xP→c1.c2(v²w)
};
(
@@ -190,9 +190,9 @@ impl G2Jacobian {
// b = X₁·Y₂·Z₁ - X₂·Y₁(常数项→c1.c1(vw)
// c = -(Y₂Z₁³-Y₁) = -rr已算,在 eval_line_at_p 中乘以 xP→c1.c2(v²w)
let line = LineEval {
a: fp2_mul_u(&z3), // H·Z₁·u(×yP→c0.c0
a: fp2_mul_u(&z3), // H·Z₁·u(×yP→c0.c0
b: fp2_sub(&fp2_mul(&fp2_mul(x1, z1), y2), &fp2_mul(x2, y1)), // X₁Y₂Z₁-X₂Y₁(→c1.c1(vw)
c: fp2_neg(&r), // -(Y₂Z₁³-Y₁)(×xP→c1.c2(v²w)
c: fp2_neg(&r), // -(Y₂Z₁³-Y₁)(×xP→c1.c2(v²w)
};
(
+86 -36
View File
@@ -158,7 +158,7 @@ pub fn generate_sign_master_keypair<R: RngCore>(rng: &mut R) -> (Sm9MasterPrivKe
/// GB/T 38635.2-2020 §6.1
/// t1 = H1(ID||hid, N) + ks
/// t2 = ks · t1^{-1} mod N(注意:不是 t1^{-1}·P1,而是 ks·t1^{-1}·P1
/// dA = [t2]P1
/// dA = \[t2\]P1
/// hid = 0x01(签名)
pub fn generate_sign_user_key(
master_priv: &Sm9MasterPrivKey,
@@ -218,7 +218,7 @@ pub fn generate_enc_master_keypair<R: RngCore>(rng: &mut R) -> (Sm9MasterPrivKey
/// GB/T 38635.1-2020 §6.1(加密密钥派生):
/// t1 = H1(ID||hid, N) + ke
/// t2 = ke · t1^{-1} mod N
/// de = [t2]P1
/// de = \[t2\]P1
pub fn generate_enc_user_key(
master_priv: &Sm9MasterPrivKey,
id: &[u8],
@@ -576,7 +576,7 @@ mod tests {
#[test]
fn test_generate_sign_master_keypair() {
let mut rng = FakeRng([0x42u8; 32]);
let (ks, ppub) = generate_sign_master_keypair(&mut rng);
let (_ks, ppub) = generate_sign_master_keypair(&mut rng);
// 验证 ppub 在 G2 上
let p = G2Affine::from_bytes(ppub.as_bytes()).expect("公钥应有效");
assert!(p.is_on_curve());
@@ -607,7 +607,7 @@ mod tests {
#[test]
fn test_pairing_bilinear() {
use crate::sm9::fields::fp12::{fp12_mul, Fp12};
use crate::sm9::fields::fp12::fp12_mul;
use crate::sm9::groups::g1::{G1Affine, G1Jacobian};
use crate::sm9::groups::g2::{G2Affine, G2Jacobian};
use crate::sm9::pairing::pairing;
@@ -617,24 +617,33 @@ mod tests {
let q = G2Affine::generator();
// 验证 G1 scalar_mul(2) == G1.double()
let g1_2_by_mul = G1Jacobian::scalar_mul_g1(&U256::from(2u32)).to_affine().unwrap();
let g1_2_by_mul = G1Jacobian::scalar_mul_g1(&U256::from(2u32))
.to_affine()
.unwrap();
let g1_jac = G1Jacobian::from_affine(&p);
let g1_2_by_double = g1_jac.double().to_affine().unwrap();
use crate::sm9::fields::fp::fp_to_bytes;
assert_eq!(
fp_to_bytes(&g1_2_by_mul.x), fp_to_bytes(&g1_2_by_double.x),
fp_to_bytes(&g1_2_by_mul.x),
fp_to_bytes(&g1_2_by_double.x),
"G1 scalar_mul(2) != G1.double() in x"
);
assert_eq!(
fp_to_bytes(&g1_2_by_mul.y), fp_to_bytes(&g1_2_by_double.y),
fp_to_bytes(&g1_2_by_mul.y),
fp_to_bytes(&g1_2_by_double.y),
"G1 scalar_mul(2) != G1.double() in y"
);
// 验证 G2 scalar_mul(2) == G2.double()
let g2_jac = G2Jacobian::from_affine(&q);
let g2_2_by_mul = G2Jacobian::scalar_mul_g2(&U256::from(2u32)).to_affine().unwrap();
let g2_2_by_mul = G2Jacobian::scalar_mul_g2(&U256::from(2u32))
.to_affine()
.unwrap();
let g2_2_by_double = g2_jac.double().to_affine().unwrap();
assert_eq!(g2_2_by_mul, g2_2_by_double, "G2 scalar_mul(2) != G2.double()");
assert_eq!(
g2_2_by_mul, g2_2_by_double,
"G2 scalar_mul(2) != G2.double()"
);
// e(2G1, G2) == e(G1, G2)^2
let e_2g1_g2 = pairing(&g1_2_by_mul, &q);
@@ -643,25 +652,30 @@ mod tests {
// 中间验证:用 G1+G1 (点加法)得到 2G1
let g1_jac2 = G1Jacobian::from_affine(&p);
let g1_add_g1 = G1Jacobian::add(&G1Jacobian::from_affine(&p), &g1_jac2).to_affine().unwrap();
let g1_add_g1 = G1Jacobian::add(&G1Jacobian::from_affine(&p), &g1_jac2)
.to_affine()
.unwrap();
let e_addg1_g2 = pairing(&g1_add_g1, &q);
assert_eq!(e_addg1_g2, e_sq, "e(G1+G1,G2) != e(G1,G2)²(用点加法)");
assert_eq!(e_2g1_g2, e_sq, "配对双线性性验证失败:e(2G1,G2) != e(G1,G2)²");
assert_eq!(
e_2g1_g2, e_sq,
"配对双线性性验证失败:e(2G1,G2) != e(G1,G2)²"
);
// e(G1, 2G2) == e(G1, G2)^2
let e_g1_2g2 = pairing(&p, &g2_2_by_mul);
assert_eq!(e_g1_2g2, e_sq, "配对双线性性验证失败:e(G1,2G2) != e(G1,G2)²");
assert_eq!(
e_g1_2g2, e_sq,
"配对双线性性验证失败:e(G1,2G2) != e(G1,G2)²"
);
}
}
#[cfg(test)]
mod pairing_tests {
use super::*;
use crate::sm9::fields::fp12::{
fp12_conjugate, fp12_frobenius_p, fp12_frobenius_p2, fp12_frobenius_p3,
fp12_inv, fp12_mul, fp12_square, Fp12,
};
use crate::sm9::fields::fp12::{fp12_conjugate, fp12_frobenius_p, fp12_mul, fp12_square, Fp12};
use crate::sm9::groups::g1::{G1Affine, G1Jacobian};
use crate::sm9::groups::g2::{G2Affine, G2Jacobian};
use crate::sm9::pairing::{final_exp, miller_loop, pairing};
@@ -671,7 +685,9 @@ mod pairing_tests {
fn test_pairing_double_only() {
let p = G1Affine::generator();
let q = G2Affine::generator();
let g1_2 = G1Jacobian::scalar_mul_g1(&U256::from(2u32)).to_affine().unwrap();
let g1_2 = G1Jacobian::scalar_mul_g1(&U256::from(2u32))
.to_affine()
.unwrap();
let e_g1_g2 = pairing(&p, &q);
let e_sq = fp12_mul(&e_g1_g2, &e_g1_g2);
@@ -687,7 +703,9 @@ mod pairing_tests {
fn test_miller_loop_raw_bilinear() {
let p = G1Affine::generator();
let q = G2Affine::generator();
let g1_2 = G1Jacobian::scalar_mul_g1(&U256::from(2u32)).to_affine().unwrap();
let g1_2 = G1Jacobian::scalar_mul_g1(&U256::from(2u32))
.to_affine()
.unwrap();
let ml1 = miller_loop(&q, &p);
let ml2 = miller_loop(&q, &g1_2);
@@ -699,14 +717,20 @@ mod pairing_tests {
let ml1_sq_inv = fp12_inv(&ml1_sq).expect("inv should exist");
let ratio = fp12_mul(&ml2, &ml1_sq_inv);
let ratio_exp = final_exp(&ratio);
assert_eq!(ratio_exp, Fp12::ONE, "final_exp(ml(2G1,G2)/ml(G1,G2)^2) != 1");
assert_eq!(
ratio_exp,
Fp12::ONE,
"final_exp(ml(2G1,G2)/ml(G1,G2)^2) != 1"
);
}
#[test]
fn test_miller_loop_bilinear() {
let p = G1Affine::generator();
let q = G2Affine::generator();
let g1_2 = G1Jacobian::scalar_mul_g1(&U256::from(2u32)).to_affine().unwrap();
let g1_2 = G1Jacobian::scalar_mul_g1(&U256::from(2u32))
.to_affine()
.unwrap();
let ml_g1_g2 = miller_loop(&q, &p);
let ml_2g1_g2 = miller_loop(&q, &g1_2);
@@ -714,7 +738,10 @@ mod pairing_tests {
let gt1 = final_exp(&ml_g1_g2);
let gt2 = final_exp(&ml_2g1_g2);
let gt1_sq = fp12_square(&gt1);
assert_eq!(gt2, gt1_sq, "final_exp(ml(2G1,G2)) != final_exp(ml(G1,G2))^2");
assert_eq!(
gt2, gt1_sq,
"final_exp(ml(2G1,G2)) != final_exp(ml(G1,G2))^2"
);
}
#[test]
@@ -736,7 +763,11 @@ mod pairing_tests {
base = fp12_mul(&base, &base);
}
}
assert_eq!(result, Fp12::ONE, "e(G1,G2)^n != 1: GT element not in subgroup");
assert_eq!(
result,
Fp12::ONE,
"e(G1,G2)^n != 1: GT element not in subgroup"
);
}
/// 验证 ml^{p^6} == conjugate(ml)Frobenius 正确性检查)
@@ -746,8 +777,9 @@ mod pairing_tests {
let q = G2Affine::generator();
let ml = miller_loop(&q, &p);
let ml_p6 = fp12_frobenius_p(&fp12_frobenius_p(&fp12_frobenius_p(
&fp12_frobenius_p(&fp12_frobenius_p(&fp12_frobenius_p(&ml))))));
let ml_p6 = fp12_frobenius_p(&fp12_frobenius_p(&fp12_frobenius_p(&fp12_frobenius_p(
&fp12_frobenius_p(&fp12_frobenius_p(&ml)),
))));
let ml_conj = fp12_conjugate(&ml);
assert_eq!(ml_p6, ml_conj, "ml^{{p^6}} != conjugate(ml)");
}
@@ -759,7 +791,6 @@ mod pairing_tests {
#[test]
fn test_single_double_step_line() {
use crate::sm9::fields::fp::fp_to_bytes;
use crate::sm9::fields::fp12::fp12_inv;
let g1 = G1Affine::generator();
let g2 = G2Affine::generator();
@@ -773,7 +804,9 @@ mod pairing_tests {
let g1_jac = G1Jacobian::from_affine(&g1);
let g1_2_by_add = G1Jacobian::add(&g1_jac, &g1_jac).to_affine().unwrap();
// 用标量乘法计算 2·G1
let g1_2_by_mul = G1Jacobian::scalar_mul_g1(&U256::from(2u32)).to_affine().unwrap();
let g1_2_by_mul = G1Jacobian::scalar_mul_g1(&U256::from(2u32))
.to_affine()
.unwrap();
// 验证两种方式得到相同的 2G1
assert_eq!(
@@ -788,7 +821,9 @@ mod pairing_tests {
);
// 检验 G2 侧双线性性:e(G1, 2G2) == e(G1, G2)^2
let g2_2 = G2Jacobian::scalar_mul_g2(&U256::from(2u32)).to_affine().unwrap();
let g2_2 = G2Jacobian::scalar_mul_g2(&U256::from(2u32))
.to_affine()
.unwrap();
let e_g1_2g2 = pairing(&g1, &g2_2);
let e_g1_g2_sq = fp12_mul(&e1, &e1);
assert_eq!(
@@ -804,25 +839,40 @@ mod pairing_tests {
/// 约定:a -> c0.c0(1 slot), b -> c0.c1(v slot), c -> c1.c0(w slot)
#[test]
fn test_line_eval_equivalence() {
use crate::sm9::fields::fp12::{
fp12_mul, fp12_mul_by_line, Fp12, Fp6, LineEval,
};
use crate::sm9::fields::fp2::Fp2;
use crate::sm9::fields::fp::Fp;
use crate::sm9::fields::fp12::{fp12_mul, fp12_mul_by_line, Fp12, Fp6, LineEval};
use crate::sm9::fields::fp2::Fp2;
// 验证 fp12_mul_by_line 等价于按约定槽位构造 full Fp12 再乘
// 约定:a -> c0.c0(1 slot), b -> c1.c1(vw slot), c -> c1.c2(v²w slot)
let line = LineEval {
a: Fp2 { c0: Fp::ONE, c1: Fp::ZERO },
b: Fp2 { c0: Fp::ONE, c1: Fp::ZERO },
c: Fp2 { c0: Fp::ONE, c1: Fp::ZERO },
a: Fp2 {
c0: Fp::ONE,
c1: Fp::ZERO,
},
b: Fp2 {
c0: Fp::ONE,
c1: Fp::ZERO,
},
c: Fp2 {
c0: Fp::ONE,
c1: Fp::ZERO,
},
};
let f = Fp12::ONE;
let sparse_result = fp12_mul_by_line(&f, &line);
// 按相同槽位手动构造 full Fp12(槽位 {c0.c0=a, c1.c1(vw)=b, c1.c2(v²w)=c}
let full_line = Fp12 {
c0: Fp6 { c0: line.a, c1: Fp2::ZERO, c2: Fp2::ZERO },
c1: Fp6 { c0: Fp2::ZERO, c1: line.b, c2: line.c },
c0: Fp6 {
c0: line.a,
c1: Fp2::ZERO,
c2: Fp2::ZERO,
},
c1: Fp6 {
c0: Fp2::ZERO,
c1: line.b,
c2: line.c,
},
};
let full_result = fp12_mul(&f, &full_line);
assert_eq!(
+21 -22
View File
@@ -9,9 +9,8 @@
use crate::sm9::fields::fp::Fp;
use crate::sm9::fields::fp12::{
fp12_conjugate, fp12_frobenius_p, fp12_frobenius_p2, fp12_frobenius_p3,
fp12_inv, fp12_mul, fp12_mul_by_line, fp12_square, Fp12, LineEval,
G2_FROB_X1_INV, G2_FROB_Y1_INV, G2_FROB_X2_INV,
fp12_conjugate, fp12_frobenius_p, fp12_frobenius_p2, fp12_frobenius_p3, fp12_inv, fp12_mul,
fp12_mul_by_line, fp12_square, Fp12, LineEval, G2_FROB_X1_INV, G2_FROB_X2_INV, G2_FROB_Y1_INV,
};
use crate::sm9::fields::fp2::{fp2_frobenius, fp2_mul, fp2_mul_fp};
use crate::sm9::groups::g1::G1Affine;
@@ -63,7 +62,7 @@ fn g2_frobenius_p2_neg(q: &G2Affine) -> G2Affine {
fn eval_line_at_p(line: &LineEval, px: &Fp, py: &Fp) -> LineEval {
LineEval {
a: fp2_mul_fp(&line.a, py), // a × yP(放 c0.c0 槽)
b: line.b, // 常数项不变(放 c0.c1 v 槽)
b: line.b, // 常数项不变(放 c0.c1 v 槽)
c: fp2_mul_fp(&line.c, px), // c × xP(放 c1.c0 w 槽)
}
}
@@ -167,25 +166,25 @@ const SM9_NINE: u128 = 9;
/// Reason: Beuchat et al. 分解针对标准 BN256(以太坊参数),不适用于 SM9 BN256。
/// 此函数使用 sm9_core 的 final_exp_last_chunk 算法(基于 SM9_A2/A3 常量)。
fn final_exp_hard(f: &Fp12) -> Fp12 {
let a = fp12_cyclotomic_pow(f, SM9_A3); // f^{A3}
let b = fp12_inv(&a).unwrap_or(Fp12::ONE); // f^{-A3}
let c = fp12_frobenius_p(&b); // f^{-A3*p}
let d = fp12_mul(&c, &b); // f^{-A3*(p+1)}
let e = fp12_mul(&d, &b); // f^{-A3*(p+2)}
let f_p1 = fp12_frobenius_p(f); // f^p
let g = fp12_mul(f, &f_p1); // f^{p+1}
let h = fp12_cyclotomic_pow(&g, SM9_NINE); // f^{9(p+1)}
let i = fp12_mul(&e, &h); // f^{-A3*(p+2)+9(p+1)}
let j = fp12_square(f); // f^2
let k = fp12_square(&j); // f^4
let l = fp12_mul(&k, &i); // f^{4 + -A3*(p+2) + 9(p+1)}
let m = fp12_square(&f_p1); // f^{2p}
let n = fp12_mul(&d, &m); // f^{-A3*(p+1)+2p}
let o = fp12_frobenius_p2(f); // f^{p^2}
let p_var = fp12_mul(&o, &n); // f^{p^2-A3*(p+1)+2p}
let q = fp12_cyclotomic_pow(&p_var, SM9_A2); // ...^{A2}
let a = fp12_cyclotomic_pow(f, SM9_A3); // f^{A3}
let b = fp12_inv(&a).unwrap_or(Fp12::ONE); // f^{-A3}
let c = fp12_frobenius_p(&b); // f^{-A3*p}
let d = fp12_mul(&c, &b); // f^{-A3*(p+1)}
let e = fp12_mul(&d, &b); // f^{-A3*(p+2)}
let f_p1 = fp12_frobenius_p(f); // f^p
let g = fp12_mul(f, &f_p1); // f^{p+1}
let h = fp12_cyclotomic_pow(&g, SM9_NINE); // f^{9(p+1)}
let i = fp12_mul(&e, &h); // f^{-A3*(p+2)+9(p+1)}
let j = fp12_square(f); // f^2
let k = fp12_square(&j); // f^4
let l = fp12_mul(&k, &i); // f^{4 + -A3*(p+2) + 9(p+1)}
let m = fp12_square(&f_p1); // f^{2p}
let n = fp12_mul(&d, &m); // f^{-A3*(p+1)+2p}
let o = fp12_frobenius_p2(f); // f^{p^2}
let p_var = fp12_mul(&o, &n); // f^{p^2-A3*(p+1)+2p}
let q = fp12_cyclotomic_pow(&p_var, SM9_A2); // ...^{A2}
let r = fp12_mul(&q, &l);
let s = fp12_frobenius_p3(f); // f^{p^3}
let s = fp12_frobenius_p3(f); // f^{p^3}
fp12_mul(&s, &r)
}
+7 -6
View File
@@ -70,12 +70,13 @@ fn hash_to_range(z: &[u8], hid: u8, n: &U256) -> U256 {
let h_raw = U256::from_be_slice(&ha[..32]);
// h = h_raw mod (n-1) + 1,确保 h ∈ [1, n-1]
// 于 h_raw 可能 ≥ n-1使用模运算
// crypto_bigint 无直接 mod,改用减法循环(n 是 256 位素数,循环次数最多 1 次)
let mut h = h_raw;
while h >= n_minus_1 {
h = h.wrapping_sub(&n_minus_1);
}
// Reason: 原 while 循环的执行次数取决于 h_raw 是否 ≥ n-1泄露 1 bit 信息。
// 改用无条件减法 + 掩码选择(conditional_select),执行时间与 h_raw 值无关。
// crypto_bigint::Uint 实现了 subtle::ConstantTimeLessct_lt 为常量时间比较。
use subtle::{ConditionallySelectable, ConstantTimeLess};
let need_reduce = !h_raw.ct_lt(&n_minus_1); // h_raw >= n_minus_1
let reduced = h_raw.wrapping_sub(&n_minus_1);
let h = U256::conditional_select(&h_raw, &reduced, need_reduce);
h.wrapping_add(&U256::ONE)
}
+12 -24
View File
@@ -13,14 +13,12 @@ use libsmx::sm2::{get_e, get_z, sign_with_k, verify, PrivateKey};
fn test_sm2_sign_verify_with_known_key() {
// GB/T 32918.2-2016 附录 A 私钥
let d_bytes =
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8")
.unwrap();
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8").unwrap();
let k_bytes =
hex::decode("59276e27d506861a16680f3ad9c02dccef3cc1fa3cdbe4ce6d54b80deac1bc21")
.unwrap();
hex::decode("59276e27d506861a16680f3ad9c02dccef3cc1fa3cdbe4ce6d54b80deac1bc21").unwrap();
let pri_key = PrivateKey::from_bytes(d_bytes.as_slice().try_into().unwrap())
.expect("私钥应有效");
let pri_key =
PrivateKey::from_bytes(d_bytes.as_slice().try_into().unwrap()).expect("私钥应有效");
let pub_key = pri_key.public_key();
let id = b"ALICE123@YAHOO.COM";
@@ -43,15 +41,13 @@ fn test_sm2_sign_verify_with_known_key() {
#[test]
fn test_sm2_different_messages_different_sigs() {
let d_bytes =
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8")
.unwrap();
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8").unwrap();
let pri_key = PrivateKey::from_bytes(d_bytes.as_slice().try_into().unwrap()).unwrap();
let pub_key = pri_key.public_key();
let id = b"test_user";
let k = U256::from_be_slice(
&hex::decode("59276e27d506861a16680f3ad9c02dccef3cc1fa3cdbe4ce6d54b80deac1bc21")
.unwrap(),
&hex::decode("59276e27d506861a16680f3ad9c02dccef3cc1fa3cdbe4ce6d54b80deac1bc21").unwrap(),
);
let z = get_z(id, &pub_key);
@@ -69,8 +65,7 @@ fn test_sm2_different_messages_different_sigs() {
#[test]
fn test_sm2_verify_tampered_message_fails() {
let d_bytes =
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8")
.unwrap();
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8").unwrap();
let pri_key = PrivateKey::from_bytes(d_bytes.as_slice().try_into().unwrap()).unwrap();
let pub_key = pri_key.public_key();
@@ -80,8 +75,7 @@ fn test_sm2_verify_tampered_message_fails() {
let e = get_e(&z, msg);
let k = U256::from_be_slice(
&hex::decode("59276e27d506861a16680f3ad9c02dccef3cc1fa3cdbe4ce6d54b80deac1bc21")
.unwrap(),
&hex::decode("59276e27d506861a16680f3ad9c02dccef3cc1fa3cdbe4ce6d54b80deac1bc21").unwrap(),
);
let sig = sign_with_k(&e, &pri_key, &k).unwrap();
@@ -97,8 +91,7 @@ fn test_sm2_verify_tampered_message_fails() {
#[test]
fn test_sm2_verify_tampered_sig_fails() {
let d_bytes =
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8")
.unwrap();
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8").unwrap();
let pri_key = PrivateKey::from_bytes(d_bytes.as_slice().try_into().unwrap()).unwrap();
let pub_key = pri_key.public_key();
@@ -108,24 +101,19 @@ fn test_sm2_verify_tampered_sig_fails() {
let e = get_e(&z, msg);
let k = U256::from_be_slice(
&hex::decode("59276e27d506861a16680f3ad9c02dccef3cc1fa3cdbe4ce6d54b80deac1bc21")
.unwrap(),
&hex::decode("59276e27d506861a16680f3ad9c02dccef3cc1fa3cdbe4ce6d54b80deac1bc21").unwrap(),
);
let mut sig = sign_with_k(&e, &pri_key, &k).unwrap();
sig[0] ^= 1; // 篡改 r 的第一字节
assert!(
verify(&e, &pub_key, &sig).is_err(),
"篡改签名后验签应失败"
);
assert!(verify(&e, &pub_key, &sig).is_err(), "篡改签名后验签应失败");
}
/// Z 值计算确定性验证(相同输入产生相同 Z)
#[test]
fn test_sm2_z_value_deterministic() {
let d_bytes =
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8")
.unwrap();
hex::decode("3945208f7b2144b13f36e38ac6d39f95889393692860b51a42fb81ef4df7c5b8").unwrap();
let pri_key = PrivateKey::from_bytes(d_bytes.as_slice().try_into().unwrap()).unwrap();
let pub_key = pri_key.public_key();
+16 -8
View File
@@ -12,9 +12,13 @@ use libsmx::sm3::Sm3Hasher;
fn test_sm3_vector_a1_abc() {
let msg = b"abc";
let digest = Sm3Hasher::digest(msg);
let expected = hex::decode("66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0")
.unwrap();
assert_eq!(digest.as_slice(), expected.as_slice(), "GB/T 32905 附录 A.1 失败");
let expected =
hex::decode("66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0").unwrap();
assert_eq!(
digest.as_slice(),
expected.as_slice(),
"GB/T 32905 附录 A.1 失败"
);
}
/// GB/T 32905-2016 附录 A.2
@@ -24,9 +28,13 @@ fn test_sm3_vector_a1_abc() {
fn test_sm3_vector_a2_64bytes() {
let msg = b"abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd";
let digest = Sm3Hasher::digest(msg);
let expected = hex::decode("debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732")
.unwrap();
assert_eq!(digest.as_slice(), expected.as_slice(), "GB/T 32905 附录 A.2 失败");
let expected =
hex::decode("debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732").unwrap();
assert_eq!(
digest.as_slice(),
expected.as_slice(),
"GB/T 32905 附录 A.2 失败"
);
}
/// 流式接口与单次接口结果一致性验证
@@ -48,7 +56,7 @@ fn test_sm3_streaming_equals_oneshot() {
#[test]
fn test_sm3_empty_message() {
let digest = Sm3Hasher::digest(b"");
let expected = hex::decode("1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b")
.unwrap();
let expected =
hex::decode("1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b").unwrap();
assert_eq!(digest.as_slice(), expected.as_slice(), "SM3 空消息测试失败");
}
+5 -5
View File
@@ -4,8 +4,8 @@
use libsmx::sm9::{
generate_enc_master_keypair, generate_enc_user_key, generate_sign_master_keypair,
generate_sign_user_key, sm9_decrypt, sm9_encrypt, sm9_sign, sm9_verify,
Sm9EncPubKey, Sm9MasterPrivKey, Sm9SignPubKey,
generate_sign_user_key, sm9_decrypt, sm9_encrypt, sm9_sign, sm9_verify, Sm9EncPubKey,
Sm9SignPubKey,
};
use rand_core::RngCore;
@@ -180,11 +180,11 @@ mod pairing_reference_tests {
/// This tests with a hardcoded known-good pairing value
#[test]
fn test_pairing_against_sm9core() {
use sm9_core::{G1, G2, Group};
use libsmx::sm9::fields::fp12::fp12_to_bytes;
use libsmx::sm9::groups::g1::G1Affine;
use libsmx::sm9::groups::g2::G2Affine;
use libsmx::sm9::pairing::pairing;
use libsmx::sm9::fields::fp12::fp12_to_bytes;
use sm9_core::{Group, G1, G2};
// Get sm9_core reference pairing of generators
let g1_ref = G1::one();
@@ -201,7 +201,7 @@ mod pairing_reference_tests {
// Print both for debugging
println!("sm9_core ref bytes[0..32]: {:02x?}", &ref_bytes[0..32]);
println!("our bytes[0..32]: {:02x?}", &our_bytes[0..32]);
// They can't be directly compared due to different tower structures
// But we can verify by checking if our e(G1,G2)^order == 1
// For now, just print to help diagnose