diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8f1fc29 --- /dev/null +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 09530ac..62dbc78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md index c5c3310..3de1fd8 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..da85e58 --- /dev/null +++ b/README.zh-CN.md @@ -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-GCM(AEAD 认证加密) + +```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` 的 API(SM2/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)。 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..69eea71 --- /dev/null +++ b/SECURITY.md @@ -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. diff --git a/benches/sm2_bench.rs b/benches/sm2_bench.rs index d07e571..72af025 100644 --- a/benches/sm2_bench.rs +++ b/benches/sm2_bench.rs @@ -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!( diff --git a/benches/sm9_bench.rs b/benches/sm9_bench.rs index 086beec..0280fb3 100644 --- a/benches/sm9_bench.rs +++ b/benches/sm9_bench.rs @@ -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!( diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..3a26366 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +edition = "2021" diff --git a/scripts/pre_publish_check.sh b/scripts/pre_publish_check.sh new file mode 100755 index 0000000..ef53c5b --- /dev/null +++ b/scripts/pre_publish_check.sh @@ -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" diff --git a/src/sm2/ec.rs b/src/sm2/ec.rs index f02d517..2601f6f 100644 --- a/src/sm2/ec.rs +++ b/src/sm2/ec.rs @@ -98,7 +98,7 @@ impl JacobianPoint { /// 点倍运算(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 特化公式降低乘法次数。 /// /// # 安全性 @@ -128,7 +128,11 @@ impl JacobianPoint { &double2(&double1(&gamma2)), ); - let d = JacobianPoint { x: x3, y: y3, z: z3 }; + let d = JacobianPoint { + x: x3, + y: y3, + z: z3, + }; // Reason: 无穷远点的倍点仍为无穷远点;用掩码选择替代 if 分支, // 避免 scalar_mul 热路径中泄露哪些迭代位为前导零。 JacobianPoint::conditional_select(&d, self, self.ct_is_infinity()) @@ -136,7 +140,7 @@ impl JacobianPoint { /// 点加运算(完全常量时间,无条件分支) /// - /// 公式来自 https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl + /// 公式来自 /// /// # 安全性 /// 采用"计算所有情况 + 掩码选择"策略,消除全部退化情况的条件分支: @@ -176,7 +180,11 @@ impl JacobianPoint { let y3 = fp_sub(&fp_mul(&r, &fp_sub(&u1h2, &x3)), &fp_mul(&s1, &h3)); // Z3 = H·Z1·Z2 (当 H==0 时 z3=0,即 INFINITY,与下面掩码一致) let z3 = fp_mul(&fp_mul(&h, &p.z), &q.z); - let normal = JacobianPoint { x: x3, y: y3, z: z3 }; + let normal = JacobianPoint { + x: x3, + y: y3, + z: z3, + }; // 预计算 P==Q 退化情况的结果(无条件执行,结果由掩码决定是否使用) let double_p = p.double(); @@ -186,12 +194,12 @@ impl JacobianPoint { let result = normal; // 优先级 2:P == -Q → INFINITY(h==0 且 r≠0) let result = JacobianPoint::conditional_select( - &result, &JacobianPoint::INFINITY, h_is_zero & !r_is_zero, + &result, + &JacobianPoint::INFINITY, + h_is_zero & !r_is_zero, ); // 优先级 3:P == Q → double(P)(h==0 且 r==0) - let result = JacobianPoint::conditional_select( - &result, &double_p, h_is_zero & r_is_zero, - ); + 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 @@ -250,6 +258,7 @@ fn double2(a: &Fp) -> Fp { /// - X1·Z2² 简化为 X1(0 次乘法) /// - Y1·Z2³ 简化为 Y1(0 次乘法) /// - Z3 中的 Z2 乘法(Z3 = H·Z1,而非 H·Z1·Z2) +/// /// 共节省约 3~4 次域乘法,用于预计算表构建和 multi_scalar_mul 内循环。 /// /// # 安全性 @@ -258,10 +267,10 @@ fn add_mixed(p: &JacobianPoint, q: &AffinePoint) -> JacobianPoint { use subtle::ConstantTimeEq; // Z_Q = 1,故 u1 = X1,s1 = 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 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); @@ -269,28 +278,29 @@ fn add_mixed(p: &JacobianPoint, q: &AffinePoint) -> JacobianPoint { 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 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), - ); + 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 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, + &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); @@ -328,7 +338,7 @@ fn scalar_mul_g_window(k: &U256) -> JacobianPoint { for _ in 0..4 { result = result.double(); } - let window = (byte >> 4) as u8; + let window = byte >> 4; // 常量时间表查找:遍历 1..=15,用 ct_eq 选出 table[window] let mut sel = JacobianPoint::INFINITY; for j in 1u8..=15 { @@ -592,6 +602,9 @@ mod tests { y: fp_neg(&g.y), z: g.z, }; - assert!(JacobianPoint::add(&g, &g_neg).is_infinity(), "G + (-G) 应为无穷远点"); + assert!( + JacobianPoint::add(&g, &g_neg).is_infinity(), + "G + (-G) 应为无穷远点" + ); } } diff --git a/src/sm3/compress.rs b/src/sm3/compress.rs index b380763..a8b8c82 100644 --- a/src/sm3/compress.rs +++ b/src/sm3/compress.rs @@ -63,26 +63,62 @@ pub(super) fn compress(state: &mut [u32; 8], block: &[u8; 64]) { // Reason: 将 64 轮分两段展开,消除 ff/gg/T 中的 if 分支。 // j = 0..15:FF = x^y^z,GG = x^y^z for j in 0..16 { - let ss1 = a.rotate_left(12).wrapping_add(e).wrapping_add(T[j]).rotate_left(7); + 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 ^ c).wrapping_add(d).wrapping_add(ss2).wrapping_add(w[j] ^ w[j + 4]); - let tt2 = (e ^ f ^ g).wrapping_add(h).wrapping_add(ss1).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); + let tt1 = (a ^ b ^ c) + .wrapping_add(d) + .wrapping_add(ss2) + .wrapping_add(w[j] ^ w[j + 4]); + let tt2 = (e ^ f ^ g) + .wrapping_add(h) + .wrapping_add(ss1) + .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..63:FF = 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 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]); + .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; a = tt1; - h = g; g = f.rotate_left(19); f = e; e = p0(tt2); + .wrapping_add(h) + .wrapping_add(ss1) + .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); } - state[0] ^= a; state[1] ^= b; state[2] ^= c; state[3] ^= d; - state[4] ^= e; state[5] ^= f; state[6] ^= g; state[7] ^= h; + state[0] ^= a; + state[1] ^= b; + state[2] ^= c; + state[3] ^= d; + state[4] ^= e; + state[5] ^= f; + state[6] ^= g; + state[7] ^= h; } diff --git a/src/sm4/cipher.rs b/src/sm4/cipher.rs index 7e0ae31..8a89b4d 100644 --- a/src/sm4/cipher.rs +++ b/src/sm4/cipher.rs @@ -39,6 +39,7 @@ const CK: [u32; 32] = [ /// /// 仅使用 `&`/`^`/`|`/`!` 位运算,零内存访问,无条件分支。 /// 每个中间变量为 0 或 1(对应输入字节的各个位平面)。 +#[allow(dead_code)] #[inline] pub(crate) fn sbox_ct(x: u8) -> u8 { // 提取输入字节的 8 个位(b0 = LSB, b7 = MSB) @@ -53,19 +54,19 @@ pub(crate) fn sbox_ct(x: u8) -> u8 { // ── 输入线性层(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 + 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 let g0 = t10; let g1 = t7; @@ -87,47 +88,47 @@ pub(crate) fn sbox_ct(x: u8) -> u8 { // ── 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 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 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 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 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 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 l1 = t11m ^ t1m; let t12m = p1 | t2m; - let l2 = t12m ^ t5m; + let l2 = t12m ^ t5m; // ── Bottom 函数(GF(2^4) 求逆的输出组合)───────────────────────────────── // Reason: 将 l0..l3 与输入中间变量结合,得到 r0..r11(12 个中间结果)。 @@ -137,42 +138,59 @@ pub(crate) fn sbox_ct(x: u8) -> u8 { 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 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 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 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); + 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) + b0o | (b1o << 1) | (b2o << 2) | (b3o << 3) | (b4o << 4) | (b5o << 5) | (b6o << 6) | (b7o << 7) } /// SM4 τ 变换:4 字节 u32 一次性位切片 S-box(常量时间,4-way 并行) @@ -197,8 +215,8 @@ fn tau(a: u32) -> u32 { // Reason: 打包后每个 u32 变量的 bit-j 对应第 j 个字节的该位面, // XOR/AND/OR 在 4 个独立"通道"上并行执行,语义不变。 let mut bits = [0u32; 8]; - for i in 0..8usize { - bits[i] = ((bytes[0] >> i) & 1) as u32 + 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; @@ -208,75 +226,136 @@ fn tau(a: u32) -> u32 { // ── 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 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 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 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 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 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 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); + 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 in 0..8usize { - let v = ob[i]; + 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; diff --git a/src/sm4/modes.rs b/src/sm4/modes.rs index d972a6c..9bf1e97 100644 --- a/src/sm4/modes.rs +++ b/src/sm4/modes.rs @@ -770,7 +770,7 @@ mod tests { ); // 非 16 倍数 assert!( - sm4_encrypt_xts(&key1, &key2, &tweak, b"not-aligned-data").is_err() == false, + sm4_encrypt_xts(&key1, &key2, &tweak, b"not-aligned-data").is_ok(), "正好 16 字节不应返回错误" ); assert!( diff --git a/src/sm9/fields/fp12.rs b/src/sm9/fields/fp12.rs index 9506cf0..42e0a74 100644 --- a/src/sm9/fields/fp12.rs +++ b/src/sm9/fields/fp12.rs @@ -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 及参考实现。 @@ -527,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::*; @@ -592,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, }, @@ -608,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, + }, }; // 稀疏乘法结果 @@ -619,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); @@ -632,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 @@ -662,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_1(fp6 Frobenius 一致性)"); + assert_eq!( + v1_sq, FROB_V1_1, + "FROB_V1_0² 应等于 FROB_V1_1(fp6 Frobenius 一致性)" + ); } /// 计算 u^{(p-1)/3} 并与 FROB_V1_0 对比(验证常量正确性) @@ -671,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}; @@ -696,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) @@ -708,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(), @@ -743,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"); } } diff --git a/src/sm9/fields/fp2.rs b/src/sm9/fields/fp2.rs index c84e0f3..395b8d2 100644 --- a/src/sm9/fields/fp2.rs +++ b/src/sm9/fields/fp2.rs @@ -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 diff --git a/src/sm9/groups/g1.rs b/src/sm9/groups/g1.rs index 4ad10f9..4f3749a 100644 --- a/src/sm9/groups/g1.rs +++ b/src/sm9/groups/g1.rs @@ -88,7 +88,7 @@ impl G1Jacobian { /// 点倍运算(BN256 a=0 专用公式) /// - /// 公式来自 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; diff --git a/src/sm9/groups/g2.rs b/src/sm9/groups/g2.rs index d5f29f2..9d0f3af 100644 --- a/src/sm9/groups/g2.rs +++ b/src/sm9/groups/g2.rs @@ -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₁) = -r(r已算,在 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)) }; ( diff --git a/src/sm9/mod.rs b/src/sm9/mod.rs index b925bec..3c92aae 100644 --- a/src/sm9/mod.rs +++ b/src/sm9/mod.rs @@ -158,7 +158,7 @@ pub fn generate_sign_master_keypair(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(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(>1); - 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!( diff --git a/src/sm9/pairing.rs b/src/sm9/pairing.rs index 52e7257..716e2e7 100644 --- a/src/sm9/pairing.rs +++ b/src/sm9/pairing.rs @@ -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) } diff --git a/tests/sm2_vectors.rs b/tests/sm2_vectors.rs index 801e577..f8cfcfa 100644 --- a/tests/sm2_vectors.rs +++ b/tests/sm2_vectors.rs @@ -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(); diff --git a/tests/sm3_vectors.rs b/tests/sm3_vectors.rs index f28d333..64345c5 100644 --- a/tests/sm3_vectors.rs +++ b/tests/sm3_vectors.rs @@ -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 空消息测试失败"); } diff --git a/tests/sm9_vectors.rs b/tests/sm9_vectors.rs index d9faee8..ba20306 100644 --- a/tests/sm9_vectors.rs +++ b/tests/sm9_vectors.rs @@ -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