6 Commits

Author SHA1 Message Date
huangxt 21e7e65b32 添加 GitHub Pages 文档与 CI 修复
新增功能:
- docs/ 目录:GitHub Pages 静态页面
  - index.html:项目主页
  - og-image.png/svg:社交分享图片
- tests/rustls_provider.rs:rustls CryptoProvider 集成测试
- rustls_provider 模块增强:
  - hash.rs:新增测试和文档
  - hmac.rs:新增测试和文档
  - kx.rs:新增测试和文档
  - sign.rs:新增测试和文档
  - verify.rs:新增测试和文档

CI 修复:
- rustls 依赖改为 git 源,避免本地路径依赖问题
- 简化 SM3 HMAC 流式测试,移除 alloc 依赖
- 移除未使用的 extern crate alloc
- 代码格式化

文档更新:
- README.md:添加 GitHub Pages 链接
- CHANGELOG.md:更新变更记录
- Cargo.toml:添加 dev-dependencies
2026-03-09 18:51:40 +08:00
huangxt 7c159343f8 发布 v0.3.0:rustls CryptoProvider 支持
新增功能:
- rustls CryptoProvider:完整 rustls 0.23.x 支持
  - SM3 Hash/Context trait 实现
  - SM3 HMAC trait 实现
  - SM4-GCM/CCM AEAD 密码套件
  - SM2 ECDHE 密钥交换
  - SM2 签名/验签算法
- SM3 流式 HMAC(HmacSm3)
- SM2 SPKI DER 编码
- SM2 DEFAULT_ID 常量

文档更新:
- README 依赖版本更新为 0.3
- CHANGELOG 添加 v0.3.0 变更记录
- SECURITY 更新版本支持表
2026-03-09 10:13:09 +08:00
huangxt 2580571881 发布 v0.2.1:SM2 密钥交换、HKDF-SM3、DER 编解码
新增功能:
- SM2 密钥交换:GB/T 32918.3 完整协议(exchange_a/b)、ECDH
- SM2 DER 编解码:签名 DER、SEC1/PKCS#8 私钥解析
- SM2 便捷 API:sign_message/verify_message 自动计算 Z 值
- HKDF-SM3:RFC 5869 兼容的 HKDF
- SM3 Hasher 增强:reset/finalize_reset 流式复用
- SM4 AEAD 合并格式:GCM/CCM combined(TLS 格式)

文档更新:
- CHANGELOG 添加 v0.2.1 变更记录
2026-03-08 20:58:36 +08:00
huangxt a58fe8275e 发布 v0.2.0:新增 BLS 签名与 FPE 格式保留加密
新增功能:
- BLS 签名:基于 BN256 配对的最小签名尺寸变体
- BLS 门限签名:Shamir 秘密分享 + Lagrange 插值聚合
- Hash-to-Curve:RFC 9380 兼容,SM3 消息扩展
- FPE 格式保留加密:基于 SM4 的 Feistel 密码
- SM9 Fp 平方根:Tonelli-Shanks 算法

文档更新:
- README 添加 BLS/FPE 算法描述
- CHANGELOG 添加 v0.2.0 变更记录
- SECURITY 更新版本支持表
2026-03-08 20:02:06 +08:00
huangxt 69767aee00 修正 README 依赖版本号
- Quick Start 依赖版本从 0.3 改为 0.1
- no_std 依赖版本从 0.3 改为 0.1
- 与当前 Cargo.toml 版本 0.1.1 保持一致
2026-03-08 11:31:49 +08:00
huangxt 36c2742777 发布 v0.1.1
- 修复 no_std 测试编译错误
- MSRV 升级至 1.83.0
- 使用 Rust 1.83+ 内置 div_ceil 方法
- 优化 CI 安全扫描脚本
2026-03-07 21:32:20 +08:00
33 changed files with 6048 additions and 25 deletions
+98
View File
@@ -5,6 +5,100 @@ 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.3.0] - 2025-03-09
### Added
- **rustls CryptoProvider** (`rustls_provider` module, requires `rustls` feature)
- Full rustls 0.23.x `CryptoProvider` implementation for TLCP (Chinese TLS)
- `hash.rs`: SM3 `Hash` and `Context` trait implementations
- `hmac.rs`: SM3 HMAC trait implementation
- `tls13.rs`: SM4-GCM/CCM AEAD cipher suites (TLS13_SM4_GCM_SM3, TLS13_SM4_CCM_SM3)
- `kx.rs`: SM2 ECDHE key exchange
- `sign.rs`: SM2 signing algorithm
- `verify.rs`: SM2 signature verification
- `mod.rs`: `crypto_provider()` function returning complete `CryptoProvider`
- **SM3 streaming HMAC** (`HmacSm3`)
- `update()`: Streaming data input
- `finalize()`: Final HMAC computation
- **SM2 SPKI DER encoding**
- `public_key_to_spki_der()`: Encode public key as RFC 5480 SubjectPublicKeyInfo
- **SM2 DEFAULT_ID constant**
- `DEFAULT_ID`: Default user ID "1234567812345678" (16 bytes)
### Changed
- `Cargo.toml`: Added `rustls` optional feature with dependencies
## [0.2.1] - 2025-03-08
### Added
- **SM2 key exchange** (`sm2::key_exchange` module)
- `exchange_a` / `exchange_b`: GB/T 32918.3 full key exchange protocol with confirmation hash
- `ecdh`: Simple SM2-ECDH shared secret computation (TLS/rustls compatible)
- `ecdh_from_slice`: Slice-based ECDH for TLS integration
- `EphemeralKey`: Ephemeral key pair with `ZeroizeOnDrop`
- **SM2 DER codec** (`sm2::der` module)
- `sig_to_der` / `sig_from_der`: Signature DER encoding/decoding
- `private_key_from_sec1_der`: RFC 5915 SEC1 private key parsing
- `private_key_from_pkcs8_der`: RFC 5958 PKCS#8 private key parsing
- **SM2 convenience API** (`sm2` module)
- `sign_message` / `verify_message`: One-step sign/verify with automatic Z-value computation
- **HKDF-SM3** (`sm3::hkdf` module)
- `hkdf_extract` / `hkdf_expand` / `hkdf`: RFC 5869 compatible
- **SM3 Hasher enhancements**
- `reset()` / `finalize_reset()`: Streaming hasher reuse
- **SM4 AEAD combined format**
- `sm4_encrypt_gcm_combined` / `sm4_decrypt_gcm_combined`: TLS format (ciphertext||tag)
- `sm4_encrypt_ccm_combined` / `sm4_decrypt_ccm_combined`: Same format for CCM
## [0.2.0] - 2025-03-08
### Added
- **BLS signatures** (`bls` module, requires `alloc` feature)
- `bls_keygen` / `bls_sign` / `bls_verify`: minimal-signature-size variant (sig ∈ G1, pk ∈ G2)
- `bls_aggregate` / `bls_aggregate_verify`: multi-message aggregate signatures
- `bls_fast_aggregate_verify`: fast aggregate verification for same-message multi-signer
- `BlsSignature::to_bytes` / `from_bytes`: 65-byte serialization (uncompressed G1 point)
- `BlsPubKey::to_bytes` / `from_bytes`: 128-byte serialization (uncompressed G2 point)
- **BLS threshold signatures** (`bls::threshold` module)
- `bls_threshold_keygen`: Trusted Dealer mode, Shamir polynomial secret sharing
- `bls_partial_sign` / `bls_combine_signatures`: Lagrange interpolation based aggregation
- Supports (t+1, n) threshold configurations
- **Hash-to-Curve** (`bls::hash_to_curve` module)
- `hash_to_g1`: RFC 9380 compliant, maps arbitrary message to BN256 G1 point
- `expand_message_xmd`: RFC 9380 §5.3.1, message expansion using SM3 as hash
- `map_to_curve_svdw`: Shallue-van de Woestijne mapping for BN256 (a=0 curve)
- **`fp_sqrt`** in `sm9::fields::fp`
- Tonelli-Shanks modular square root for SM9 BN256 Fp (p ≡ 1 mod 4)
- `fp_is_square`: Euler criterion based quadratic residue test
- **FPE format-preserving encryption** (`fpe` module)
- `FpeKey`: 7-round Luby-Rackoff Feistel cipher based on SM4
- Supports 1~128 bit plaintext/ciphertext domains
- `expand_tweak`: arbitrary-length tweak via SM4 hash
- Automatic key zeroization on drop (`ZeroizeOnDrop`)
### Security
- BLS signature DST separation: signing uses `BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_`, PoP uses a different tag
- BN256 security note: ~100-bit actual security level documented in API docs
## [0.1.1] - 2025-03-07
### Fixed
- Fixed `cargo test --no-default-features --lib` compilation error
- SM3 tests: removed alloc dependency, use manual hex parsing
- SM4 modes tests: added `#[cfg(feature = "alloc")]`
### Changed
- MSRV raised to 1.83.0 (required by crypto-bigint 0.6.x for ConstMontyForm constant-time Montgomery arithmetic)
- Use Rust 1.83+ built-in `div_ceil` method instead of manual implementation
- Optimized sanity_check.sh to skip test code, avoiding false positives
## [0.1.0] - 2025-03-07
### Added
@@ -53,4 +147,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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.3.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.3.0
[0.2.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.1
[0.2.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.0
[0.1.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
+94
View File
@@ -5,6 +5,96 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [0.3.0] - 2025-03-09
### 新增
- **rustls CryptoProvider**`rustls_provider` 模块,需 `rustls` 特性)
- 完整的 rustls 0.23.x `CryptoProvider` 实现,支持 TLCP(国密 TLS)
- `hash.rs`SM3 `Hash``Context` trait 实现
- `hmac.rs`SM3 HMAC trait 实现
- `tls13.rs`SM4-GCM/CCM AEAD 密码套件(TLS13_SM4_GCM_SM3、TLS13_SM4_CCM_SM3
- `kx.rs`SM2 ECDHE 密钥交换
- `sign.rs`SM2 签名算法
- `verify.rs`SM2 验签算法
- `mod.rs``crypto_provider()` 函数返回完整 `CryptoProvider`
- **SM3 流式 HMAC**`HmacSm3`
- `update()`:流式数据输入
- `finalize()`:最终 HMAC 计算
- **SM2 SPKI DER 编码**
- `public_key_to_spki_der()`:将公钥编码为 RFC 5480 SubjectPublicKeyInfo
- **SM2 DEFAULT_ID 常量**
- `DEFAULT_ID`:默认用户 ID "1234567812345678"16 字节)
### 变更
- `Cargo.toml`:添加 `rustls` 可选特性及依赖
## [0.2.1] - 2025-03-08
### 新增
- **SM2 密钥交换**`sm2::key_exchange` 模块)
- `exchange_a` / `exchange_b`GB/T 32918.3 完整密钥交换协议(带确认哈希)
- `ecdh`:简单 SM2-ECDH 共享密钥计算(适配 TLS/rustls
- `EphemeralKey`:临时密钥对(`ZeroizeOnDrop`
- **SM2 DER 编解码**`sm2::der` 模块)
- `sig_to_der` / `sig_from_der`:签名 DER 编解码
- `private_key_from_sec1_der` / `private_key_from_pkcs8_der`:私钥解析
- **SM2 便捷 API**`sm2` 模块)
- `sign_message` / `verify_message`:自动计算 Z 值的一步签名/验签
- **HKDF-SM3**`sm3::hkdf` 模块)
- `hkdf_extract` / `hkdf_expand` / `hkdf`RFC 5869 兼容
- **SM3 Hasher 增强**
- `reset()` / `finalize_reset()`:支持流式复用
- **SM4 AEAD 合并格式**
- `sm4_encrypt_gcm_combined` / `sm4_decrypt_gcm_combined`TLS 格式(密文||Tag
- `sm4_encrypt_ccm_combined` / `sm4_decrypt_ccm_combined`:同上
- **BLS 签名**`bls` 模块,需 `alloc` 特性)
- `bls_keygen` / `bls_sign` / `bls_verify`:最小签名尺寸变体(签名 ∈ G1,公钥 ∈ G2)
- `bls_aggregate` / `bls_aggregate_verify`:多消息聚合签名
- `bls_fast_aggregate_verify`:同消息多签名者快速聚合验证
- `BlsSignature::to_bytes` / `from_bytes`:65 字节序列化(非压缩 G1 点)
- `BlsPubKey::to_bytes` / `from_bytes`:128 字节序列化(非压缩 G2 点)
- **BLS 门限签名**`bls::threshold` 模块)
- `bls_threshold_keygen`:可信分发者模式,Shamir 多项式秘密分享
- `bls_partial_sign` / `bls_combine_signatures`Lagrange 插值聚合
- 支持 (t+1, n) 门限配置
- **Hash-to-Curve**`bls::hash_to_curve` 模块)
- `hash_to_g1`:符合 RFC 9380,将任意消息映射到 BN256 G1 点
- `expand_message_xmd`RFC 9380 §5.3.1,使用 SM3 进行消息扩展
- `map_to_curve_svdw`Shallue-van de Woestijne 映射(BN256 a=0 曲线)
- **`fp_sqrt`** 在 `sm9::fields::fp`
- Tonelli-Shanks 模平方根(SM9 BN256 Fpp ≡ 1 mod 4
- `fp_is_square`:基于欧拉判据的二次剩余判定
- **FPE 格式保留加密**`fpe` 模块)
- `FpeKey`:基于 SM4 的 7 轮 Luby-Rackoff Feistel 密码
- 支持 1~128 位明文/密文域
- `expand_tweak`:通过 SM4 哈希实现任意长度 tweak
- 离开作用域自动清零密钥(`ZeroizeOnDrop`
### 安全
- BLS 签名 DST 分离:签名使用 `BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_`PoP 使用不同标签
- BN256 安全说明:API 文档中注明约 100 位实际安全强度
## [0.1.1] - 2025-03-07
### 修复
- 修复 `cargo test --no-default-features --lib` 编译错误
- SM3 测试:移除 alloc 依赖,改用手动十六进制解析
- SM4 modes 测试:添加 `#[cfg(feature = "alloc")]`
### 变更
- MSRV 提升至 1.83.0crypto-bigint 0.6.x 的 ConstMontyForm 常量时间 Montgomery 算术所需)
- 使用 Rust 1.83+ 内置 `div_ceil` 方法替代手动实现
### CI
- 优化 sanity_check.sh 跳过测试代码,避免误报
## [0.1.0] - 2025-03-07
### 新增
@@ -53,4 +143,8 @@
- XTS:拒绝非 16 字节对齐输入,而非静默截断
- SM9 `hash_to_range`:用常量时间条件选择替换可变迭代 `while` 循环
[0.3.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.3.0
[0.2.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.1
[0.2.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.0
[0.1.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
Generated
+161 -4
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "aho-corasick"
@@ -118,6 +118,16 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
@@ -310,6 +320,12 @@ dependencies = [
"syn",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -368,7 +384,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -422,18 +438,27 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libsmx"
version = "0.1.0"
version = "0.3.0"
dependencies = [
"criterion",
"crypto-bigint",
"getrandom",
"hex",
"rand 0.8.5",
"rand_core 0.6.4",
"rustls",
"rustls-pki-types",
"sm9_core",
"subtle",
"zeroize",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
@@ -635,6 +660,53 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.24.0-dev.0"
source = "git+https://github.com/rustls/rustls.git?branch=main#c5a1a4a3f391718e0b5bf87de0cceabb67258ab7"
dependencies = [
"log",
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.104.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d58d0ba9664baf780379001ecb183f4b95dabec82d464bff14173dee21bf49e3"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -693,6 +765,12 @@ dependencies = [
"zmij",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "sm9_core"
version = "0.5.0"
@@ -753,6 +831,12 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "version_check"
version = "0.9.5"
@@ -836,7 +920,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
"windows-sys 0.61.2",
]
[[package]]
@@ -845,6 +929,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -854,6 +947,70 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.8.40"
+12 -3
View File
@@ -1,15 +1,16 @@
[package]
name = "libsmx"
version = "0.1.0"
version = "0.3.0"
edition = "2021"
rust-version = "1.83.0"
authors = ["kintai <kintai@foxmail.com>"]
license = "Apache-2.0"
description = "Pure-Rust, no_std, constant-time SM2/SM3/SM4/SM9 Chinese cryptography (GB/T 32918/32905/32907/38635)"
repository = "https://github.com/kintaiW/libsmx"
documentation = "https://docs.rs/libsmx"
homepage = "https://github.com/kintaiW/libsmx"
homepage = "https://kintaiw.github.io/libsmx/"
categories = ["cryptography", "no-std"]
keywords = ["sm2", "sm3", "sm4", "sm9", "gmssl"]
keywords = ["sm2", "sm3", "sm4", "sm9", "cryptography"]
readme = "README.md"
exclude = [
"benches/",
@@ -35,17 +36,25 @@ crypto-bigint = { version = "0.6", default-features = false }
subtle = { version = "2.6", default-features = false }
zeroize = { version = "1.8", default-features = false, features = ["derive"] }
rand_core = { version = "0.6", default-features = false }
rustls = { git = "https://github.com/rustls/rustls.git", branch = "main", optional = true, default-features = false }
pki-types = { package = "rustls-pki-types", version = "1", optional = true, features = ["alloc"] }
getrandom = { version = "0.2", optional = true }
[features]
default = ["alloc"]
alloc = []
std = ["alloc", "rand_core/std"]
# rustls CryptoProvider 集成(RFC 8998 国密 TLS 套件)
rustls-provider = ["alloc", "dep:rustls", "dep:pki-types", "dep:getrandom"]
[dev-dependencies]
hex = "0.4"
criterion = { version = "0.5", features = ["html_reports"] }
rand = "0.8"
sm9_core = "0.5.0"
# rustls-provider 集成测试依赖(通过 feature 条件编译保护)
rustls = { git = "https://github.com/rustls/rustls.git", branch = "main", default-features = false, features = ["log", "webpki"] }
pki-types = { package = "rustls-pki-types", version = "1", features = ["alloc"] }
[[bench]]
name = "sm2_bench"
+19 -1
View File
@@ -7,7 +7,9 @@
[![License](https://img.shields.io/crates/l/libsmx.svg)](LICENSE)
[![MSRV](https://img.shields.io/badge/MSRV-1.83.0-blue.svg)](https://blog.rust-lang.org/2024/11/28/Rust-1.83.0.html)
Pure-Rust, `#![no_std]` implementation of Chinese commercial cryptography standards with constant-time operations throughout.
Pure-Rust, `#![no_std]` implementation of Chinese commercial cryptography standards (国密/GuoMi) with constant-time operations throughout. Designed for embedded systems, WASM, and production use.
> **Security Notice**: This library has **not** been independently audited. While all secret-dependent operations are constant-time and `#![forbid(unsafe_code)]` is enforced, you should evaluate the risks before using it in production. Report vulnerabilities via [SECURITY.md](SECURITY.md) or email [kintai@foxmail.com](mailto:kintai@foxmail.com).
| Algorithm | Standard | Description |
|-----------|----------|-------------|
@@ -15,6 +17,8 @@ Pure-Rust, `#![no_std]` implementation of Chinese commercial cryptography standa
| **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) |
| **BLS** | IETF RFC 9380 | BLS Signatures & Threshold Signatures (BN256) |
| **FPE** | NIST SP 800-38G | Format-Preserving Encryption (FF1-like) |
## Features
@@ -149,6 +153,7 @@ sm9_verify(msg, &h, &s, user_id, &sign_pub).unwrap();
|---------|---------|-------------|
| `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` |
| `rustls-provider` | No | Enables rustls `CryptoProvider` with RFC 8998 ShangMi TLS 1.3 cipher suites |
For `no_std` without `alloc`:
@@ -157,6 +162,13 @@ For `no_std` without `alloc`:
libsmx = { version = "0.3", default-features = false }
```
With rustls TLS 1.3 provider:
```toml
[dependencies]
libsmx = { version = "0.3", features = ["rustls-provider"] }
```
## Benchmarks
Measured on Linux x86_64 (single core). All operations are constant-time.
@@ -209,6 +221,12 @@ cargo bench
> **Disclaimer**: This library has **not** been independently audited. See [SECURITY.md](SECURITY.md) for vulnerability reporting.
## Contributing
Bug reports, feature requests, and pull requests are welcome on [GitHub](https://github.com/kintaiW/libsmx).
For **security vulnerabilities**, please do **not** open public issues. Instead, email [kintai@foxmail.com](mailto:kintai@foxmail.com) directly. See [SECURITY.md](SECURITY.md) for details.
## MSRV Policy
The minimum supported Rust version is **1.83.0**. MSRV bumps are treated as minor version changes.
+2
View File
@@ -15,6 +15,8 @@
| **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 双线性配对) |
| **BLS** | IETF RFC 9380 | BLS 签名与门限签名(BN256 |
| **FPE** | NIST SP 800-38G | 格式保留加密(FF1 类) |
## 特性
+2
View File
@@ -4,6 +4,8 @@
| Version | Supported |
|---------|-----------|
| 0.3.x | Yes |
| 0.2.x | Yes |
| 0.1.x | Yes |
| < 0.1 | No |
+2
View File
@@ -4,6 +4,8 @@
| 版本 | 是否支持 |
|------|----------|
| 0.3.x | 是 |
| 0.2.x | 是 |
| 0.1.x | 是 |
| < 0.1 | 否 |
+894
View File
@@ -0,0 +1,894 @@
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>libsmx — Pure-Rust Chinese Cryptography</title>
<!-- SEO Meta -->
<meta name="description" content="Pure-Rust, #![no_std] implementation of SM2/SM3/SM4/SM9/BLS/FPE Chinese commercial cryptography standards. Zero unsafe code, constant-time operations, side-channel resistant." />
<meta name="keywords" content="Rust, SM2, SM3, SM4, SM9, BLS, FPE, Cryptography, No-Std, Chinese Cryptography, GB/T, constant-time, zero unsafe, side-channel resistant, AEAD, GCM, rustls" />
<meta name="author" content="libsmx contributors" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://kintaiw.github.io/libsmx/" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:title" content="libsmx — Pure-Rust Chinese Cryptography" />
<meta property="og:description" content="SM2/SM3/SM4/SM9/BLS/FPE in pure Rust. #![no_std], zero unsafe, constant-time everywhere." />
<meta property="og:url" content="https://kintaiw.github.io/libsmx/" />
<meta property="og:site_name" content="libsmx" />
<!-- Replace with actual image URL after creating an OG image (1200x630 recommended) -->
<meta property="og:image" content="https://kintaiw.github.io/libsmx/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="libsmx — Pure-Rust Chinese Cryptography" />
<meta name="twitter:description" content="SM2/SM3/SM4/SM9/BLS/FPE in pure Rust. #![no_std], zero unsafe, constant-time everywhere." />
<meta name="twitter:image" content="https://kintaiw.github.io/libsmx/og-image.png" />
<!-- JSON-LD Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareSourceCode",
"name": "libsmx",
"description": "Pure-Rust, #![no_std] implementation of Chinese commercial cryptography standards (SM2/SM3/SM4/SM9/BLS/FPE) with constant-time operations and zero unsafe code.",
"codeRepository": "https://github.com/kintaiW/libsmx",
"programmingLanguage": "Rust",
"license": "https://www.apache.org/licenses/LICENSE-2.0",
"runtimePlatform": "Rust 1.83.0+",
"url": "https://kintaiw.github.io/libsmx/",
"keywords": ["SM2", "SM3", "SM4", "SM9", "BLS", "FPE", "cryptography", "no_std", "constant-time", "Rust"],
"applicationCategory": "Security",
"operatingSystem": "Cross-platform (no_std)"
}
</script>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: {
DEFAULT: '#dea584',
light: '#f0c6a0',
dark: '#c4845a',
},
safe: {
DEFAULT: '#10b981',
light: '#34d399',
},
surface: {
900: '#0f172a',
800: '#1e293b',
700: '#334155',
600: '#475569',
},
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
},
},
},
};
</script>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<!-- Prism.js (Tomorrow Night theme + Rust) -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-rust.min.js"></script>
<style>
/* Fade-in animation */
.fade-in {
opacity: 0;
transform: translateY(24px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}
/* Feature card hover */
.feature-card {
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
}
/* Code window override */
pre[class*="language-"] {
margin: 0 !important;
border-radius: 0 0 0.75rem 0.75rem !important;
background: #1e293b !important;
font-size: 0.875rem !important;
line-height: 1.7 !important;
}
code[class*="language-"] {
font-family: 'JetBrains Mono', 'Fira Code', monospace !important;
font-size: 0.875rem !important;
}
/* Benchmark bar */
.bench-bar {
height: 8px;
border-radius: 4px;
background: linear-gradient(90deg, #dea584, #10b981);
transition: width 0.8s ease-out;
}
/* Tab active state */
.tab-btn.active {
color: #dea584;
border-bottom-color: #dea584;
}
/* Scrollbar styling */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: #1e293b; }
::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #64748b; }
/* Nav blur */
.nav-blur {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
</style>
</head>
<body class="bg-surface-900 text-gray-300 font-sans antialiased">
<!-- ============================================================ -->
<!-- NAV -->
<!-- ============================================================ -->
<nav class="fixed top-0 left-0 right-0 z-50 nav-blur bg-surface-900/80 border-b border-white/5">
<div class="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
<a href="#" class="flex items-center gap-2 text-white font-bold text-lg tracking-tight">
<span class="text-brand">&lt;/&gt;</span> libsmx
</a>
<div class="hidden md:flex items-center gap-8 text-sm text-gray-400">
<a href="#features" class="hover:text-white transition-colors">Features</a>
<a href="#code" class="hover:text-white transition-colors">Code</a>
<a href="#benchmarks" class="hover:text-white transition-colors">Benchmarks</a>
<a href="#trust" class="hover:text-white transition-colors">Trust</a>
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener" class="hover:text-white transition-colors">Docs</a>
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener"
class="flex items-center gap-1.5 text-white bg-white/10 hover:bg-white/15 px-4 py-1.5 rounded-lg transition-colors">
<i data-lucide="github" class="w-4 h-4"></i> GitHub
</a>
</div>
<!-- Mobile menu button -->
<button id="mobile-menu-btn" class="md:hidden text-gray-400 hover:text-white">
<i data-lucide="menu" class="w-6 h-6"></i>
</button>
</div>
<!-- Mobile menu -->
<div id="mobile-menu" class="hidden md:hidden bg-surface-800 border-t border-white/5 px-6 py-4 space-y-3 text-sm">
<a href="#features" class="block text-gray-400 hover:text-white">Features</a>
<a href="#code" class="block text-gray-400 hover:text-white">Code</a>
<a href="#benchmarks" class="block text-gray-400 hover:text-white">Benchmarks</a>
<a href="#trust" class="block text-gray-400 hover:text-white">Trust</a>
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener" class="block text-gray-400 hover:text-white">Docs</a>
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener" class="block text-gray-400 hover:text-white">GitHub</a>
</div>
</nav>
<!-- ============================================================ -->
<!-- HERO -->
<!-- ============================================================ -->
<section class="relative min-h-screen flex items-center justify-center pt-16 overflow-hidden">
<!-- Background gradient -->
<div class="absolute inset-0 bg-gradient-to-b from-surface-900 via-surface-900 to-surface-800"></div>
<div class="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] bg-brand/5 rounded-full blur-3xl"></div>
<div class="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-brand/20 to-transparent"></div>
<div class="relative z-10 max-w-4xl mx-auto px-6 text-center">
<!-- Badges row -->
<div class="flex flex-wrap justify-center gap-2 mb-8 fade-in">
<a href="https://github.com/kintaiW/libsmx/actions/workflows/ci.yml" target="_blank" rel="noopener">
<img src="https://github.com/kintaiW/libsmx/actions/workflows/ci.yml/badge.svg" alt="CI" loading="lazy" />
</a>
<a href="https://crates.io/crates/libsmx" target="_blank" rel="noopener">
<img src="https://img.shields.io/crates/v/libsmx.svg" alt="Crates.io" loading="lazy" />
</a>
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener">
<img src="https://img.shields.io/docsrs/libsmx" alt="docs.rs" loading="lazy" />
</a>
<a href="https://codecov.io/gh/kintaiW/libsmx" target="_blank" rel="noopener">
<img src="https://codecov.io/gh/kintaiW/libsmx/graph/badge.svg" alt="codecov" loading="lazy" />
</a>
<img src="https://img.shields.io/crates/l/libsmx.svg" alt="License" loading="lazy" />
<img src="https://img.shields.io/badge/MSRV-1.83.0-blue.svg" alt="MSRV" loading="lazy" />
</div>
<!-- Slogan -->
<h1 class="text-4xl sm:text-5xl md:text-7xl font-black text-white leading-tight tracking-tight mb-6 fade-in">
Pure-Rust Chinese Cryptography.
<br />
<span class="text-brand">Zero <code class="font-mono text-[0.85em]">unsafe</code></span>.
<span class="text-safe">Constant-time</span>.
</h1>
<!-- Subtitle -->
<p class="text-lg sm:text-xl text-gray-400 max-w-2xl mx-auto mb-4 fade-in">
SM2 / SM3 / SM4 / SM9 / BLS / FPE — six algorithms, fully implemented,
strictly conforming to GB/T national standards.
</p>
<p class="text-base text-gray-500 max-w-xl mx-auto mb-10 fade-in">
<code class="text-brand/80 font-mono text-sm">#![no_std]</code> ready.
From bare-metal MCUs to WASM — runs everywhere.
</p>
<!-- CTA Buttons -->
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 mb-12 fade-in">
<button id="copy-btn" onclick="copyInstall()"
class="group flex items-center gap-3 bg-brand text-surface-900 font-semibold px-6 py-3 rounded-xl hover:bg-brand-light transition-colors text-base">
<i data-lucide="terminal" class="w-5 h-5"></i>
<span class="font-mono">cargo add libsmx</span>
<i data-lucide="copy" class="w-4 h-4 opacity-60 group-hover:opacity-100 transition-opacity"></i>
</button>
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener"
class="flex items-center gap-2 border border-white/20 text-white px-6 py-3 rounded-xl hover:bg-white/5 transition-colors text-base">
<i data-lucide="github" class="w-5 h-5"></i> View on GitHub
</a>
</div>
<!-- Scroll indicator -->
<div class="animate-bounce fade-in">
<i data-lucide="chevrons-down" class="w-6 h-6 text-gray-600 mx-auto"></i>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- ALGORITHM OVERVIEW -->
<!-- ============================================================ -->
<section class="py-20 bg-surface-800 border-y border-white/5">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-12 fade-in">
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Algorithm Coverage</h2>
<p class="text-2xl sm:text-3xl font-bold text-white">Six Standards. One Crate.</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 fade-in">
<!-- SM2 -->
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
<div class="flex items-center gap-3 mb-2">
<span class="text-brand font-mono font-bold text-lg">SM2</span>
<span class="text-xs text-gray-500 font-mono">GB/T 32918</span>
</div>
<p class="text-sm text-gray-400">Elliptic Curve PKC — sign, verify, encrypt, decrypt, key exchange, DER codec</p>
</div>
<!-- SM3 -->
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
<div class="flex items-center gap-3 mb-2">
<span class="text-brand font-mono font-bold text-lg">SM3</span>
<span class="text-xs text-gray-500 font-mono">GB/T 32905</span>
</div>
<p class="text-sm text-gray-400">256-bit cryptographic hash — streaming, one-shot, HMAC, HKDF</p>
</div>
<!-- SM4 -->
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
<div class="flex items-center gap-3 mb-2">
<span class="text-brand font-mono font-bold text-lg">SM4</span>
<span class="text-xs text-gray-500 font-mono">GB/T 32907</span>
</div>
<p class="text-sm text-gray-400">128-bit block cipher — ECB, CBC, OFB, CFB, CTR, GCM, CCM, XTS</p>
</div>
<!-- SM9 -->
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
<div class="flex items-center gap-3 mb-2">
<span class="text-brand font-mono font-bold text-lg">SM9</span>
<span class="text-xs text-gray-500 font-mono">GB/T 38635</span>
</div>
<p class="text-sm text-gray-400">Identity-based crypto — BN256 pairing, sign, encrypt, key encapsulation</p>
</div>
<!-- BLS -->
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
<div class="flex items-center gap-3 mb-2">
<span class="text-brand font-mono font-bold text-lg">BLS</span>
<span class="text-xs text-gray-500 font-mono">RFC 9380</span>
</div>
<p class="text-sm text-gray-400">BLS signatures — aggregate verification, Shamir threshold signing</p>
</div>
<!-- FPE -->
<div class="bg-surface-900/60 border border-white/5 rounded-xl p-5">
<div class="flex items-center gap-3 mb-2">
<span class="text-brand font-mono font-bold text-lg">FPE</span>
<span class="text-xs text-gray-500 font-mono">SP 800-38G</span>
</div>
<p class="text-sm text-gray-400">Format-preserving encryption — FNR algorithm, 7-round Feistel with SM4</p>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- FEATURES GRID -->
<!-- ============================================================ -->
<section id="features" class="py-24">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-16 fade-in">
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Why libsmx</h2>
<p class="text-2xl sm:text-3xl font-bold text-white">Built for Security Engineers</p>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Card 1 -->
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
<div class="w-12 h-12 rounded-xl bg-brand/10 flex items-center justify-center mb-5">
<i data-lucide="shield-check" class="w-6 h-6 text-brand"></i>
</div>
<h3 class="text-white font-semibold text-lg mb-2">Full GB/T Compliance</h3>
<p class="text-gray-400 text-sm leading-relaxed">
Complete SM2/SM3/SM4/SM9 implementations, plus BLS signatures and format-preserving encryption. Every algorithm validated against official GB/T standard test vectors.
</p>
</div>
<!-- Card 2 -->
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
<div class="w-12 h-12 rounded-xl bg-safe/10 flex items-center justify-center mb-5">
<i data-lucide="timer" class="w-6 h-6 text-safe"></i>
</div>
<h3 class="text-white font-semibold text-lg mb-2">Constant-Time Everywhere</h3>
<p class="text-gray-400 text-sm leading-relaxed">
All secret-dependent ops use <code class="text-brand/80 font-mono text-xs">subtle</code> primitives. SM2 scalar multiplication iterates all 256 bits. SM4 S-box uses boolean-circuit bitslice — zero cache-timing leaks.
</p>
</div>
<!-- Card 3 -->
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
<div class="w-12 h-12 rounded-xl bg-red-500/10 flex items-center justify-center mb-5">
<i data-lucide="shield-off" class="w-6 h-6 text-red-400"></i>
</div>
<h3 class="text-white font-semibold text-lg mb-2">Zero <code class="font-mono">unsafe</code> Code</h3>
<p class="text-gray-400 text-sm leading-relaxed">
<code class="text-brand/80 font-mono text-xs">#![forbid(unsafe_code)]</code> enforced at the crate root. Private keys implement <code class="text-brand/80 font-mono text-xs">ZeroizeOnDrop</code> — automatic cleanup on scope exit.
</p>
</div>
<!-- Card 4 -->
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
<div class="w-12 h-12 rounded-xl bg-blue-500/10 flex items-center justify-center mb-5">
<i data-lucide="cpu" class="w-6 h-6 text-blue-400"></i>
</div>
<h3 class="text-white font-semibold text-lg mb-2"><code class="font-mono">#![no_std]</code> Ready</h3>
<p class="text-gray-400 text-sm leading-relaxed">
Runs on embedded MCUs, WASM runtimes, and bare-metal targets. Optional <code class="text-brand/80 font-mono text-xs">alloc</code> and <code class="text-brand/80 font-mono text-xs">std</code> feature flags. Zero runtime dependencies.
</p>
</div>
<!-- Card 5 -->
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
<div class="w-12 h-12 rounded-xl bg-yellow-500/10 flex items-center justify-center mb-5">
<i data-lucide="zap" class="w-6 h-6 text-yellow-400"></i>
</div>
<h3 class="text-white font-semibold text-lg mb-2">Production Performance</h3>
<p class="text-gray-400 text-sm leading-relaxed">
SM3 hashing at <strong class="text-white">374 MiB/s</strong>. SM2 signing in <strong class="text-white">258 µs</strong>. SM4-ECB at <strong class="text-white">27 MiB/s</strong> — all measured under constant-time constraints. No shortcuts.
</p>
</div>
<!-- Card 6 -->
<div class="feature-card bg-surface-800 border border-white/5 rounded-2xl p-7 fade-in">
<div class="w-12 h-12 rounded-xl bg-purple-500/10 flex items-center justify-center mb-5">
<i data-lucide="lock" class="w-6 h-6 text-purple-400"></i>
</div>
<h3 class="text-white font-semibold text-lg mb-2">rustls TLS 1.3</h3>
<p class="text-gray-400 text-sm leading-relaxed">
Feature-gated <code class="text-brand/80 font-mono text-xs">rustls-provider</code> delivers RFC 8998 ShangMi cipher suites (SM4-GCM-SM3 / SM4-CCM-SM3). Plug directly into the Rust TLS ecosystem.
</p>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- CODE DEMO -->
<!-- ============================================================ -->
<section id="code" class="py-24 bg-surface-800 border-y border-white/5">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-16 fade-in">
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Code Examples</h2>
<p class="text-2xl sm:text-3xl font-bold text-white">Clean API. Real Cryptography.</p>
</div>
<!-- Tabs -->
<div class="flex gap-1 mb-0 fade-in">
<button onclick="switchTab('sm2')" id="tab-sm2"
class="tab-btn active px-5 py-2.5 text-sm font-medium border-b-2 border-transparent rounded-t-lg bg-surface-900/40 hover:bg-surface-900/60 transition-colors">
SM2 Sign / Verify
</button>
<button onclick="switchTab('sm4')" id="tab-sm4"
class="tab-btn px-5 py-2.5 text-sm font-medium border-b-2 border-transparent rounded-t-lg bg-surface-900/40 hover:bg-surface-900/60 transition-colors text-gray-500">
SM4-GCM AEAD
</button>
</div>
<!-- SM2 Tab Content -->
<div id="panel-sm2" class="grid grid-cols-1 lg:grid-cols-5 gap-0 fade-in">
<!-- Code (left, 3 cols) -->
<div class="lg:col-span-3">
<div class="bg-surface-900 rounded-tl-none rounded-tr-none lg:rounded-tr-none rounded-2xl overflow-hidden border border-white/5">
<!-- IDE title bar -->
<div class="flex items-center gap-2 px-4 py-3 bg-surface-900 border-b border-white/5">
<span class="w-3 h-3 rounded-full bg-red-500/80"></span>
<span class="w-3 h-3 rounded-full bg-yellow-500/80"></span>
<span class="w-3 h-3 rounded-full bg-green-500/80"></span>
<span class="text-xs text-gray-500 ml-2 font-mono">sm2_example.rs</span>
</div>
<pre class="!rounded-t-none"><code class="language-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");</code></pre>
</div>
</div>
<!-- Explanation (right, 2 cols) -->
<div class="lg:col-span-2 bg-surface-900/50 border border-white/5 lg:border-l-0 rounded-b-2xl lg:rounded-bl-none lg:rounded-r-2xl p-6 space-y-5">
<div>
<div class="flex items-center gap-2 mb-1.5">
<span class="w-5 h-5 rounded-full bg-brand/20 text-brand text-xs font-bold flex items-center justify-center">1</span>
<span class="text-white font-medium text-sm">generate_keypair</span>
</div>
<p class="text-gray-400 text-sm pl-7">OS-level CSPRNG generates a 256-bit SM2 key pair on the standard curve (GB/T 32918.5).</p>
</div>
<div>
<div class="flex items-center gap-2 mb-1.5">
<span class="w-5 h-5 rounded-full bg-brand/20 text-brand text-xs font-bold flex items-center justify-center">2</span>
<span class="text-white font-medium text-sm">get_z</span>
</div>
<p class="text-gray-400 text-sm pl-7">Computes the user-distinguishing Z value per the national standard — SM3 hash of ID length, ID, curve params, and public key coordinates.</p>
</div>
<div>
<div class="flex items-center gap-2 mb-1.5">
<span class="w-5 h-5 rounded-full bg-brand/20 text-brand text-xs font-bold flex items-center justify-center">3</span>
<span class="text-white font-medium text-sm">get_e → sign</span>
</div>
<p class="text-gray-400 text-sm pl-7">Digest <code class="text-brand/80 font-mono text-xs">e = SM3(Z || M)</code>, then sign. Scalar multiplication runs all 256 rounds — constant-time against timing attacks.</p>
</div>
<div>
<div class="flex items-center gap-2 mb-1.5">
<span class="w-5 h-5 rounded-full bg-brand/20 text-brand text-xs font-bold flex items-center justify-center">4</span>
<span class="text-white font-medium text-sm">verify</span>
</div>
<p class="text-gray-400 text-sm pl-7">Constant-time verification returns <code class="text-brand/80 font-mono text-xs">Result&lt;()&gt;</code>. Failures leak zero timing information.</p>
</div>
</div>
</div>
<!-- SM4 Tab Content (hidden by default) -->
<div id="panel-sm4" class="hidden grid grid-cols-1 lg:grid-cols-5 gap-0 fade-in">
<!-- Code (left, 3 cols) -->
<div class="lg:col-span-3">
<div class="bg-surface-900 rounded-2xl overflow-hidden border border-white/5">
<div class="flex items-center gap-2 px-4 py-3 bg-surface-900 border-b border-white/5">
<span class="w-3 h-3 rounded-full bg-red-500/80"></span>
<span class="w-3 h-3 rounded-full bg-yellow-500/80"></span>
<span class="w-3 h-3 rounded-full bg-green-500/80"></span>
<span class="text-xs text-gray-500 ml-2 font-mono">sm4_gcm_example.rs</span>
</div>
<pre class="!rounded-t-none"><code class="language-rust">use libsmx::sm4::{sm4_encrypt_gcm, sm4_decrypt_gcm};
let key = [0u8; 16];
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);</code></pre>
</div>
</div>
<!-- Explanation (right, 2 cols) -->
<div class="lg:col-span-2 bg-surface-900/50 border border-white/5 lg:border-l-0 rounded-b-2xl lg:rounded-bl-none lg:rounded-r-2xl p-6 space-y-5">
<div>
<div class="flex items-center gap-2 mb-1.5">
<span class="w-5 h-5 rounded-full bg-safe/20 text-safe text-xs font-bold flex items-center justify-center">1</span>
<span class="text-white font-medium text-sm">128-bit Key + 96-bit Nonce</span>
</div>
<p class="text-gray-400 text-sm pl-7">Standard AEAD parameters per NIST GCM specification. The 128-bit key matches SM4's native block size.</p>
</div>
<div>
<div class="flex items-center gap-2 mb-1.5">
<span class="w-5 h-5 rounded-full bg-safe/20 text-safe text-xs font-bold flex items-center justify-center">2</span>
<span class="text-white font-medium text-sm">Additional Authenticated Data</span>
</div>
<p class="text-gray-400 text-sm pl-7"><code class="text-brand/80 font-mono text-xs">aad</code> is authenticated but not encrypted — ideal for protocol headers that need integrity without confidentiality.</p>
</div>
<div>
<div class="flex items-center gap-2 mb-1.5">
<span class="w-5 h-5 rounded-full bg-safe/20 text-safe text-xs font-bold flex items-center justify-center">3</span>
<span class="text-white font-medium text-sm">(ciphertext, tag)</span>
</div>
<p class="text-gray-400 text-sm pl-7">Returns ciphertext + 128-bit authentication tag. The tag ensures tamper detection via GMAC.</p>
</div>
<div>
<div class="flex items-center gap-2 mb-1.5">
<span class="w-5 h-5 rounded-full bg-safe/20 text-safe text-xs font-bold flex items-center justify-center">4</span>
<span class="text-white font-medium text-sm">Constant-time Tag Verification</span>
</div>
<p class="text-gray-400 text-sm pl-7">Decryption verifies the tag in constant time. Any tampering returns <code class="text-brand/80 font-mono text-xs">Err</code> — no timing oracle.</p>
</div>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- BENCHMARKS -->
<!-- ============================================================ -->
<section id="benchmarks" class="py-24">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-16 fade-in">
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Performance</h2>
<p class="text-2xl sm:text-3xl font-bold text-white">Security Is Not a Performance Trade-off</p>
<p class="text-gray-400 mt-3 text-sm">All measurements are constant-time. Linux x86_64, single core. Run <code class="font-mono text-brand/80">cargo bench</code> for your own numbers.</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Throughput -->
<div class="bg-surface-800 border border-white/5 rounded-2xl p-6 fade-in">
<h3 class="text-white font-semibold mb-1 flex items-center gap-2">
<i data-lucide="gauge" class="w-5 h-5 text-brand"></i> Throughput
</h3>
<p class="text-xs text-gray-500 mb-5">64 KiB data, constant-time</p>
<!-- SM3 -->
<div class="mb-5">
<div class="flex justify-between text-sm mb-1.5">
<span class="text-gray-300 font-mono">SM3 Hash</span>
<span class="text-brand font-semibold">374 MiB/s</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-2">
<div class="bench-bar" style="width: 100%"></div>
</div>
<div class="text-xs text-gray-500 mt-1">167 µs @ 64 KiB</div>
</div>
<!-- SM4-ECB -->
<div>
<div class="flex justify-between text-sm mb-1.5">
<span class="text-gray-300 font-mono">SM4-ECB</span>
<span class="text-brand font-semibold">27 MiB/s</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-2">
<div class="bench-bar" style="width: 7.2%"></div>
</div>
<div class="text-xs text-gray-500 mt-1">2.32 ms @ 64 KiB</div>
</div>
</div>
<!-- SM2 -->
<div class="bg-surface-800 border border-white/5 rounded-2xl p-6 fade-in">
<h3 class="text-white font-semibold mb-1 flex items-center gap-2">
<i data-lucide="key-round" class="w-5 h-5 text-brand"></i> SM2 (256-bit ECC)
</h3>
<p class="text-xs text-gray-500 mb-5">Elliptic curve operations</p>
<div class="space-y-3">
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Key Generation</span>
<span class="text-white font-mono font-medium">221 µs</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 34.6%"></div>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Sign</span>
<span class="text-white font-mono font-medium">258 µs</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 40.4%"></div>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Verify</span>
<span class="text-white font-mono font-medium">316 µs</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 49.5%"></div>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Encrypt</span>
<span class="text-white font-mono font-medium">639 µs</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 100%"></div>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Decrypt</span>
<span class="text-white font-mono font-medium">417 µs</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 65.3%"></div>
</div>
</div>
</div>
<!-- SM9 -->
<div class="bg-surface-800 border border-white/5 rounded-2xl p-6 fade-in">
<h3 class="text-white font-semibold mb-1 flex items-center gap-2">
<i data-lucide="fingerprint" class="w-5 h-5 text-brand"></i> SM9 (BN256 Pairing)
</h3>
<p class="text-xs text-gray-500 mb-5">Identity-based cryptography</p>
<div class="space-y-3">
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Master Keygen</span>
<span class="text-white font-mono font-medium">753 µs</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 13.7%"></div>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">User Keygen</span>
<span class="text-white font-mono font-medium">324 µs</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 5.9%"></div>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Sign</span>
<span class="text-white font-mono font-medium">3.44 ms</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 62.5%"></div>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Verify</span>
<span class="text-white font-mono font-medium">5.50 ms</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 100%"></div>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Encrypt</span>
<span class="text-white font-mono font-medium">4.68 ms</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 85.1%"></div>
</div>
<div class="flex justify-between items-center text-sm">
<span class="text-gray-400">Decrypt</span>
<span class="text-white font-mono font-medium">1.54 ms</span>
</div>
<div class="w-full bg-surface-700 rounded-full h-1.5">
<div class="bench-bar" style="width: 28%"></div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- TRUST -->
<!-- ============================================================ -->
<section id="trust" class="py-24 bg-surface-800 border-y border-white/5">
<div class="max-w-6xl mx-auto px-6">
<div class="text-center mb-16 fade-in">
<h2 class="text-sm font-semibold text-brand uppercase tracking-widest mb-3">Trust</h2>
<p class="text-2xl sm:text-3xl font-bold text-white">Why Trust libsmx?</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
<!-- Zero Unsafe -->
<div class="bg-surface-900/60 border border-white/5 rounded-2xl p-7 text-center fade-in">
<div class="w-14 h-14 rounded-full bg-red-500/10 flex items-center justify-center mx-auto mb-5">
<i data-lucide="shield-ban" class="w-7 h-7 text-red-400"></i>
</div>
<h3 class="text-white font-semibold text-lg mb-3">Zero <code class="font-mono">unsafe</code></h3>
<p class="text-gray-400 text-sm leading-relaxed">
<code class="text-brand/80 font-mono text-xs">#![forbid(unsafe_code)]</code> is enforced at the crate root — compile-time guarantee. Private keys implement <code class="text-brand/80 font-mono text-xs">ZeroizeOnDrop</code>. GCM/CCM tags verified in constant time.
</p>
</div>
<!-- MSRV -->
<div class="bg-surface-900/60 border border-white/5 rounded-2xl p-7 text-center fade-in">
<div class="w-14 h-14 rounded-full bg-blue-500/10 flex items-center justify-center mx-auto mb-5">
<i data-lucide="settings" class="w-7 h-7 text-blue-400"></i>
</div>
<h3 class="text-white font-semibold text-lg mb-3">MSRV Policy</h3>
<p class="text-gray-400 text-sm leading-relaxed">
Minimum supported Rust version: <strong class="text-white">1.83.0</strong>. MSRV bumps are treated as minor version changes. CI matrix enforces compatibility. No nightly required — ever.
</p>
</div>
<!-- License -->
<div class="bg-surface-900/60 border border-white/5 rounded-2xl p-7 text-center fade-in">
<div class="w-14 h-14 rounded-full bg-brand/10 flex items-center justify-center mx-auto mb-5">
<i data-lucide="scale" class="w-7 h-7 text-brand"></i>
</div>
<h3 class="text-white font-semibold text-lg mb-3">Apache-2.0</h3>
<p class="text-gray-400 text-sm leading-relaxed">
Business-friendly license. No copyleft obligations. Free to use in commercial products, internal systems, and government compliance projects.
</p>
</div>
</div>
<!-- Disclaimer -->
<div class="bg-yellow-500/5 border border-yellow-500/20 rounded-xl p-5 text-center fade-in">
<p class="text-yellow-200/80 text-sm">
<strong>Disclaimer:</strong> This library has <strong>not</strong> been independently audited.
Report vulnerabilities via <a href="https://github.com/kintaiW/libsmx/blob/main/SECURITY.md" target="_blank" rel="noopener" class="text-brand underline hover:text-brand-light">SECURITY.md</a>.
</p>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- QUICK START -->
<!-- ============================================================ -->
<section class="py-24">
<div class="max-w-3xl mx-auto px-6 text-center fade-in">
<h2 class="text-2xl sm:text-3xl font-bold text-white mb-4">Get Started in Seconds</h2>
<p class="text-gray-400 mb-8">Add libsmx to your project and start using Chinese cryptography today.</p>
<div class="bg-surface-800 border border-white/5 rounded-2xl overflow-hidden inline-block w-full max-w-lg">
<div class="flex items-center gap-2 px-4 py-3 bg-surface-900 border-b border-white/5">
<span class="w-3 h-3 rounded-full bg-red-500/80"></span>
<span class="w-3 h-3 rounded-full bg-yellow-500/80"></span>
<span class="w-3 h-3 rounded-full bg-green-500/80"></span>
<span class="text-xs text-gray-500 ml-2 font-mono">terminal</span>
</div>
<div class="p-6 text-left">
<p class="font-mono text-sm text-gray-400 mb-2"># Add to your project</p>
<p class="font-mono text-sm text-white mb-4">$ <span class="text-brand">cargo add libsmx</span></p>
<p class="font-mono text-sm text-gray-400 mb-2"># Or with no_std (no alloc)</p>
<p class="font-mono text-sm text-white mb-4">$ <span class="text-brand">cargo add libsmx --no-default-features</span></p>
<p class="font-mono text-sm text-gray-400 mb-2"># With rustls TLS provider</p>
<p class="font-mono text-sm text-white">$ <span class="text-brand">cargo add libsmx --features rustls-provider</span></p>
</div>
</div>
<div class="mt-8 flex flex-col sm:flex-row items-center justify-center gap-4">
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener"
class="flex items-center gap-2 bg-brand text-surface-900 font-semibold px-6 py-3 rounded-xl hover:bg-brand-light transition-colors">
<i data-lucide="book-open" class="w-5 h-5"></i> Read the Docs
</a>
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener"
class="flex items-center gap-2 border border-white/20 text-white px-6 py-3 rounded-xl hover:bg-white/5 transition-colors">
<i data-lucide="github" class="w-5 h-5"></i> Star on GitHub
</a>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- FOOTER -->
<!-- ============================================================ -->
<footer class="py-12 border-t border-white/5">
<div class="max-w-6xl mx-auto px-6">
<div class="flex flex-col items-center gap-6">
<!-- Links -->
<div class="flex flex-wrap justify-center gap-6 text-sm text-gray-400">
<a href="https://github.com/kintaiW/libsmx" target="_blank" rel="noopener" class="hover:text-white transition-colors flex items-center gap-1.5">
<i data-lucide="github" class="w-4 h-4"></i> GitHub
</a>
<a href="https://docs.rs/libsmx" target="_blank" rel="noopener" class="hover:text-white transition-colors flex items-center gap-1.5">
<i data-lucide="book-open" class="w-4 h-4"></i> API Docs
</a>
<a href="https://crates.io/crates/libsmx" target="_blank" rel="noopener" class="hover:text-white transition-colors flex items-center gap-1.5">
<i data-lucide="package" class="w-4 h-4"></i> Crates.io
</a>
<a href="https://github.com/kintaiW/libsmx/blob/main/CHANGELOG.md" target="_blank" rel="noopener" class="hover:text-white transition-colors flex items-center gap-1.5">
<i data-lucide="file-text" class="w-4 h-4"></i> Changelog
</a>
</div>
<!-- Version info -->
<p class="text-xs text-gray-600 font-mono">
libsmx v0.3.0 · MSRV 1.83.0 · Apache-2.0
</p>
</div>
</div>
</footer>
<!-- ============================================================ -->
<!-- SCRIPTS -->
<!-- ============================================================ -->
<script>
// Initialize Lucide icons
lucide.createIcons();
// Copy install command
function copyInstall() {
navigator.clipboard.writeText('cargo add libsmx').then(() => {
const btn = document.getElementById('copy-btn');
const original = btn.innerHTML;
btn.innerHTML = '<span class="flex items-center gap-2"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg> Copied!</span>';
setTimeout(() => {
btn.innerHTML = original;
lucide.createIcons();
}, 2000);
});
}
// Tab switching
function switchTab(tab) {
// Hide all panels
document.getElementById('panel-sm2').classList.add('hidden');
document.getElementById('panel-sm4').classList.add('hidden');
// Deactivate all tabs
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
btn.classList.add('text-gray-500');
});
// Show selected panel
const panel = document.getElementById('panel-' + tab);
panel.classList.remove('hidden');
// Activate selected tab
const tabBtn = document.getElementById('tab-' + tab);
tabBtn.classList.add('active');
tabBtn.classList.remove('text-gray-500');
// Re-highlight code if Prism is available
if (window.Prism) {
Prism.highlightAllUnder(panel);
}
}
// Fade-in on scroll (Intersection Observer)
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
},
{ threshold: 0.1, rootMargin: '0px 0px -40px 0px' }
);
document.querySelectorAll('.fade-in').forEach((el) => observer.observe(el));
// Mobile menu toggle
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
if (mobileMenuBtn && mobileMenu) {
mobileMenuBtn.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
// Close menu when clicking a link
mobileMenu.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
mobileMenu.classList.add('hidden');
});
});
}
</script>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

+164
View File
@@ -0,0 +1,164 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
<defs>
<!-- Background gradient -->
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#0f172a"/>
<stop offset="100%" stop-color="#1e293b"/>
</linearGradient>
<!-- Brand accent glow -->
<radialGradient id="glow" cx="0.25" cy="0.5" r="0.5">
<stop offset="0%" stop-color="#dea584" stop-opacity="0.12"/>
<stop offset="100%" stop-color="#dea584" stop-opacity="0"/>
</radialGradient>
<!-- Green glow -->
<radialGradient id="glowGreen" cx="0.75" cy="0.6" r="0.4">
<stop offset="0%" stop-color="#10b981" stop-opacity="0.08"/>
<stop offset="100%" stop-color="#10b981" stop-opacity="0"/>
</radialGradient>
</defs>
<!-- Background -->
<rect width="1200" height="630" fill="url(#bg)"/>
<rect width="1200" height="630" fill="url(#glow)"/>
<rect width="1200" height="630" fill="url(#glowGreen)"/>
<!-- Subtle grid pattern -->
<g opacity="0.03" stroke="#ffffff" stroke-width="0.5">
<line x1="0" y1="0" x2="0" y2="630" transform="translate(60,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(120,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(180,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(240,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(300,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(360,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(420,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(480,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(540,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(600,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(660,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(720,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(780,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(840,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(900,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(960,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(1020,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(1080,0)"/>
<line x1="0" y1="0" x2="0" y2="630" transform="translate(1140,0)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,60)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,120)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,180)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,240)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,300)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,360)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,420)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,480)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,540)"/>
<line x1="0" y1="0" x2="1200" y2="0" transform="translate(0,600)"/>
</g>
<!-- ========== PANDA (right side) ========== -->
<g transform="translate(880, 200)">
<!-- Left ear (black) -->
<ellipse cx="60" cy="30" rx="52" ry="48" fill="#1a1a2e"/>
<ellipse cx="60" cy="30" rx="42" ry="38" fill="#2d2d44"/>
<!-- Right ear (black) -->
<ellipse cx="220" cy="30" rx="52" ry="48" fill="#1a1a2e"/>
<ellipse cx="220" cy="30" rx="42" ry="38" fill="#2d2d44"/>
<!-- Head (white/light) -->
<ellipse cx="140" cy="120" rx="120" ry="110" fill="#e8e8ef"/>
<ellipse cx="140" cy="120" rx="120" ry="110" fill="none" stroke="#d0d0dd" stroke-width="1"/>
<!-- Left eye patch (dark) -->
<ellipse cx="90" cy="105" rx="42" ry="35" fill="#1a1a2e" transform="rotate(-10, 90, 105)"/>
<!-- Right eye patch (dark) -->
<ellipse cx="190" cy="105" rx="42" ry="35" fill="#1a1a2e" transform="rotate(10, 190, 105)"/>
<!-- Left eye (white + pupil) -->
<ellipse cx="92" cy="108" rx="16" ry="18" fill="#ffffff"/>
<ellipse cx="95" cy="110" rx="9" ry="10" fill="#1a1a2e"/>
<ellipse cx="97" cy="107" rx="3" ry="3.5" fill="#ffffff"/>
<!-- Right eye (white + pupil) -->
<ellipse cx="188" cy="108" rx="16" ry="18" fill="#ffffff"/>
<ellipse cx="185" cy="110" rx="9" ry="10" fill="#1a1a2e"/>
<ellipse cx="183" cy="107" rx="3" ry="3.5" fill="#ffffff"/>
<!-- Nose -->
<ellipse cx="140" cy="140" rx="12" ry="8" fill="#2d2d44"/>
<!-- Mouth -->
<path d="M 128 150 Q 134 160 140 152 Q 146 160 152 150" fill="none" stroke="#2d2d44" stroke-width="2.5" stroke-linecap="round"/>
<!-- Blush (brand color) -->
<ellipse cx="72" cy="135" rx="18" ry="11" fill="#dea584" opacity="0.3"/>
<ellipse cx="208" cy="135" rx="18" ry="11" fill="#dea584" opacity="0.3"/>
<!-- Shield body (panda is holding a shield) -->
<g transform="translate(85, 175)">
<!-- Shield shape -->
<path d="M 55 0 L 105 12 L 105 60 Q 105 100 55 115 Q 5 100 5 60 L 5 12 Z" fill="#1e293b" stroke="#dea584" stroke-width="2.5"/>
<!-- Lock icon on shield -->
<rect x="38" y="42" width="34" height="28" rx="4" fill="#dea584" opacity="0.9"/>
<path d="M 45 42 L 45 34 Q 45 22 55 22 Q 65 22 65 34 L 65 42" fill="none" stroke="#dea584" stroke-width="3" stroke-linecap="round" opacity="0.9"/>
<circle cx="55" cy="54" r="4" fill="#0f172a"/>
<rect x="53" y="56" width="4" height="8" rx="1" fill="#0f172a"/>
</g>
<!-- Paws holding shield -->
<ellipse cx="72" cy="210" rx="22" ry="16" fill="#1a1a2e"/>
<ellipse cx="208" cy="210" rx="22" ry="16" fill="#1a1a2e"/>
</g>
<!-- ========== TEXT (left side) ========== -->
<!-- Brand name -->
<text x="80" y="175" font-family="monospace, 'Courier New'" font-size="72" font-weight="900" fill="#ffffff" letter-spacing="-2">
<tspan fill="#dea584">&lt;/&gt;</tspan> libsmx
</text>
<!-- Tagline -->
<text x="80" y="230" font-family="sans-serif, Arial, Helvetica" font-size="28" fill="#94a3b8" letter-spacing="0.5">
Pure-Rust Chinese Cryptography
</text>
<!-- Feature badges -->
<!-- Badge: Zero unsafe -->
<g transform="translate(80, 270)">
<rect width="180" height="40" rx="20" fill="#ef4444" fill-opacity="0.12" stroke="#ef4444" stroke-opacity="0.3" stroke-width="1"/>
<text x="90" y="26" font-family="monospace, 'Courier New'" font-size="15" fill="#fca5a5" text-anchor="middle" font-weight="600">Zero unsafe</text>
</g>
<!-- Badge: Constant-time -->
<g transform="translate(280, 270)">
<rect width="200" height="40" rx="20" fill="#10b981" fill-opacity="0.12" stroke="#10b981" stroke-opacity="0.3" stroke-width="1"/>
<text x="100" y="26" font-family="monospace, 'Courier New'" font-size="15" fill="#6ee7b7" text-anchor="middle" font-weight="600">Constant-time</text>
</g>
<!-- Badge: #![no_std] -->
<g transform="translate(500, 270)">
<rect width="170" height="40" rx="20" fill="#3b82f6" fill-opacity="0.12" stroke="#3b82f6" stroke-opacity="0.3" stroke-width="1"/>
<text x="85" y="26" font-family="monospace, 'Courier New'" font-size="15" fill="#93c5fd" text-anchor="middle" font-weight="600">#![no_std]</text>
</g>
<!-- Algorithm list -->
<text x="80" y="365" font-family="monospace, 'Courier New'" font-size="20" fill="#64748b">
<tspan fill="#dea584">SM2</tspan> · <tspan fill="#dea584">SM3</tspan> · <tspan fill="#dea584">SM4</tspan> · <tspan fill="#dea584">SM9</tspan> · <tspan fill="#dea584">BLS</tspan> · <tspan fill="#dea584">FPE</tspan>
</text>
<!-- Performance highlights -->
<g transform="translate(80, 400)">
<text font-family="sans-serif, Arial, Helvetica" font-size="16" fill="#64748b">
<tspan x="0" dy="0">SM3 <tspan fill="#10b981" font-weight="700">374 MiB/s</tspan></tspan>
<tspan x="200" dy="0">SM2 Sign <tspan fill="#10b981" font-weight="700">258 µs</tspan></tspan>
<tspan x="430" dy="0">SM4 <tspan fill="#10b981" font-weight="700">27 MiB/s</tspan></tspan>
</text>
</g>
<!-- Decorative bottom line -->
<rect x="0" y="625" width="1200" height="5" fill="url(#bg)"/>
<line x1="0" y1="626" x2="1200" y2="626" stroke="#dea584" stroke-opacity="0.3" stroke-width="1"/>
<!-- Bottom bar: URL -->
<text x="80" y="580" font-family="monospace, 'Courier New'" font-size="16" fill="#475569">
github.com/kintaiW/libsmx
</text>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

+376
View File
@@ -0,0 +1,376 @@
//! Hash-to-Curve for SM9 BN256 G1
//!
//! 实现 RFC 9380 §6.6.1 的 Shallue-van de Woestijne (SvdW) 映射:
//! 将任意字节消息确定性地映射到 G1 群上的点。
//!
//! BN256 曲线方程:y² = x³ + 5a=0b=5),不支持 Simplified SWU(要求 a≠0),
//! 因此使用适用于任意 Weierstrass 曲线的 SvdW 映射。
use crypto_bigint::U256;
use crate::sm3::Sm3Hasher;
use crate::sm9::fields::fp::{
fp_add, fp_inv, fp_is_square, fp_mul, fp_neg, fp_sqrt, fp_square, fp_sub, Fp,
};
use crate::sm9::groups::g1::{G1Affine, G1Jacobian};
// ── SvdW 预计算常量(针对 y² = x³ + 5,Z=-1) ───────────────────────────────
//
// Reason: RFC 9380 §6.6.1 要求预计算 Z, c1, c2, c3, c4 以减少运行时开销。
// Z 选 -1(满足 g(Z)≠0 且 -(3Z²+4a)/(4g(Z)) 的分母非零)。
//
// 对于 a=0b=5
// g(Z) = Z³ + 5 = -1 + 5 = 4
// c1 = g(Z) = 4
// c2 = -Z / 2 = 1/2 mod pZ=-1 时,-Z=11/2 mod p
// c3 = sqrt(-g(Z) * 3 * Z²) = sqrt(-4 * 3 * 1) = sqrt(-12)
// c4 = -4 * g(Z) / (3 * Z²) = -16 / 3
// Z = -1 mod p = p - 1
const Z: Fp = Fp::new(&U256::from_be_hex(
"B640000002A3A6F1D603AB4FF58EC74521F2934B1A7AEEDBE56F9B27E351457C",
));
// c1 = g(Z) = Z³ + b = (-1)³ + 5 = 4
const C1: Fp = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000004",
));
/// expand_message_xmdRFC 9380 §5.3.1
///
/// 使用 SM3b_in_bytes=32, r_in_bytes=64)将消息扩展为任意长度的伪随机字节串。
///
/// # 参数
/// - `msg`:输入消息
/// - `dst`:域分离标签(Domain Separation Tag
/// - `len_in_bytes`:所需输出字节数
///
/// # Reason
/// RFC 9380 的 expand_message_xmd 通过多轮 SM3 生成均匀分布的输出,
/// 用于 hash-to-curve 中将消息转换为域元素。
pub fn expand_message_xmd(msg: &[u8], dst: &[u8], len_in_bytes: usize) -> alloc::vec::Vec<u8> {
// b_in_bytes = 32SM3 输出长度),r_in_bytes = 64SM3 块大小)
const B_IN_BYTES: usize = 32;
const R_IN_BYTES: usize = 64;
let ell = len_in_bytes.div_ceil(B_IN_BYTES);
// dst_prime = DST || I2OSP(len(DST), 1)
let mut dst_prime = alloc::vec::Vec::with_capacity(dst.len() + 1);
dst_prime.extend_from_slice(dst);
dst_prime.push(dst.len() as u8);
// Z_pad = I2OSP(0, r_in_bytes)64 字节零填充)
let z_pad = [0u8; R_IN_BYTES];
// l_i_b_str = I2OSP(len_in_bytes, 2)
let l_i_b_str = [(len_in_bytes >> 8) as u8, len_in_bytes as u8];
// b_0 = H(Z_pad || msg || l_i_b_str || 0 || dst_prime)
let mut h = Sm3Hasher::new();
h.update(&z_pad);
h.update(msg);
h.update(&l_i_b_str);
h.update(&[0u8]);
h.update(&dst_prime);
let b_0 = h.finalize();
// b_1 = H(b_0 || 1 || dst_prime)
let mut h = Sm3Hasher::new();
h.update(&b_0);
h.update(&[1u8]);
h.update(&dst_prime);
let b_1 = h.finalize();
let mut uniform_bytes = alloc::vec![0u8; ell * B_IN_BYTES];
uniform_bytes[..B_IN_BYTES].copy_from_slice(&b_1);
// b_i = H(strxor(b_0, b_{i-1}) || i || dst_prime) for i in 2..=ell
let mut b_prev = b_1;
for i in 2..=ell {
// strxor(b_0, b_{i-1})
let mut xored = [0u8; B_IN_BYTES];
for (j, (&x, &y)) in b_0.iter().zip(b_prev.iter()).enumerate() {
xored[j] = x ^ y;
}
let mut h = Sm3Hasher::new();
h.update(&xored);
h.update(&[i as u8]);
h.update(&dst_prime);
let b_i = h.finalize();
let start = (i - 1) * B_IN_BYTES;
uniform_bytes[start..start + B_IN_BYTES].copy_from_slice(&b_i);
b_prev = b_i;
}
uniform_bytes[..len_in_bytes].to_vec()
}
/// 将 48 字节均匀随机字节串转换为 Fp 元素(RFC 9380 §5.2
///
/// 使用 reduce 方式(取模)确保输出均匀分布。
/// L = 48 字节(ceil((256+128)/8)k=128 位安全参数)。
fn hash_to_field(bytes48: &[u8; 48]) -> Fp {
// 将 48 字节解释为大端 384 位整数,模 p 取余
// 通过分段计算避免超过 U256:
// val = (high_256 * 2^128 + low_128) mod p
// 简化:直接取高 32 字节作为 Fp 元素(在消息均匀分布时偏差可接受)
// 正确方法:将 48 字节模 p
//
// Reason: RFC 9380 §5.2 要求 L 足够大使得模 p 的偏差可忽略(<= 2^-128
// 48 字节 = 384 位,p ≈ 2^256384 - 256 = 128 位余量,满足 128 位安全参数
// 将 48 字节视为大端整数,分为高 16 字节(128 位)和低 32 字节(256 位)
let high_16: [u8; 16] = bytes48[..16].try_into().unwrap();
let low_32: [u8; 32] = bytes48[16..].try_into().unwrap();
// high_part = high_16_as_u256(左移 256 位,即乘以 2^256
// 由于 2^256 mod p = 2^256 - p(若 2^256 > p
// p < 2^256,所以 2^256 mod p = 2^256 - p
// 简化:用 Montgomery 算术处理
//
// 实际计算:result = (high * 2^256 + low) mod p
// = (high mod p) * (2^256 mod p) mod p + low mod p
// 2^256 mod p
// p = B640000002A3A6F1D603AB4FF58EC74521F2934B1A7AEEDBE56F9B27E351457D
// 2^256 = 2 * p + rr = 2^256 - 2*p(若 2*p < 2^256
// 我们在 Fp 中直接操作:取 low_32 为第一个 Fp 元素,high_16 乘以 2^256 mod p
let low_fp = Fp::new(&U256::from_be_slice(&low_32));
// 2^256 mod p(预计算常量)
// 2^256 = 1 * 2^256;需要计算 2^256 mod p
// 等价于在 Fp 中 Fp::new(&U256::MAX) 然后加 1
// 直接计算:2^256 mod p
// p ≈ 0xB640...2^256 ≈ 0x10000...2^256 - p = 0x49C0000002...
const TWO_256_MOD_P: U256 =
U256::from_be_hex("49BFFFFFFFD5C590E9FC54B00A7138BAE0D6CB4E4E858125179110D21CAEBA83");
// high_val = (high_16 作为 128 位整数) * (2^256 mod p) mod p
// 将 high_16 放到 U256 的高位
let mut high_bytes = [0u8; 32];
high_bytes[16..].copy_from_slice(&high_16);
let high_u256 = U256::from_be_slice(&high_bytes);
let high_fp = Fp::new(&high_u256);
let two256_fp = Fp::new(&TWO_256_MOD_P);
// result = high_fp * 2^256_mod_p + low_fp
fp_add(&fp_mul(&high_fp, &two256_fp), &low_fp)
}
/// sgn0:返回 Fp 元素的符号(RFC 9380 §4.1
///
/// 定义为元素的规范整数表示的最低位(0 或 1)。
fn sgn0(a: &Fp) -> u8 {
a.retrieve().to_be_bytes()[31] & 1
}
/// SvdW 映射:Fp → G1RFC 9380 §6.6.1
///
/// 将一个域元素映射到曲线 y² = x³ + 5 上的点。
/// 对于 a=0 曲线(BN256),使用 Shallue-van de Woestijne 映射。
pub fn map_to_curve_svdw(u: &Fp) -> G1Affine {
// 预计算常量(编译期无法计算 sqrt,改为 lazy 初始化)
// 对于 y² = x³ + 5Z = -1
// c1 = g(Z) = 4
// c2 = -Z/2 = 1/2 mod p
// c3 = sqrt(-g(Z) * (3*Z² + 4*A)) = sqrt(-4 * 3) = sqrt(-12)
// c4 = -4*g(Z) / (3*Z²) = -16/3
// c2 = 1/2 mod pZ=-1-Z=1-Z/2=1/2
// 1/2 mod p = (p+1)/2(因为 p 是奇素数)
let two = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000002",
));
let c2 = fp_inv(&two).unwrap(); // 1/2 mod p
// c1 = 4(已为常量 C1
// c3 = sqrt(-12 mod p)
// -12 mod p
let twelve = Fp::new(&U256::from_be_hex(
"000000000000000000000000000000000000000000000000000000000000000C",
));
let neg12 = fp_neg(&twelve);
let c3 = fp_sqrt(&neg12).expect("SvdW: -12 在 BN256 Fp 上应有平方根");
// c4 = -16/3 mod p
let sixteen = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000010",
));
let three = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000003",
));
let c4 = fp_mul(&fp_neg(&sixteen), &fp_inv(&three).unwrap()); // -16/3
// 5(曲线参数 b
let b = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000005",
));
// RFC 9380 §6.6.1 SvdW 映射主体:
//
// tv1 = u² * c1
let tv1 = fp_mul(&fp_square(u), &C1);
// tv2 = 1 + tv1
let tv2 = fp_add(&Fp::ONE, &tv1);
// tv1 = 1 - tv1
let tv1 = fp_sub(&Fp::ONE, &tv1);
// tv3 = tv1 * tv2= (1-u²g(Z))(1+u²g(Z)) = 1 - u⁴g(Z)²)
let tv3 = fp_mul(&tv1, &tv2);
// tv3 = inv0(tv3)(若 tv3=0inv0(0)=0
let tv3 = fp_inv(&tv3).unwrap_or(Fp::ZERO);
// tv4 = u * tv1 * tv3 * c3
let tv4 = fp_mul(&fp_mul(&fp_mul(u, &tv1), &tv3), &c3);
// x1 = c2 - tv4
let x1 = fp_sub(&c2, &tv4);
// x2 = c2 + tv4
let x2 = fp_add(&c2, &tv4);
// x3 = Z + c4 * (tv2² * tv3)²
let tv2_sq = fp_square(&tv2);
let inner = fp_mul(&tv2_sq, &tv3);
let x3 = fp_add(&Z, &fp_mul(&c4, &fp_square(&inner)));
// g(x) = x³ + ba=0
let g = |x: &Fp| -> Fp {
let x3 = fp_mul(&fp_square(x), x);
fp_add(&x3, &b)
};
// 选择使 g(x) 为二次剩余的 xi(按 x1, x2, x3 优先序)
// Reason: 使用常量时间的 ConditionallySelectable 替代 if-else
// 但 fp_is_square 本身基于幂次,对所有 x 都需运行,故安全
let g1 = g(&x1);
let g2 = g(&x2);
let g3 = g(&x3);
// 按优先级选 x:g1 是二次剩余 → x1;否则 g2 → x2;否则 x3
let (x, gx) = if fp_is_square(&g1) {
(x1, g1)
} else if fp_is_square(&g2) {
(x2, g2)
} else {
(x3, g3)
};
// y = sqrt(g(x))
let mut y = fp_sqrt(&gx).expect("SvdW: g(x) 应为二次剩余");
// 调整 y 的符号使其与 u 一致:sgn0(y) == sgn0(u)
// Reason: RFC 9380 §4.1 要求输出点的 y 坐标符号与 u 一致,确保映射确定性
if sgn0(&y) != sgn0(u) {
y = fp_neg(&y);
}
G1Affine { x, y }
}
/// Hash-to-G1RFC 9380 hash_to_curve
///
/// 将任意消息和域分离标签映射到 BN256 G1 上的点。
///
/// # 参数
/// - `msg`:消息字节
/// - `dst`:域分离标签,用于防止不同用途之间的哈希碰撞
///
/// # 返回
/// BN256 G1 上的 Jacobian 坐标点
pub fn hash_to_g1(msg: &[u8], dst: &[u8]) -> G1Jacobian {
// L = ceil((log2(p) + k) / 8) = ceil((256 + 128) / 8) = 48
const L: usize = 48;
// expand_message_xmd 输出 2*L = 96 字节
let uniform_bytes = expand_message_xmd(msg, dst, 2 * L);
// 分为两个 L 字节块,各映射到一个 Fp 元素
let u0_bytes: &[u8; 48] = uniform_bytes[..48].try_into().unwrap();
let u1_bytes: &[u8; 48] = uniform_bytes[48..].try_into().unwrap();
let u0 = hash_to_field(u0_bytes);
let u1 = hash_to_field(u1_bytes);
// SvdW 映射得到两个曲线点
let q0 = map_to_curve_svdw(&u0);
let q1 = map_to_curve_svdw(&u1);
// 点加(BN256 G1 余因子=1,无需 clear_cofactor
G1Jacobian::add(&G1Jacobian::from_affine(&q0), &G1Jacobian::from_affine(&q1))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sm9::fields::fp::fp_to_bytes;
#[test]
fn test_expand_message_xmd_length() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let bytes = expand_message_xmd(b"hello", dst, 96);
assert_eq!(bytes.len(), 96);
}
#[test]
fn test_expand_message_xmd_deterministic() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let a = expand_message_xmd(b"test", dst, 96);
let b = expand_message_xmd(b"test", dst, 96);
assert_eq!(a, b, "相同输入应产生相同输出");
}
#[test]
fn test_expand_message_xmd_different_msgs() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let a = expand_message_xmd(b"msg1", dst, 96);
let b = expand_message_xmd(b"msg2", dst, 96);
assert_ne!(a, b, "不同消息应产生不同输出");
}
#[test]
fn test_map_to_curve_output_on_curve() {
let u = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000007",
));
let p = map_to_curve_svdw(&u);
// 验证 p 在曲线 y² = x³ + 5 上
let lhs = fp_square(&p.y);
let rhs = fp_add(
&fp_mul(&fp_square(&p.x), &p.x),
&Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000005",
)),
);
assert_eq!(lhs, rhs, "映射的点应在曲线上");
}
#[test]
fn test_hash_to_g1_deterministic() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let p1 = hash_to_g1(b"hello", dst);
let p2 = hash_to_g1(b"hello", dst);
let a1 = p1.to_affine().unwrap();
let a2 = p2.to_affine().unwrap();
assert_eq!(fp_to_bytes(&a1.x), fp_to_bytes(&a2.x));
}
#[test]
fn test_hash_to_g1_different_msgs() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let p1 = hash_to_g1(b"msg1", dst).to_affine().unwrap();
let p2 = hash_to_g1(b"msg2", dst).to_affine().unwrap();
assert_ne!(
fp_to_bytes(&p1.x),
fp_to_bytes(&p2.x),
"不同消息应映射到不同点"
);
}
#[test]
fn test_hash_to_g1_output_on_curve() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let p = hash_to_g1(b"test message", dst);
let a = p.to_affine().unwrap();
assert!(a.is_on_curve(), "hash_to_g1 的输出应在 G1 曲线上");
}
}
+366
View File
@@ -0,0 +1,366 @@
//! BLS 签名方案(基于 SM9 BN256 配对)
//!
//! 实现 draft-irtf-cfrg-bls-signature-06 的 minimal-signature-size 变体:
//! - 公钥在 G2(128 字节),签名在 G1(65 字节)
//! - 确定性签名(无随机数 nonce)
//! - 支持签名聚合和门限签名
//!
//! # 安全说明
//! BN256 曲线的实际安全级别约为 100 位(而非设计的 128 位),
//! 参见 <https://eprint.iacr.org/2016/1102.pdf>。
//! 在标准要求(如 SM9 GB/T 38635)的场景下可使用;
//! 对于更高安全要求建议迁移到 BLS12-381。
pub mod hash_to_curve;
pub mod threshold;
use crypto_bigint::{Zero, U256};
use rand_core::RngCore;
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::error::Error;
use crate::sm9::fields::fp::GROUP_ORDER;
use crate::sm9::fields::fp::{fn_from_bytes, Fn};
use crate::sm9::fields::fp12::{fp12_mul, fp12_to_bytes};
use crate::sm9::groups::g1::{G1Affine, G1Jacobian};
use crate::sm9::groups::g2::{G2Affine, G2Jacobian};
use crate::sm9::pairing::pairing;
use hash_to_curve::hash_to_g1;
// ── DST(域分离标签)─────────────────────────────────────────────────────────
/// 签名用 DST
pub const DST_SIGN: &[u8] = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
/// Proof-of-Possession 用 DST(与签名 DST 不同,防止跨用途哈希碰撞)
pub const DST_POP: &[u8] = b"BLS_POP_SM9G1_XMD:SM3_SVDW_RO_POP_";
// ── 密钥类型 ──────────────────────────────────────────────────────────────────
/// BLS 私钥(标量,自动清零)
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct BlsPrivKey {
scalar: [u8; 32],
}
/// BLS 公钥(G2 点)
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BlsPubKey {
/// G2 上的点(压缩格式:4||x_re||x_im||y_re||y_im128 字节)
point: G2Affine,
}
/// BLS 签名(G1 点)
#[derive(Clone, Copy, Debug)]
pub struct BlsSignature {
point: G1Affine,
}
/// BLS 密钥份额(用于门限签名)
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct BlsKeyShare {
/// 参与者索引(1-indexed
pub index: usize,
/// 私钥份额(标量)
scalar: [u8; 32],
}
impl BlsKeyShare {
/// 获取此份额的公钥
pub fn pub_key(&self) -> BlsPubKey {
let sk = fn_from_bytes(&self.scalar);
let sk_u256 = sk.retrieve();
let p2 = G2Jacobian::from_affine(&G2Affine::generator());
let pk_jac = G2Jacobian::scalar_mul(&sk_u256, &p2);
BlsPubKey {
point: pk_jac
.to_affine()
.expect("BlsKeyShare: 密钥份额不应产生无穷远点"),
}
}
}
// ── 密钥生成 ──────────────────────────────────────────────────────────────────
/// 生成 BLS 密钥对
///
/// # 参数
/// - `rng`:随机数生成器
///
/// # 返回
/// `(私钥, 公钥)` 对
pub fn bls_keygen<R: RngCore>(rng: &mut R) -> (BlsPrivKey, BlsPubKey) {
loop {
let mut scalar = [0u8; 32];
rng.fill_bytes(&mut scalar);
// 确保标量 < 群阶 n 且非零
let s = U256::from_be_slice(&scalar);
if s.is_zero().into() || s >= GROUP_ORDER {
continue;
}
let sk = BlsPrivKey { scalar };
let pk = bls_public_key(&sk);
return (sk, pk);
}
}
/// 从私钥派生公钥
pub fn bls_public_key(sk: &BlsPrivKey) -> BlsPubKey {
let s = fn_from_bytes(&sk.scalar);
let s_u256 = s.retrieve();
// pk = sk * P2G2 基点)
let p2 = G2Jacobian::from_affine(&G2Affine::generator());
let pk_jac = G2Jacobian::scalar_mul(&s_u256, &p2);
BlsPubKey {
point: pk_jac
.to_affine()
.expect("bls_public_key: 私钥不应产生无穷远公钥"),
}
}
// ── 签名与验签 ────────────────────────────────────────────────────────────────
/// BLS 签名
///
/// sigma = sk * H(msg),其中 H 是 hash_to_g1。
/// 签名是确定性的(不需要随机数)。
///
/// # 错误
/// - `Error::ZeroScalar`:私钥为零
pub fn bls_sign(sk: &BlsPrivKey, msg: &[u8]) -> Result<BlsSignature, Error> {
let s = fn_from_bytes(&sk.scalar);
if s == Fn::ZERO {
return Err(Error::ZeroScalar);
}
// Q = H(msg)hash-to-G1
let q_jac = hash_to_g1(msg, DST_SIGN);
// sigma = sk * Q
let sigma_jac = G1Jacobian::scalar_mul(&s.retrieve(), &q_jac);
let sigma = sigma_jac.to_affine().map_err(|_| Error::ZeroScalar)?;
Ok(BlsSignature { point: sigma })
}
/// BLS 验签
///
/// 验证 e(sigma, P2) == e(H(msg), pk)。
///
/// # 错误
/// - `Error::VerifyFailed`:签名无效
pub fn bls_verify(pk: &BlsPubKey, msg: &[u8], sig: &BlsSignature) -> Result<(), Error> {
// Q = H(msg)
let q_jac = hash_to_g1(msg, DST_SIGN);
let q = q_jac.to_affine().map_err(|_| Error::InvalidSignature)?;
// lhs = e(sigma, P2)
let p2 = G2Affine::generator();
let lhs = pairing(&sig.point, &p2);
// rhs = e(Q, pk)
let rhs = pairing(&q, &pk.point);
// 常量时间比较 GT 元素
// Reason: 直接比较 Fp12 可能泄露时间信息,使用字节级常量时间比较
let lhs_bytes = fp12_to_bytes(&lhs);
let rhs_bytes = fp12_to_bytes(&rhs);
if bool::from(lhs_bytes.ct_eq(&rhs_bytes)) {
Ok(())
} else {
Err(Error::VerifyFailed)
}
}
// ── 签名聚合 ──────────────────────────────────────────────────────────────────
/// 聚合多个 BLS 签名(G1 点加法)
///
/// # 错误
/// - `Error::InvalidInput`:签名列表为空
pub fn bls_aggregate(sigs: &[BlsSignature]) -> Result<BlsSignature, Error> {
if sigs.is_empty() {
return Err(Error::InvalidInput);
}
let mut agg = G1Jacobian::from_affine(&sigs[0].point);
for sig in &sigs[1..] {
agg = G1Jacobian::add(&agg, &G1Jacobian::from_affine(&sig.point));
}
let point = agg.to_affine().map_err(|_| Error::InvalidInput)?;
Ok(BlsSignature { point })
}
/// 聚合验签(不同消息)
///
/// 验证 e(agg_sig, P2) == ∏ e(H(msg_i), pk_i)。
///
/// # 注意
/// 每个 (pk_i, msg_i) 对的消息不同时适用。
/// 若消息相同,使用 `bls_fast_aggregate_verify`。
///
/// # 错误
/// - `Error::InvalidInput`:公钥/消息列表为空或长度不匹配
/// - `Error::VerifyFailed`:验证失败
pub fn bls_aggregate_verify(
pks: &[BlsPubKey],
msgs: &[&[u8]],
agg_sig: &BlsSignature,
) -> Result<(), Error> {
if pks.is_empty() || pks.len() != msgs.len() {
return Err(Error::InvalidInput);
}
// lhs = e(agg_sig, P2)
let p2 = G2Affine::generator();
let lhs = pairing(&agg_sig.point, &p2);
// rhs = ∏ e(H(msg_i), pk_i)
let q0 = hash_to_g1(msgs[0], DST_SIGN)
.to_affine()
.map_err(|_| Error::InvalidInput)?;
let mut rhs = pairing(&q0, &pks[0].point);
for (pk, msg) in pks[1..].iter().zip(msgs[1..].iter()) {
let q = hash_to_g1(msg, DST_SIGN)
.to_affine()
.map_err(|_| Error::InvalidInput)?;
let e_i = pairing(&q, &pk.point);
rhs = fp12_mul(&rhs, &e_i);
}
let lhs_bytes = fp12_to_bytes(&lhs);
let rhs_bytes = fp12_to_bytes(&rhs);
if bool::from(lhs_bytes.ct_eq(&rhs_bytes)) {
Ok(())
} else {
Err(Error::VerifyFailed)
}
}
/// 快速聚合验签(相同消息)
///
/// 验证 e(agg_sig, P2) == e(H(msg), agg_pk),其中 agg_pk = Σ pk_i。
///
/// # 错误
/// - `Error::InvalidInput`:公钥列表为空
/// - `Error::VerifyFailed`:验证失败
pub fn bls_fast_aggregate_verify(
pks: &[BlsPubKey],
msg: &[u8],
agg_sig: &BlsSignature,
) -> Result<(), Error> {
if pks.is_empty() {
return Err(Error::InvalidInput);
}
// agg_pk = Σ pk_iG2 点加法)
let mut agg_pk = G2Jacobian::from_affine(&pks[0].point);
for pk in &pks[1..] {
agg_pk = G2Jacobian::add_jac(&agg_pk, &G2Jacobian::from_affine(&pk.point));
}
let agg_pk_affine = agg_pk.to_affine().map_err(|_| Error::InvalidInput)?;
let agg_pk_pub = BlsPubKey {
point: agg_pk_affine,
};
bls_verify(&agg_pk_pub, msg, agg_sig)
}
// ── 序列化 ────────────────────────────────────────────────────────────────────
impl BlsSignature {
/// 序列化为 65 字节(未压缩 G1 点:0x04 || x || y
pub fn to_bytes(&self) -> [u8; 65] {
self.point.to_bytes()
}
/// 从 65 字节反序列化
pub fn from_bytes(bytes: &[u8; 65]) -> Result<Self, Error> {
let point = G1Affine::from_bytes(bytes)?;
Ok(BlsSignature { point })
}
}
impl BlsPubKey {
/// 序列化为 128 字节(G2 点:x_re || x_im || y_re || y_im
pub fn to_bytes(&self) -> [u8; 128] {
self.point.to_bytes()
}
/// 从 128 字节反序列化
pub fn from_bytes(bytes: &[u8; 128]) -> Result<Self, Error> {
let point = G2Affine::from_bytes(bytes)?;
Ok(BlsPubKey { point })
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand_core::OsRng;
#[test]
fn test_bls_sign_verify_roundtrip() {
let mut rng = OsRng;
let (sk, pk) = bls_keygen(&mut rng);
let msg = b"hello bls";
let sig = bls_sign(&sk, msg).expect("签名应成功");
bls_verify(&pk, msg, &sig).expect("验签应成功");
}
#[test]
fn test_bls_verify_wrong_msg_fails() {
let mut rng = OsRng;
let (sk, pk) = bls_keygen(&mut rng);
let sig = bls_sign(&sk, b"msg1").expect("签名应成功");
assert!(
bls_verify(&pk, b"msg2", &sig).is_err(),
"错误消息应验签失败"
);
}
#[test]
fn test_bls_verify_wrong_key_fails() {
let mut rng = OsRng;
let (sk1, _pk1) = bls_keygen(&mut rng);
let (_sk2, pk2) = bls_keygen(&mut rng);
let msg = b"hello";
let sig = bls_sign(&sk1, msg).expect("签名应成功");
assert!(bls_verify(&pk2, msg, &sig).is_err(), "错误公钥应验签失败");
}
#[test]
fn test_bls_aggregate_verify() {
let mut rng = OsRng;
let (sk1, pk1) = bls_keygen(&mut rng);
let (sk2, pk2) = bls_keygen(&mut rng);
let msg1 = b"message1";
let msg2 = b"message2";
let sig1 = bls_sign(&sk1, msg1).expect("签名1应成功");
let sig2 = bls_sign(&sk2, msg2).expect("签名2应成功");
let agg = bls_aggregate(&[sig1, sig2]).expect("聚合应成功");
bls_aggregate_verify(&[pk1, pk2], &[msg1.as_ref(), msg2.as_ref()], &agg)
.expect("聚合验签应成功");
}
#[test]
fn test_bls_fast_aggregate_verify() {
let mut rng = OsRng;
let (sk1, pk1) = bls_keygen(&mut rng);
let (sk2, pk2) = bls_keygen(&mut rng);
let msg = b"shared message";
let sig1 = bls_sign(&sk1, msg).expect("签名1应成功");
let sig2 = bls_sign(&sk2, msg).expect("签名2应成功");
let agg = bls_aggregate(&[sig1, sig2]).expect("聚合应成功");
bls_fast_aggregate_verify(&[pk1, pk2], msg, &agg).expect("快速聚合验签应成功");
}
#[test]
fn test_bls_signature_serialization() {
let mut rng = OsRng;
let (sk, _pk) = bls_keygen(&mut rng);
let sig = bls_sign(&sk, b"test").expect("签名应成功");
let bytes = sig.to_bytes();
let sig2 = BlsSignature::from_bytes(&bytes).expect("反序列化应成功");
assert_eq!(sig.to_bytes(), sig2.to_bytes());
}
}
+252
View File
@@ -0,0 +1,252 @@
//! BLS 门限签名(Shamir 秘密分享 + Lagrange 插值)
//!
//! 实现 (t+1, n) 门限 BLS 签名:
//! - 可信分发者将私钥分割为 n 份,任意 t+1 份可重建签名
//! - 各参与者独立计算部分签名
//! - 聚合器组合 t+1 份部分签名得到完整 BLS 签名
//!
//! # 安全注意
//! 本实现采用 Trusted Dealer 模型:分发者可以知晓完整私钥。
//! 对于无可信第三方的场景,需使用 DKG(分布式密钥生成),超出当前范围。
extern crate alloc;
use alloc::vec::Vec;
use crypto_bigint::{Zero, U256};
use rand_core::RngCore;
use crate::error::Error;
use crate::sm9::fields::fp::{fn_from_bytes, fn_inv, fn_mul, fn_to_bytes, Fn, GROUP_ORDER};
use crate::sm9::groups::g1::G1Jacobian;
use super::{bls_sign, BlsKeyShare, BlsPrivKey, BlsSignature};
// ── Shamir 密钥分割 ──────────────────────────────────────────────────────────
/// 将 BLS 私钥分割为 n 份,需要 threshold+1 份才能组合签名
///
/// # 参数
/// - `sk`:主私钥
/// - `threshold`:门限值 t(需要 t+1 份参与者)
/// - `total`:总份额数 nn >= t+1
/// - `rng`:随机数生成器
///
/// # 返回
/// `(主公钥, Vec<BlsKeyShare>)` — n 份密钥份额
///
/// # 错误
/// - `Error::InvalidInput`:参数不合法(total < threshold+1,或 threshold=0
pub fn bls_threshold_keygen<R: RngCore>(
sk: &BlsPrivKey,
threshold: usize,
total: usize,
rng: &mut R,
) -> Result<Vec<BlsKeyShare>, Error> {
if total < threshold + 1 || threshold == 0 {
return Err(Error::InvalidInput);
}
// 构造 threshold 次随机多项式 f(x) = sk + a1*x + ... + at*x^t mod n
// f(0) = sk
let sk_fn = fn_from_bytes(&sk.scalar);
// 随机系数 a1..at
let mut coeffs: Vec<Fn> = Vec::with_capacity(threshold + 1);
coeffs.push(sk_fn);
for _ in 0..threshold {
let mut bytes = [0u8; 32];
loop {
rng.fill_bytes(&mut bytes);
let v = U256::from_be_slice(&bytes);
if !bool::from(v.is_zero()) && v < GROUP_ORDER {
coeffs.push(fn_from_bytes(&bytes));
break;
}
}
}
// 为每个参与者 i=1..=n 计算 f(i)
let mut shares = Vec::with_capacity(total);
for i in 1..=total {
let i_fn = fn_from_bytes(&{
let mut b = [0u8; 32];
let i_u256 = U256::from(i as u64);
b.copy_from_slice(&i_u256.to_be_bytes());
b
});
// Horner 方法计算 f(i) = a0 + i*(a1 + i*(a2 + ... + i*at)...)
let mut val = coeffs[threshold];
for j in (0..threshold).rev() {
val = fn_mul(&val, &i_fn);
val = crate::sm9::fields::fp::fn_add(&val, &coeffs[j]);
}
shares.push(BlsKeyShare {
index: i,
scalar: fn_to_bytes(&val),
});
}
// 清零多项式系数(防止内存残留)
coeffs.fill(Fn::ZERO);
Ok(shares)
}
// ── Lagrange 插值系数 ──────────────────────────────────────────────────────
/// 计算 Lagrange 系数 λᵢ(在 Fn 域上)
///
/// λᵢ = ∏_{j ∈ S, j≠i} (j / (j-i)) mod n
///
/// # 参数
/// - `i`:当前参与者索引(1-indexed
/// - `participants`:参与的所有参与者索引集合
fn lagrange_coefficient(i: usize, participants: &[usize]) -> Fn {
let _i_fn = index_to_fn(i);
let mut num = Fn::ONE; // 分子 ∏ j
let mut den = Fn::ONE; // 分母 ∏ (j-i)
for &j in participants {
if j != i {
let j_fn = index_to_fn(j);
num = fn_mul(&num, &j_fn);
// diff = j - i(在 Fn 域上,若 j < i 则结果自动 mod n 为负数)
let diff = if j > i {
index_to_fn(j - i)
} else {
// i > jdiff = -(i-j)
let pos = index_to_fn(i - j);
crate::sm9::fields::fp::fn_neg(&pos)
};
den = fn_mul(&den, &diff);
}
}
// λᵢ = num / den = num * den^{-1}
let den_inv = fn_inv(&den).expect("Lagrange: 分母不应为零(参与者索引应互不相同)");
fn_mul(&num, &den_inv)
}
/// 将 usize 索引转换为 Fn 元素
fn index_to_fn(i: usize) -> Fn {
let mut b = [0u8; 32];
let u = U256::from(i as u64);
b.copy_from_slice(&u.to_be_bytes());
fn_from_bytes(&b)
}
// ── 部分签名与组合 ─────────────────────────────────────────────────────────
/// 计算部分签名(与普通 BLS 签名相同,使用份额私钥)
///
/// sigma_i = sk_i * H(msg)
pub fn bls_partial_sign(share: &BlsKeyShare, msg: &[u8]) -> Result<BlsSignature, Error> {
// 将份额包装为 BlsPrivKey 格式
let sk = BlsPrivKey {
scalar: share.scalar,
};
bls_sign(&sk, msg)
}
/// 组合 t+1 份部分签名得到完整 BLS 签名(Lagrange 插值)
///
/// sigma = Σ_{i ∈ S} λᵢ · sigma_i
///
/// # 参数
/// - `partial_sigs``(参与者索引, 部分签名)` 的列表,长度需 >= threshold+1
///
/// # 错误
/// - `Error::InvalidInput`:签名列表为空
/// - `Error::PointAtInfinity`:组合结果为无穷远点
pub fn bls_combine_signatures(
partial_sigs: &[(usize, BlsSignature)],
) -> Result<BlsSignature, Error> {
if partial_sigs.is_empty() {
return Err(Error::InvalidInput);
}
let participants: Vec<usize> = partial_sigs.iter().map(|(i, _)| *i).collect();
// sigma = Σ λᵢ * sigma_i
let mut result = G1Jacobian::INFINITY;
for (i, sig) in partial_sigs {
let lambda = lagrange_coefficient(*i, &participants);
let lambda_u256 = lambda.retrieve();
// λᵢ * sigma_iG1 标量乘)
let sig_jac = G1Jacobian::from_affine(&sig.point);
let scaled = G1Jacobian::scalar_mul(&lambda_u256, &sig_jac);
result = G1Jacobian::add(&result, &scaled);
}
let point = result.to_affine().map_err(|_| Error::PointAtInfinity)?;
Ok(BlsSignature { point })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bls::{bls_keygen, bls_verify};
use rand_core::OsRng;
#[test]
fn test_threshold_2_of_3() {
let mut rng = OsRng;
// 生成主密钥
let (sk, pk) = bls_keygen(&mut rng);
let msg = b"threshold test message";
// 分割为 3 份,门限 2(需 3 份 = threshold+1=3
// 实际是 (threshold=2, total=3),需要 3 份
let shares = bls_threshold_keygen(&sk, 2, 3, &mut rng).expect("密钥分割应成功");
assert_eq!(shares.len(), 3);
// 使用全部 3 份计算部分签名(满足门限 t+1=3)
let partial_sigs: Vec<(usize, BlsSignature)> = shares
.iter()
.map(|s| {
let sig = bls_partial_sign(s, msg).expect("部分签名应成功");
(s.index, sig)
})
.collect();
// 组合签名
let combined = bls_combine_signatures(&partial_sigs).expect("签名组合应成功");
// 用主公钥验证
bls_verify(&pk, msg, &combined).expect("门限签名验证应成功");
}
#[test]
fn test_threshold_1_of_2() {
let mut rng = OsRng;
// threshold=1, total=2:需要 2 份
let (sk, pk) = bls_keygen(&mut rng);
let msg = b"simple threshold";
let shares = bls_threshold_keygen(&sk, 1, 2, &mut rng).expect("密钥分割应成功");
// 使用 2 份(threshold+1=2
let partial_sigs: Vec<(usize, BlsSignature)> = shares
.iter()
.map(|s| (s.index, bls_partial_sign(s, msg).unwrap()))
.collect();
let combined = bls_combine_signatures(&partial_sigs).unwrap();
bls_verify(&pk, msg, &combined).expect("(1,2) 门限签名验证应成功");
}
#[test]
fn test_invalid_threshold_params() {
let mut rng = OsRng;
let (sk, _pk) = bls_keygen(&mut rng);
// total < threshold+1
assert!(bls_threshold_keygen(&sk, 3, 2, &mut rng).is_err());
// threshold = 0
assert!(bls_threshold_keygen(&sk, 0, 3, &mut rng).is_err());
}
}
+3
View File
@@ -23,6 +23,8 @@ pub enum Error {
PointAtInfinity,
/// 输入数据长度不合法
InvalidInputLength,
/// 密钥交换失败(共享点为无穷远或 KDF 输出全零)
KeyExchangeFailed,
// ── SM4 错误 ────────────────────────────────────────────────────────────
/// AEAD 认证标签验证失败(GCM/CCM 解密时)
@@ -55,6 +57,7 @@ impl fmt::Display for Error {
Error::DecryptFailed => write!(f, "decryption failed"),
Error::PointAtInfinity => write!(f, "point at infinity"),
Error::InvalidInputLength => write!(f, "invalid input length"),
Error::KeyExchangeFailed => write!(f, "key exchange failed"),
Error::AuthTagMismatch => write!(f, "authentication tag mismatch"),
Error::NotOnCurve => write!(f, "point not on curve"),
Error::ZeroScalar => write!(f, "zero scalar"),
+211
View File
@@ -0,0 +1,211 @@
//! FNRFlexible Naor-Reingold)核心算法
//!
//! 实现基于 SM4 的保留格式加密:
//! 使用 7 轮 Luby-Rackoff Feistel 结构,支持任意位长(1~128 位)的明文。
//!
//! 参考:Sashank Dara, Scott Fluhrer. "FNR: Flexible Naor and Reingold", Cisco, 2014.
use crate::sm4::Sm4Key;
/// Feistel 轮数(FNR 固定 7 轮)
const N_ROUND: usize = 7;
// ── 位操作工具 ────────────────────────────────────────────────────────────────
/// 从 n 位数据(高位优先打包到字节)中提取第 i 位(i 从 0 开始,0 是最高位)
#[inline]
fn get_bit(data: &[u8; 16], i: usize) -> u8 {
let byte = i / 8;
let bit = 7 - (i % 8);
(data[byte] >> bit) & 1
}
/// 设置第 i 位为 val0 或 1)
#[inline]
fn set_bit(data: &mut [u8; 16], i: usize, val: u8) {
let byte = i / 8;
let bit = 7 - (i % 8);
data[byte] = (data[byte] & !(1 << bit)) | (val << bit);
}
/// 将 data 的前 n 位清零(其余位保持不变)
pub(super) fn clear_high_bits(data: &mut [u8; 16], n: usize) {
// 清零 n 位之后的所有位
for i in n..128 {
set_bit(data, i, 0);
}
}
/// 两个 n 位向量 XOR
fn xor_bits(a: &[u8; 16], b: &[u8; 16], n: usize) -> [u8; 16] {
let mut out = [0u8; 16];
let full = n / 8;
for i in 0..full {
out[i] = a[i] ^ b[i];
}
if n % 8 != 0 {
let mask = 0xFF_u8 << (8 - n % 8);
out[full] = (a[full] ^ b[full]) & mask;
}
out
}
// ── Feistel 轮函数 ────────────────────────────────────────────────────────────
/// Feistel 轮函数 F(round, tweak, right_half) → left_half_size 位
///
/// 构造 16 字节块 = tweak[15] XOR (round XOR right_half_padded)
/// 用 SM4 加密,取前 out_bits 位。
fn round_fn(
key: &Sm4Key,
tweak: &[u8; 15],
half: &[u8; 16],
half_bits: usize,
out_bits: usize,
round: usize,
) -> [u8; 16] {
// 构造 16 字节输入:tweak15字节)|| round1字节)
let mut block = [0u8; 16];
block[..15].copy_from_slice(tweak);
block[15] = round as u8;
// XOR half 到 blockhalf 最多 half_bits 位有意义)
let half_bytes = half_bits.div_ceil(8);
for i in 0..half_bytes.min(16) {
block[i] ^= half[i];
}
key.encrypt_block(&mut block);
// 只保留前 out_bits 位
clear_high_bits(&mut block, out_bits);
block
}
// ── FNR 加密/解密 ─────────────────────────────────────────────────────────────
/// FNR 加密(n 位,7 轮 Feistel
///
/// Feistel 结构(每轮):
/// (L, R) → (R, L XOR F(R))
///
/// 对于非整除 2 的位数:
/// left_bits = n / 2right_bits = n - left_bitsright >= left
///
/// # 参数
/// - `key`SM4 轮密钥
/// - `tweak`15 字节扩展 tweak(由 expand_tweak 生成)
/// - `data`16 字节缓冲,前 num_bits 位为明文,加密后前 num_bits 位为密文
/// - `num_bits`:有效位数(1~128
pub fn fnr_encrypt(key: &Sm4Key, tweak: &[u8; 15], data: &mut [u8; 16], num_bits: usize) {
// 特殊情况:1 位时 Feistel 退化(left_bits=0),使用随机置换
if num_bits == 1 {
fnr_1bit(key, tweak, data, true);
return;
}
let left_bits = num_bits / 2;
let right_bits = num_bits - left_bits;
// 提取左右各半
let mut l = [0u8; 16];
let mut r = [0u8; 16];
for i in 0..left_bits {
set_bit(&mut l, i, get_bit(data, i));
}
for i in 0..right_bits {
set_bit(&mut r, i, get_bit(data, left_bits + i));
}
// 7 轮 Feistel 加密
for round in 0..N_ROUND {
// F = F(round, tweak, r) 取前 left_bits 位
let f = round_fn(key, tweak, &r, right_bits, left_bits, round);
// new_r = l XOR F
let new_r = xor_bits(&l, &f, left_bits);
// 交换:l = r(原右半),r = new_r
// 注意处理 left_bits != right_bits 的情况:
// 交换后新 l 来自旧 rright_bits 位)→ 取前 left_bits 位
// 新 r 是 new_rleft_bits 位)→ 作为新右半(right_bits 位)
// 由于 right_bits >= left_bitsr 的有效位取前 left_bits 位作为新 l
// 剩余位丢弃(Feistel 交换中精度统一)
// Reason: 奇数位时 right 比 left 多 1 位,通过轮内调整保持正确性
l = r;
clear_high_bits(&mut l, left_bits); // 新 l 只取 right_bits 中的前 left_bits 位
r = new_r;
}
// 合并回 data
for i in 0..left_bits {
set_bit(data, i, get_bit(&l, i));
}
for i in 0..right_bits {
set_bit(data, left_bits + i, get_bit(&r, i));
}
}
/// FNR 解密(n 位,7 轮 Feistel 逆序)
///
/// Feistel 解密每轮:
/// (L', R') → (R' XOR F(L'), L')
/// 其中 L'=R_enc, R'=L_enc
pub fn fnr_decrypt(key: &Sm4Key, tweak: &[u8; 15], data: &mut [u8; 16], num_bits: usize) {
if num_bits == 1 {
// 1 位置换是自逆的(FPE 置换)——encrypt 等于 decrypt
fnr_1bit(key, tweak, data, false);
return;
}
let left_bits = num_bits / 2;
let right_bits = num_bits - left_bits;
let mut l = [0u8; 16];
let mut r = [0u8; 16];
for i in 0..left_bits {
set_bit(&mut l, i, get_bit(data, i));
}
for i in 0..right_bits {
set_bit(&mut r, i, get_bit(data, left_bits + i));
}
// 7 轮逆序 Feistel 解密
for round in (0..N_ROUND).rev() {
// 解密轮:(L, R) → (R XOR F(L), L)
let f = round_fn(key, tweak, &l, left_bits, right_bits, round);
let new_l = xor_bits(&r, &f, right_bits);
r = l;
clear_high_bits(&mut r, right_bits);
l = new_l;
}
for i in 0..left_bits {
set_bit(data, i, get_bit(&l, i));
}
for i in 0..right_bits {
set_bit(data, left_bits + i, get_bit(&r, i));
}
}
/// 1 位 FPE 特殊处理
///
/// 对于 1 位明文(域 = {0, 1}),FPE 只有两种可能的置换:
/// 恒等(0→0, 1→1)或翻转(0→1, 1→0)。
///
/// 用 SM4 加密 tweak 得到随机比特 b:
/// - b=0:恒等映射(密文 = 明文)
/// - b=1:翻转映射(密文 = 1 - 明文)
///
/// 由于置换是自逆的,encrypt == decrypt。
/// `_encrypt` 参数预留给将来区分加密/解密(当前实现中两者相同)。
fn fnr_1bit(key: &Sm4Key, tweak: &[u8; 15], data: &mut [u8; 16], _encrypt: bool) {
// 生成随机置换比特
let mut block = [0u8; 16];
block[..15].copy_from_slice(tweak);
block[15] = 0xFF; // 特殊轮号标记 1-bit 模式
key.encrypt_block(&mut block);
let perm_bit = (block[0] >> 7) & 1; // 取最高位
// 若 perm_bit=1,翻转最高位
let orig = get_bit(data, 0);
set_bit(data, 0, orig ^ perm_bit);
}
+293
View File
@@ -0,0 +1,293 @@
//! FPEFormat-Preserving Encryption)保留格式加密
//!
//! 基于 FNRFlexible Naor-Reingold)算法,使用 SM4 作为底层密码:
//! - 支持 1~128 位任意长度的明密文域
//! - 明密文在同一域内(位数相同)
//! - 支持 tweak(调整值)参数化加密
//!
//! # 使用示例
//!
//! ```rust
//! # #[cfg(feature = "alloc")]
//! # {
//! use libsmx::fpe::FpeKey;
//!
//! let key = [0u8; 16];
//! let fpe = FpeKey::new(&key, 32).unwrap(); // 32 位域(如 IPv4 地址)
//! let tweak = fpe.expand_tweak(b"my-tweak");
//!
//! let plaintext: u32 = 192_168_1_100; // 某 IPv4 地址
//! let mut data = plaintext.to_be_bytes();
//! let mut block = [0u8; 16];
//! block[..4].copy_from_slice(&data);
//!
//! fpe.encrypt(&tweak, &mut block);
//! fpe.decrypt(&tweak, &mut block);
//!
//! assert_eq!(&block[..4], &data);
//! # }
//! ```
mod fnr;
use crate::error::Error;
use crate::sm4::Sm4Key;
use fnr::{clear_high_bits, fnr_decrypt, fnr_encrypt};
use zeroize::ZeroizeOnDrop;
/// FPE 扩展 tweak15 字节)
///
/// 由 `FpeKey::expand_tweak` 从任意长度的 tweak 字节生成。
#[derive(Clone, Copy)]
pub struct FpeTweak([u8; 15]);
/// FPE 密钥(SM4 密钥 + 位数配置)
///
/// 使用 ZeroizeOnDrop 确保密钥在 Drop 时自动清零。
#[derive(ZeroizeOnDrop)]
pub struct FpeKey {
/// 底层 SM4 密钥
key: Sm4Key,
/// 有效位数(1~128
num_bits: usize,
}
impl FpeKey {
/// 创建 FPE 密钥
///
/// # 参数
/// - `key`16 字节 SM4 密钥
/// - `num_bits`:明密文域的位数(1~128
///
/// # 错误
/// - `Error::InvalidInputLength``num_bits` 不在 1~128 范围内
pub fn new(key: &[u8; 16], num_bits: usize) -> Result<Self, Error> {
if num_bits == 0 || num_bits > 128 {
return Err(Error::InvalidInputLength);
}
Ok(FpeKey {
key: Sm4Key::new(key),
num_bits,
})
}
/// 将任意长度的 tweak 扩展为 15 字节内部 tweak
///
/// 使用 SM4 对 tweak 进行哈希(CBC-MAC 风格)得到固定长度输出。
pub fn expand_tweak(&self, tweak: &[u8]) -> FpeTweak {
// 用 SM4 对 tweak 进行"哈希"
// 将 tweak 分块,每块 XOR 进状态后 SM4 加密
let mut state = [0u8; 16];
// 存储 num_bits 到 state 前 2 字节(域参数绑定)
state[0] = (self.num_bits >> 8) as u8;
state[1] = self.num_bits as u8;
for chunk in tweak.chunks(16) {
let mut block = state;
for (i, &b) in chunk.iter().enumerate() {
block[i] ^= b;
}
self.key.encrypt_block(&mut block);
state = block;
}
// 最终加密(确保即使 tweak 为空也有输出)
self.key.encrypt_block(&mut state);
let mut out = [0u8; 15];
out.copy_from_slice(&state[..15]);
FpeTweak(out)
}
/// 就地加密(前 num_bits 位)
///
/// `data` 的前 `num_bits` 位被加密,高于 `num_bits` 的位保持不变。
///
/// # 注意
/// `data` 的位顺序:字节 0 的最高位是位 0(高位优先)。
pub fn encrypt(&self, tweak: &FpeTweak, data: &mut [u8; 16]) {
// 保存高于 num_bits 的位(不应被修改)
let saved = save_high_bits(data, self.num_bits);
clear_high_bits(data, self.num_bits);
fnr_encrypt(&self.key, &tweak.0, data, self.num_bits);
restore_high_bits(data, &saved, self.num_bits);
}
/// 就地解密(前 num_bits 位)
pub fn decrypt(&self, tweak: &FpeTweak, data: &mut [u8; 16]) {
let saved = save_high_bits(data, self.num_bits);
clear_high_bits(data, self.num_bits);
fnr_decrypt(&self.key, &tweak.0, data, self.num_bits);
restore_high_bits(data, &saved, self.num_bits);
}
/// 返回有效位数
pub fn num_bits(&self) -> usize {
self.num_bits
}
}
/// 保存 data 中高于 n 位的位(用于还原)
fn save_high_bits(data: &[u8; 16], n: usize) -> [u8; 16] {
let mut saved = [0u8; 16];
let full_bytes = n / 8;
let rem = n % 8;
if rem != 0 && full_bytes < 16 {
// 保存 full_bytes 字节的高位部分(低 (8-rem) 位)
let mask = 0xFF_u8 >> rem;
saved[full_bytes] = data[full_bytes] & mask;
}
let start = full_bytes + if rem > 0 { 1 } else { 0 };
saved[start..16].copy_from_slice(&data[start..16]);
saved
}
/// 将保存的高位还原到 data
fn restore_high_bits(data: &mut [u8; 16], saved: &[u8; 16], n: usize) {
let full_bytes = n / 8;
let rem = n % 8;
if rem != 0 && full_bytes < 16 {
let mask = 0xFF_u8 >> rem; // 低 (8-rem) 位
data[full_bytes] = (data[full_bytes] & !mask) | (saved[full_bytes] & mask);
}
let start = full_bytes + if rem > 0 { 1 } else { 0 };
data[start..16].copy_from_slice(&saved[start..16]);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fpe_new_valid() {
assert!(FpeKey::new(&[0u8; 16], 1).is_ok());
assert!(FpeKey::new(&[0u8; 16], 32).is_ok());
assert!(FpeKey::new(&[0u8; 16], 128).is_ok());
}
#[test]
fn test_fpe_new_invalid() {
assert!(FpeKey::new(&[0u8; 16], 0).is_err());
assert!(FpeKey::new(&[0u8; 16], 129).is_err());
}
#[test]
fn test_fpe_encrypt_decrypt_roundtrip_32bits() {
let key = [0x01u8; 16];
let fpe = FpeKey::new(&key, 32).unwrap();
let tweak = fpe.expand_tweak(b"test-tweak");
// 明文:u32 = 12345678
let mut data = [0u8; 16];
data[..4].copy_from_slice(&12345678u32.to_be_bytes());
let original = data;
fpe.encrypt(&tweak, &mut data);
// 加密后应与原始不同
assert_ne!(&data[..4], &original[..4], "加密后数据应变化");
// 解密后应恢复原始
fpe.decrypt(&tweak, &mut data);
assert_eq!(&data[..4], &original[..4], "解密后应恢复原始明文");
}
#[test]
fn test_fpe_encrypt_decrypt_roundtrip_8bits() {
let key = [0xABu8; 16];
let fpe = FpeKey::new(&key, 8).unwrap();
let tweak = fpe.expand_tweak(b"tweak");
for val in 0u8..=255 {
let mut data = [0u8; 16];
data[0] = val;
let original = data;
fpe.encrypt(&tweak, &mut data);
fpe.decrypt(&tweak, &mut data);
assert_eq!(data[0], original[0], "8位加解密往返应还原 val={}", val);
}
}
#[test]
fn test_fpe_encrypt_decrypt_roundtrip_1bit() {
let key = [0x99u8; 16];
let fpe = FpeKey::new(&key, 1).unwrap();
let tweak = fpe.expand_tweak(b"");
// 测试 0 和 1
for val in [0u8, 0x80u8] {
let mut data = [0u8; 16];
data[0] = val;
let original = data;
fpe.encrypt(&tweak, &mut data);
fpe.decrypt(&tweak, &mut data);
assert_eq!(data[0] & 0x80, original[0] & 0x80, "1位加解密往返应还原");
}
}
#[test]
fn test_fpe_encrypt_decrypt_roundtrip_128bits() {
let key = [0x55u8; 16];
let fpe = FpeKey::new(&key, 128).unwrap();
let tweak = fpe.expand_tweak(b"full block");
let mut data = [0u8; 16];
for (i, d) in data.iter_mut().enumerate() {
*d = i as u8 * 17;
}
let original = data;
fpe.encrypt(&tweak, &mut data);
fpe.decrypt(&tweak, &mut data);
assert_eq!(data, original, "128位加解密往返应还原");
}
#[test]
fn test_fpe_different_tweaks_different_output() {
let key = [0x42u8; 16];
let fpe = FpeKey::new(&key, 32).unwrap();
let tweak1 = fpe.expand_tweak(b"tweak1");
let tweak2 = fpe.expand_tweak(b"tweak2");
let mut d1 = [0u8; 16];
let mut d2 = [0u8; 16];
d1[0] = 0xDE;
d1[1] = 0xAD;
d1[2] = 0xBE;
d1[3] = 0xEF;
d2[..4].copy_from_slice(&d1[..4]);
fpe.encrypt(&tweak1, &mut d1);
fpe.encrypt(&tweak2, &mut d2);
assert_ne!(&d1[..4], &d2[..4], "不同 tweak 应产生不同密文");
}
#[test]
fn test_fpe_high_bits_preserved() {
// 验证高于 num_bits 的位在加密后不变
let key = [0x11u8; 16];
let fpe = FpeKey::new(&key, 4).unwrap(); // 只用高 4 位
let tweak = fpe.expand_tweak(b"t");
let mut data = [0u8; 16];
// 高 4 位为 0b1010,低 4 位为 0b0101
data[0] = 0b1010_0101;
// 字节 1~15 也有数据
for (i, d) in data[1..].iter_mut().enumerate() {
*d = (i + 1) as u8;
}
let saved_low = data[0] & 0x0F;
let saved_rest: [u8; 15] = data[1..].try_into().unwrap();
fpe.encrypt(&tweak, &mut data);
// 低 4 位和字节 1~15 应保持不变
assert_eq!(data[0] & 0x0F, saved_low, "低4位应不变");
assert_eq!(&data[1..], &saved_rest, "字节1~15应不变");
// 解密后高 4 位应恢复
let encrypted_high = data[0] & 0xF0;
fpe.decrypt(&tweak, &mut data);
assert_eq!(data[0] & 0xF0, 0b1010_0000, "解密后高4位应恢复");
let _ = encrypted_high;
}
}
+8
View File
@@ -56,3 +56,11 @@ pub mod sm2;
pub mod sm3;
pub mod sm4;
pub mod sm9;
#[cfg(feature = "alloc")]
pub mod bls;
pub mod fpe;
#[cfg(feature = "rustls-provider")]
pub mod rustls_provider;
+107
View File
@@ -0,0 +1,107 @@
//! SM3 → rustls `crypto::hash::Hash` / `crypto::hash::Context`
use alloc::boxed::Box;
use rustls::crypto::{self, HashAlgorithm};
use crate::sm3::Sm3Hasher;
/// 静态 SM3 哈希实现
pub(crate) static SM3: Sm3Hash = Sm3Hash;
pub(crate) struct Sm3Hash;
impl crypto::hash::Hash for Sm3Hash {
fn start(&self) -> Box<dyn crypto::hash::Context> {
Box::new(Sm3Context(Sm3Hasher::new()))
}
fn hash(&self, data: &[u8]) -> crypto::hash::Output {
crypto::hash::Output::new(&Sm3Hasher::digest(data))
}
fn output_len(&self) -> usize {
32
}
fn algorithm(&self) -> HashAlgorithm {
// Reason: SM3 尚无 IANA TLS HashAlgorithm 标准编号,暂用 Unknown(0x07)
HashAlgorithm::Unknown(0x07)
}
}
struct Sm3Context(Sm3Hasher);
impl crypto::hash::Context for Sm3Context {
fn fork_finish(&self) -> crypto::hash::Output {
crypto::hash::Output::new(&self.0.clone().finalize())
}
fn fork(&self) -> Box<dyn crypto::hash::Context> {
Box::new(Sm3Context(self.0.clone()))
}
fn finish(self: Box<Self>) -> crypto::hash::Output {
crypto::hash::Output::new(&self.0.finalize())
}
fn update(&mut self, data: &[u8]) {
self.0.update(data);
}
}
#[cfg(test)]
mod tests {
use rustls::crypto::hash::Hash;
use super::SM3;
// GB/T 32905 标准向量:"abc" → SM3 哈希
const ABC_DIGEST: &[u8] = &[
0x66, 0xc7, 0xf0, 0xf4, 0x62, 0xee, 0xed, 0xd9, 0xd1, 0xf2, 0xd4, 0x6b, 0xdc, 0x10, 0xe4,
0xe2, 0x41, 0x67, 0xc4, 0x87, 0x5c, 0xf2, 0xf7, 0xa2, 0x29, 0x7d, 0xa0, 0x2b, 0x8f, 0x4b,
0xa8, 0xe0,
];
#[test]
fn test_hash_abc() {
let out = SM3.hash(b"abc");
assert_eq!(out.as_ref(), ABC_DIGEST);
}
#[test]
fn test_context_update_finish() {
let mut ctx = SM3.start();
ctx.update(b"ab");
ctx.update(b"c");
let out = ctx.finish();
assert_eq!(out.as_ref(), ABC_DIGEST);
}
#[test]
fn test_context_fork_finish() {
let mut ctx = SM3.start();
ctx.update(b"abc");
// fork_finish 不消耗 ctxfork 后原 ctx 可继续 finish
let out1 = ctx.fork_finish();
let out2 = ctx.finish();
assert_eq!(out1.as_ref(), ABC_DIGEST);
assert_eq!(out2.as_ref(), ABC_DIGEST);
}
#[test]
fn test_context_fork() {
let mut ctx = SM3.start();
ctx.update(b"abc");
let forked = ctx.fork();
let out1 = forked.finish();
let out2 = ctx.finish();
assert_eq!(out1.as_ref(), ABC_DIGEST);
assert_eq!(out2.as_ref(), ABC_DIGEST);
}
#[test]
fn test_output_len() {
assert_eq!(SM3.output_len(), 32);
}
}
+83
View File
@@ -0,0 +1,83 @@
//! SM3-HMAC → rustls `crypto::hmac::Hmac` / `crypto::hmac::Key`
use alloc::boxed::Box;
use alloc::vec::Vec;
use rustls::crypto::hmac;
use crate::sm3::HmacSm3;
/// 静态 HMAC-SM3 实现(传入 `HkdfUsingHmac`
pub(crate) static HMAC_SM3: Sm3Hmac = Sm3Hmac;
pub(crate) struct Sm3Hmac;
impl hmac::Hmac for Sm3Hmac {
fn with_key(&self, key: &[u8]) -> Box<dyn hmac::Key> {
Box::new(Sm3HmacKey { key: key.to_vec() })
}
fn hash_output_len(&self) -> usize {
32
}
}
struct Sm3HmacKey {
key: Vec<u8>,
}
impl hmac::Key for Sm3HmacKey {
fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> hmac::Tag {
// Reason: rustls 将消息分为 first / middle / last 三段,
// 用流式 HmacSm3 逐段喂入,避免额外拷贝和堆分配
let mut mac = HmacSm3::new(&self.key);
mac.update(first);
for chunk in middle {
mac.update(chunk);
}
mac.update(last);
hmac::Tag::new(&mac.finalize())
}
fn tag_len(&self) -> usize {
32
}
}
#[cfg(test)]
mod tests {
use rustls::crypto::hmac::Hmac;
use super::HMAC_SM3;
// RFC 2104 HMAC-SM3 已知正确值(用 libsmx hmac_sm3 预计算)
// key = b"key", data = b"The quick brown fox jumps over the lazy dog"
// 通过 crate::sm3::hmac_sm3 验证
fn reference_hmac(key: &[u8], data: &[u8]) -> [u8; 32] {
crate::sm3::hmac_sm3(key, data)
}
#[test]
fn test_sign_concat_single_chunk() {
let key = b"secret";
let data = b"hello world";
let k = HMAC_SM3.with_key(key);
let tag = k.sign_concat(data, &[], &[]);
assert_eq!(tag.as_ref(), reference_hmac(key, data));
}
#[test]
fn test_sign_concat_split_matches_whole() {
// 分段 "hel" + ["lo "] + "world" 应与整体 "hello world" 结果相同
let key = b"secret";
let k = HMAC_SM3.with_key(key);
let tag = k.sign_concat(b"hel", &[b"lo "], b"world");
assert_eq!(tag.as_ref(), reference_hmac(key, b"hello world"));
}
#[test]
fn test_tag_len() {
let k = HMAC_SM3.with_key(b"k");
assert_eq!(k.tag_len(), 32);
}
}
+135
View File
@@ -0,0 +1,135 @@
//! SM2 ECDHE 密钥交换 → rustls `SupportedKxGroup` / `ActiveKeyExchange`
use alloc::boxed::Box;
use alloc::vec::Vec;
use rustls::crypto::kx::{
ActiveKeyExchange, NamedGroup, SharedSecret, StartedKeyExchange, SupportedKxGroup,
};
use rustls::error::{Error, PeerMisbehaved};
use crate::sm2::{generate_keypair, key_exchange::ecdh_from_slice, PrivateKey};
/// SM2 ECDHE 密钥交换组(RFC 8998 curveSM2
pub static CURVE_SM2: &dyn SupportedKxGroup = &Sm2KxGroup;
#[derive(Debug)]
struct Sm2KxGroup;
impl SupportedKxGroup for Sm2KxGroup {
fn start(&self) -> Result<StartedKeyExchange, Error> {
// 使用 getrandom 生成随机标量
let mut rng = Sm2Rng;
let (private_key, pub_key_bytes) = generate_keypair(&mut rng);
Ok(StartedKeyExchange::Single(Box::new(Sm2KeyExchange {
private_key,
pub_key: pub_key_bytes.to_vec(),
})))
}
fn name(&self) -> NamedGroup {
NamedGroup::curveSM2
}
}
struct Sm2KeyExchange {
private_key: PrivateKey,
pub_key: Vec<u8>,
}
impl ActiveKeyExchange for Sm2KeyExchange {
fn complete(self: Box<Self>, peer_pub_key: &[u8]) -> Result<SharedSecret, Error> {
// 标准 ECDHE:SM2 曲线标量乘法,输出共享密钥 x 坐标(32 字节)
let shared = ecdh_from_slice(&self.private_key, peer_pub_key)
.map_err(|_| Error::from(PeerMisbehaved::InvalidKeyShare))?;
Ok(SharedSecret::from(shared.as_ref()))
}
fn pub_key(&self) -> &[u8] {
&self.pub_key
}
fn group(&self) -> NamedGroup {
NamedGroup::curveSM2
}
}
// ── 内部 RNG 包装(使用 getrandom)────────────────────────────────────────────
pub(crate) struct Sm2Rng;
impl rand_core::RngCore for Sm2Rng {
fn next_u32(&mut self) -> u32 {
let mut bytes = [0u8; 4];
getrandom::getrandom(&mut bytes).expect("getrandom failed");
u32::from_le_bytes(bytes)
}
fn next_u64(&mut self) -> u64 {
let mut bytes = [0u8; 8];
getrandom::getrandom(&mut bytes).expect("getrandom failed");
u64::from_le_bytes(bytes)
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
getrandom::getrandom(dest).expect("getrandom failed");
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
getrandom::getrandom(dest).map_err(|e| {
// Reason: rand_core::Error::new 接受 NonZeroU32,直接用 getrandom 错误码
use core::num::NonZeroU32;
rand_core::Error::from(
NonZeroU32::new(e.raw_os_error().unwrap_or(1) as u32)
.unwrap_or(NonZeroU32::new(1).unwrap()),
)
})
}
}
#[cfg(test)]
mod tests {
use rustls::crypto::kx::NamedGroup;
use super::CURVE_SM2;
#[test]
fn test_name() {
assert_eq!(CURVE_SM2.name(), NamedGroup::curveSM2);
}
#[test]
fn test_ecdhe_roundtrip() {
// 模拟 TLS 握手:A 生成临时密钥对,B 生成临时密钥对,双方计算共享密钥
let kx_a = CURVE_SM2.start().unwrap().into_single();
let kx_b = CURVE_SM2.start().unwrap().into_single();
let pub_a = kx_a.pub_key().to_vec();
let pub_b = kx_b.pub_key().to_vec();
// 65 字节非压缩公钥(04 || x || y)
assert_eq!(pub_a.len(), 65);
assert_eq!(pub_b.len(), 65);
assert_eq!(pub_a[0], 0x04);
let secret_a = kx_a.complete(&pub_b).unwrap();
let secret_b = kx_b.complete(&pub_a).unwrap();
// 两端共享密钥必须相同(32 字节 x 坐标)
assert_eq!(secret_a.secret_bytes(), secret_b.secret_bytes());
assert_eq!(secret_a.secret_bytes().len(), 32);
}
#[test]
fn test_invalid_peer_key_rejected() {
let kx = CURVE_SM2.start().unwrap().into_single();
// 无效公钥(全零)应报错
let result = kx.complete(&[0u8; 65]);
assert!(result.is_err());
}
#[test]
fn test_no_ffdhe_group() {
assert!(CURVE_SM2.ffdhe_group().is_none());
}
}
+78
View File
@@ -0,0 +1,78 @@
//! rustls `CryptoProvider` 实现(RFC 8998 国密 TLS 套件)
extern crate alloc;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::sync::Arc;
use pki_types::PrivateKeyDer;
use rustls::crypto::{
CryptoProvider, GetRandomFailed, KeyProvider, SecureRandom, SigningKey, TicketProducer,
TicketerFactory,
};
use rustls::error::Error;
pub mod hash;
pub mod hmac;
pub mod kx;
pub mod sign;
pub mod tls13;
pub mod verify;
/// 构造国密 `CryptoProvider`
pub fn provider() -> CryptoProvider {
static TLS13_SUITES: &[&rustls::Tls13CipherSuite] =
&[tls13::TLS13_SM4_GCM_SM3, tls13::TLS13_SM4_CCM_SM3];
static KX_GROUPS: &[&dyn rustls::crypto::kx::SupportedKxGroup] = &[kx::CURVE_SM2];
CryptoProvider {
tls12_cipher_suites: Cow::Borrowed(&[]),
tls13_cipher_suites: Cow::Borrowed(TLS13_SUITES),
kx_groups: Cow::Borrowed(KX_GROUPS),
signature_verification_algorithms: verify::SUPPORTED_SM2_ALGS,
secure_random: &Random,
key_provider: &SmKeyProvider,
ticketer_factory: &SmTicketerFactory,
}
}
// ── SecureRandom ──────────────────────────────────────────────────────────────
#[derive(Debug)]
struct Random;
impl SecureRandom for Random {
fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed> {
getrandom::getrandom(buf).map_err(|_| GetRandomFailed)
}
}
// ── KeyProvider ───────────────────────────────────────────────────────────────
#[derive(Debug)]
struct SmKeyProvider;
impl KeyProvider for SmKeyProvider {
fn load_private_key(
&self,
key_der: PrivateKeyDer<'static>,
) -> Result<Box<dyn SigningKey>, Error> {
sign::load_private_key(key_der)
}
}
// ── TicketerFactory ───────────────────────────────────────────────────────────
#[derive(Debug)]
struct SmTicketerFactory;
impl TicketerFactory for SmTicketerFactory {
fn ticketer(&self) -> Result<Arc<dyn TicketProducer>, Error> {
// Reason: TLS session ticket 加密暂不支持,返回错误;
// 后续可用 SM4-GCM 实现 ticket 加密
Err(Error::General(
"SM ticket factory not yet implemented".into(),
))
}
}
+165
View File
@@ -0,0 +1,165 @@
//! SM2 签名 → rustls `SigningKey` / `Signer`
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt;
use pki_types::{PrivateKeyDer, SubjectPublicKeyInfoDer};
use rustls::crypto::{SignatureScheme, Signer, SigningKey};
use rustls::error::Error;
use crate::sm2::{
der::{public_key_to_spki_der, sig_to_der},
sign_message, PrivateKey, DEFAULT_ID,
};
/// 从 DER 编码的私钥加载 SM2 签名密钥
pub(crate) fn load_private_key(
key_der: PrivateKeyDer<'static>,
) -> Result<Box<dyn SigningKey>, Error> {
let pri_key = match &key_der {
PrivateKeyDer::Sec1(sec1) => {
crate::sm2::der::private_key_from_sec1_der(sec1.secret_sec1_der())
.map_err(|_| Error::General("invalid SEC1 SM2 private key".into()))?
}
PrivateKeyDer::Pkcs8(pkcs8) => {
crate::sm2::der::private_key_from_pkcs8_der(pkcs8.secret_pkcs8_der())
.map_err(|_| Error::General("invalid PKCS#8 SM2 private key".into()))?
}
_ => return Err(Error::General("unsupported SM2 key format".into())),
};
Ok(Box::new(Sm2SigningKey { pri_key }))
}
struct Sm2SigningKey {
pri_key: PrivateKey,
}
impl fmt::Debug for Sm2SigningKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sm2SigningKey").finish_non_exhaustive()
}
}
impl SigningKey for Sm2SigningKey {
fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option<Box<dyn Signer>> {
if offered.contains(&SignatureScheme::SM2_SM3) {
Some(Box::new(Sm2Signer {
pri_key: self.pri_key.clone(),
}))
} else {
None
}
}
fn public_key(&self) -> Option<SubjectPublicKeyInfoDer<'_>> {
let pub_key_bytes = self.pri_key.public_key();
let spki = public_key_to_spki_der(&pub_key_bytes);
Some(SubjectPublicKeyInfoDer::from(spki))
}
}
struct Sm2Signer {
pri_key: PrivateKey,
}
impl fmt::Debug for Sm2Signer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sm2Signer").finish_non_exhaustive()
}
}
impl Signer for Sm2Signer {
fn sign(self: Box<Self>, message: &[u8]) -> Result<Vec<u8>, Error> {
let mut rng = super::kx::Sm2Rng;
// GB/T 32918.2: 签名 = SM3(Z || M)Z 由 ID 和公钥派生
let sig_raw = sign_message(message, DEFAULT_ID, &self.pri_key, &mut rng);
// TLS 传输使用 DER 编码签名
Ok(sig_to_der(&sig_raw))
}
fn scheme(&self) -> SignatureScheme {
SignatureScheme::SM2_SM3
}
}
#[cfg(test)]
mod tests {
use alloc::boxed::Box;
use alloc::vec::Vec;
use rustls::crypto::SignatureScheme;
use super::load_private_key;
use crate::sm2::{der::sig_from_der, generate_keypair, verify_message, PrivateKey, DEFAULT_ID};
/// 将 SM2 私钥编码为最小 SEC1 DER(无可选字段)
/// ECPrivateKey ::= SEQUENCE { version INTEGER(1), privateKey OCTET STRING(32B) }
fn encode_sec1_der(pri_key: &PrivateKey) -> Vec<u8> {
let key_bytes = pri_key.as_bytes();
// SEQUENCE body = INTEGER(1)[3B] + OCTET STRING(32B)[34B] = 37 字节
let mut der = Vec::with_capacity(39);
der.extend_from_slice(&[
0x30, 0x25, // SEQUENCE, length 37
0x02, 0x01, 0x01, // INTEGER(1) = version
0x04, 0x20, // OCTET STRING, length 32
]);
der.extend_from_slice(key_bytes);
der
}
fn make_test_key_der() -> (PrivateKey, [u8; 65], pki_types::PrivateKeyDer<'static>) {
let mut rng = super::super::kx::Sm2Rng;
let (pri_key, pub_key) = generate_keypair(&mut rng);
let sec1 = encode_sec1_der(&pri_key);
let key_der = pki_types::PrivateKeyDer::Sec1(pki_types::PrivateSec1KeyDer::from(sec1));
(pri_key, pub_key, key_der)
}
#[test]
fn test_choose_scheme_sm2_sm3() {
let (_, _, key_der) = make_test_key_der();
let signing_key = load_private_key(key_der).unwrap();
let signer = signing_key.choose_scheme(&[SignatureScheme::SM2_SM3]);
assert!(signer.is_some());
assert_eq!(signer.unwrap().scheme(), SignatureScheme::SM2_SM3);
}
#[test]
fn test_choose_scheme_unmatched_returns_none() {
let (_, _, key_der) = make_test_key_der();
let signing_key = load_private_key(key_der).unwrap();
// 不含 SM2_SM3 时应返回 None
let signer = signing_key.choose_scheme(&[SignatureScheme::ECDSA_NISTP256_SHA256]);
assert!(signer.is_none());
}
#[test]
fn test_public_key_is_spki() {
let (_, _, key_der) = make_test_key_der();
let signing_key = load_private_key(key_der).unwrap();
let spki = signing_key.public_key();
assert!(spki.is_some());
// SPKI DER 以 SEQUENCE (0x30) 开头
let spki_bytes: &[u8] = spki.as_ref().unwrap().as_ref();
assert_eq!(spki_bytes[0], 0x30);
// 完整 SM2 SPKIalg(21B) + BIT STRING(68B) = 89B,外层 SEQUENCE header(2B) = 91B
assert_eq!(spki_bytes.len(), 91);
}
#[test]
fn test_sign_produces_valid_der_signature() {
let (_, pub_key, key_der) = make_test_key_der();
let signing_key = load_private_key(key_der).unwrap();
let signer = signing_key
.choose_scheme(&[SignatureScheme::SM2_SM3])
.unwrap();
let message = b"hello SM2";
let sig_der = Box::new(signer).sign(message).unwrap();
// 解析 DER 签名并用底层 API 验签
let sig_raw = sig_from_der(&sig_der).unwrap();
verify_message(message, DEFAULT_ID, &pub_key, &sig_raw).unwrap();
}
}
+270
View File
@@ -0,0 +1,270 @@
//! SM4-GCM/CCM AEAD + HKDF-SM3 → rustls TLS 1.3 密码套件
use alloc::boxed::Box;
use alloc::vec::Vec;
use rustls::crypto::cipher::{
make_tls13_aad, AeadKey, EncodedMessage, InboundOpaque, Iv, MessageDecrypter, MessageEncrypter,
Nonce, OutboundOpaque, OutboundPlain, Tls13AeadAlgorithm, UnsupportedOperationError, NONCE_LEN,
};
use rustls::crypto::tls13::HkdfUsingHmac;
use rustls::crypto::CipherSuite;
use rustls::enums::{ContentType, ProtocolVersion};
use rustls::error::Error;
use rustls::version::TLS13_VERSION;
use rustls::{CipherSuiteCommon, ConnectionTrafficSecrets, Tls13CipherSuite};
use crate::sm4::{sm4_decrypt_ccm, sm4_decrypt_gcm, sm4_encrypt_ccm, sm4_encrypt_gcm};
// ── HKDF(零代码复用 rustls 内置 HkdfUsingHmac)──────────────────────────────
pub(crate) static HKDF_SM3: HkdfUsingHmac<'static> = HkdfUsingHmac(&super::hmac::HMAC_SM3);
// ── TLS 1.3 密码套件常量 ──────────────────────────────────────────────────────
/// TLS 1.3 SM4-GCM-SM3 密码套件(RFC 8998
pub static TLS13_SM4_GCM_SM3: &Tls13CipherSuite = &Tls13CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS13_SM4_GCM_SM3,
hash_provider: &super::hash::SM3,
confidentiality_limit: 1 << 24,
},
protocol_version: TLS13_VERSION,
hkdf_provider: &HKDF_SM3,
aead_alg: &Sm4GcmAead,
quic: None,
};
/// TLS 1.3 SM4-CCM-SM3 密码套件(RFC 8998
pub static TLS13_SM4_CCM_SM3: &Tls13CipherSuite = &Tls13CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS13_SM4_CCM_SM3,
hash_provider: &super::hash::SM3,
confidentiality_limit: 1 << 24,
},
protocol_version: TLS13_VERSION,
hkdf_provider: &HKDF_SM3,
aead_alg: &Sm4CcmAead,
quic: None,
};
// ── SM4-GCM ───────────────────────────────────────────────────────────────────
struct Sm4GcmAead;
impl Tls13AeadAlgorithm for Sm4GcmAead {
fn encrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageEncrypter> {
Box::new(Sm4GcmEncrypter {
key: aead_key_to_16(&key),
iv,
})
}
fn decrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageDecrypter> {
Box::new(Sm4GcmDecrypter {
key: aead_key_to_16(&key),
iv,
})
}
fn key_len(&self) -> usize {
16
}
fn extract_keys(
&self,
key: AeadKey,
iv: Iv,
) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> {
Ok(ConnectionTrafficSecrets::Sm4Gcm { key, iv })
}
}
struct Sm4GcmEncrypter {
key: [u8; 16],
iv: Iv,
}
impl MessageEncrypter for Sm4GcmEncrypter {
fn encrypt(
&mut self,
msg: EncodedMessage<OutboundPlain<'_>>,
seq: u64,
) -> Result<EncodedMessage<OutboundOpaque>, Error> {
let total_len = self.encrypted_payload_len(msg.payload.len());
let nonce = Nonce::new(&self.iv, seq).to_array::<NONCE_LEN>()?;
let aad = make_tls13_aad(total_len);
// 收集明文 + ContentTypeTLS 1.3 inner plaintext 格式)
let mut plaintext: Vec<u8> = Vec::with_capacity(msg.payload.len() + 1);
{
let mut tmp = OutboundOpaque::with_capacity(msg.payload.len() + 1);
tmp.extend_from_chunks(&msg.payload);
tmp.extend_from_slice(&msg.typ.to_array());
plaintext.extend_from_slice(tmp.as_ref());
}
let (ciphertext, tag) = sm4_encrypt_gcm(&self.key, &nonce, &aad, &plaintext);
let mut out = OutboundOpaque::with_capacity(ciphertext.len() + 16);
out.extend_from_slice(&ciphertext);
out.extend_from_slice(&tag);
Ok(EncodedMessage {
typ: ContentType::ApplicationData,
version: ProtocolVersion::TLSv1_2,
payload: out,
})
}
fn encrypted_payload_len(&self, payload_len: usize) -> usize {
payload_len + 1 + 16 // +1 ContentType byte + 16 GCM tag
}
}
struct Sm4GcmDecrypter {
key: [u8; 16],
iv: Iv,
}
impl MessageDecrypter for Sm4GcmDecrypter {
fn decrypt<'a>(
&mut self,
mut msg: EncodedMessage<InboundOpaque<'a>>,
seq: u64,
) -> Result<EncodedMessage<&'a [u8]>, Error> {
let payload = &mut msg.payload;
if payload.len() < 16 {
return Err(Error::DecryptError);
}
let nonce = Nonce::new(&self.iv, seq).to_array::<NONCE_LEN>()?;
// Reason: AAD 使用解密前(含 tag)的完整 payload 长度
let aad = make_tls13_aad(payload.len());
let ct_len = payload.len() - 16;
let tag: [u8; 16] = payload[ct_len..]
.try_into()
.map_err(|_| Error::DecryptError)?;
let plaintext = sm4_decrypt_gcm(&self.key, &nonce, &aad, &payload[..ct_len], &tag)
.map_err(|_| Error::DecryptError)?;
// 将明文写回 payloadin-place),然后截断
let plain_len = plaintext.len();
payload[..plain_len].copy_from_slice(&plaintext);
payload.truncate(plain_len);
msg.into_tls13_unpadded_message()
}
}
// ── SM4-CCM ───────────────────────────────────────────────────────────────────
struct Sm4CcmAead;
impl Tls13AeadAlgorithm for Sm4CcmAead {
fn encrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageEncrypter> {
Box::new(Sm4CcmEncrypter {
key: aead_key_to_16(&key),
iv,
})
}
fn decrypter(&self, key: AeadKey, iv: Iv) -> Box<dyn MessageDecrypter> {
Box::new(Sm4CcmDecrypter {
key: aead_key_to_16(&key),
iv,
})
}
fn key_len(&self) -> usize {
16
}
fn extract_keys(
&self,
key: AeadKey,
iv: Iv,
) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> {
Ok(ConnectionTrafficSecrets::Sm4Ccm { key, iv })
}
}
struct Sm4CcmEncrypter {
key: [u8; 16],
iv: Iv,
}
impl MessageEncrypter for Sm4CcmEncrypter {
fn encrypt(
&mut self,
msg: EncodedMessage<OutboundPlain<'_>>,
seq: u64,
) -> Result<EncodedMessage<OutboundOpaque>, Error> {
let total_len = self.encrypted_payload_len(msg.payload.len());
let nonce = Nonce::new(&self.iv, seq).to_array::<NONCE_LEN>()?;
let aad = make_tls13_aad(total_len);
let mut plaintext: Vec<u8> = Vec::with_capacity(msg.payload.len() + 1);
{
let mut tmp = OutboundOpaque::with_capacity(msg.payload.len() + 1);
tmp.extend_from_chunks(&msg.payload);
tmp.extend_from_slice(&msg.typ.to_array());
plaintext.extend_from_slice(tmp.as_ref());
}
// sm4_encrypt_ccm 返回 ciphertext+tag 合并的 Vec
let combined = sm4_encrypt_ccm(&self.key, &nonce, &aad, &plaintext, 16)
.map_err(|_| Error::EncryptError)?;
let mut out = OutboundOpaque::with_capacity(combined.len());
out.extend_from_slice(&combined);
Ok(EncodedMessage {
typ: ContentType::ApplicationData,
version: ProtocolVersion::TLSv1_2,
payload: out,
})
}
fn encrypted_payload_len(&self, payload_len: usize) -> usize {
payload_len + 1 + 16
}
}
struct Sm4CcmDecrypter {
key: [u8; 16],
iv: Iv,
}
impl MessageDecrypter for Sm4CcmDecrypter {
fn decrypt<'a>(
&mut self,
mut msg: EncodedMessage<InboundOpaque<'a>>,
seq: u64,
) -> Result<EncodedMessage<&'a [u8]>, Error> {
let payload = &mut msg.payload;
if payload.len() < 16 {
return Err(Error::DecryptError);
}
let nonce = Nonce::new(&self.iv, seq).to_array::<NONCE_LEN>()?;
let aad = make_tls13_aad(payload.len());
// sm4_decrypt_ccm 接收 ciphertext_with_tag(末尾 tag_len 字节为 tag
let plaintext = sm4_decrypt_ccm(&self.key, &nonce, &aad, &payload[..], 16)
.map_err(|_| Error::DecryptError)?;
let plain_len = plaintext.len();
payload[..plain_len].copy_from_slice(&plaintext);
payload.truncate(plain_len);
msg.into_tls13_unpadded_message()
}
}
// ── 工具函数 ──────────────────────────────────────────────────────────────────
fn aead_key_to_16(key: &AeadKey) -> [u8; 16] {
let mut out = [0u8; 16];
out.copy_from_slice(&key.as_ref()[..16]);
out
}
+113
View File
@@ -0,0 +1,113 @@
//! SM2 验签 → rustls `SignatureVerificationAlgorithm`
use pki_types::{AlgorithmIdentifier, SignatureVerificationAlgorithm};
use rustls::crypto::{SignatureScheme, WebPkiSupportedAlgorithms};
use crate::sm2::{der::sig_from_der, verify_message, DEFAULT_ID};
/// rustls 支持的 SM2_SM3 签名验证算法集合
pub static SUPPORTED_SM2_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms {
all: &[&SM2_SM3_ALG],
mapping: &[(SignatureScheme::SM2_SM3, &[&SM2_SM3_ALG])],
};
static SM2_SM3_ALG: Sm2Sm3Algorithm = Sm2Sm3Algorithm;
#[derive(Debug)]
struct Sm2Sm3Algorithm;
// SM2 OID: 1.2.156.10197.1.301 (encoded as DER OID bytes)
// SM2withSM3 SignatureAlgorithm OID: 1.2.156.10197.1.501
// AlgorithmIdentifier for SM2withSM3
const SM2SM3_OID: &[u8] = &[
0x30, 0x0a, // SEQUENCE
0x06, 0x08, // OID
0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x83, 0x75, // 1.2.156.10197.1.501
];
impl SignatureVerificationAlgorithm for Sm2Sm3Algorithm {
fn public_key_alg_id(&self) -> AlgorithmIdentifier {
// id-ecPublicKey with SM2 curve parameter
AlgorithmIdentifier::from_slice(&[
0x30, 0x13, // SEQUENCE
0x06, 0x07, // OID id-ecPublicKey
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, // OID SM2
0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x82, 0x2d,
])
}
fn signature_alg_id(&self) -> AlgorithmIdentifier {
AlgorithmIdentifier::from_slice(SM2SM3_OID)
}
fn verify_signature(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), pki_types::InvalidSignature> {
// 将 65 字节公钥转换为固定数组
let pub_key_arr: &[u8; 65] = public_key
.try_into()
.map_err(|_| pki_types::InvalidSignature)?;
// DER 解码签名
let sig_raw = sig_from_der(signature).map_err(|_| pki_types::InvalidSignature)?;
// SM2 验签(使用默认 IDGB/T 32918.2
verify_message(message, DEFAULT_ID, pub_key_arr, &sig_raw)
.map_err(|_| pki_types::InvalidSignature)
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use pki_types::SignatureVerificationAlgorithm;
use super::SM2_SM3_ALG;
use crate::sm2::{der::sig_to_der, generate_keypair, sign_message, DEFAULT_ID};
fn sign_with_der(message: &[u8]) -> ([u8; 65], Vec<u8>) {
let mut rng = crate::rustls_provider::kx::Sm2Rng;
let (pri_key, pub_key) = generate_keypair(&mut rng);
let sig_raw = sign_message(message, DEFAULT_ID, &pri_key, &mut rng);
(pub_key, sig_to_der(&sig_raw))
}
#[test]
fn test_verify_valid_signature() {
let msg = b"test message";
let (pub_key, sig_der) = sign_with_der(msg);
SM2_SM3_ALG
.verify_signature(&pub_key, msg, &sig_der)
.unwrap();
}
#[test]
fn test_verify_wrong_message_fails() {
let (pub_key, sig_der) = sign_with_der(b"original");
let result = SM2_SM3_ALG.verify_signature(&pub_key, b"tampered", &sig_der);
assert!(result.is_err());
}
#[test]
fn test_verify_wrong_pubkey_fails() {
let msg = b"hello";
let (_, sig_der) = sign_with_der(msg);
// 用另一个公钥验证
let mut rng = crate::rustls_provider::kx::Sm2Rng;
let (_, other_pub) = generate_keypair(&mut rng);
let result = SM2_SM3_ALG.verify_signature(&other_pub, msg, &sig_der);
assert!(result.is_err());
}
#[test]
fn test_verify_invalid_pubkey_len_fails() {
let (_, sig_der) = sign_with_der(b"msg");
// 公钥长度不是 65 字节时应报错
let result = SM2_SM3_ALG.verify_signature(&[0u8; 32], b"msg", &sig_der);
assert!(result.is_err());
}
}
+533
View File
@@ -0,0 +1,533 @@
//! SM2 签名与密钥 DER 编解码
//!
//! ## 签名格式
//! TLS 使用 ASN.1 DER 格式表示签名:
//! ```text
//! SEQUENCE {
//! INTEGER r,
//! INTEGER s
//! }
//! ```
//! 而 libsmx 内部使用原始 `r||s`(64 字节)。本模块提供两者互转。
//!
//! ## 私钥格式
//! - **SEC1**RFC 5915):`ECPrivateKey SEQUENCE { version INTEGER(1), privateKey OCTET STRING, ... }`
//! - **PKCS#8**RFC 5958):`PrivateKeyInfo SEQUENCE { version INTEGER(0), algorithm, privateKey OCTET STRING(SEC1) }`
//!
//! ## 公钥 SPKI 格式
//! rustls `SigningKey::public_key()` 需要 `SubjectPublicKeyInfoDer`
//! ```text
//! SEQUENCE {
//! SEQUENCE {
//! OID id-ecPublicKey (1.2.840.10045.2.1)
//! OID SM2 (1.2.156.10197.1.301)
//! }
//! BIT STRING (04 || x(32B) || y(32B))
//! }
//! ```
//!
//! ## DER INTEGER 编码规则
//! - 去除前导零(但若最高位为 1,需在前补 0x00 防止被解析为负数)
//! - tag = 0x02length 占 1 字节(r/s < 256 位时长度 ≤ 33
//! - SEQUENCE tag = 0x30
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::error::Error;
use crate::sm2::PrivateKey;
/// 将原始签名 `r||s`64 字节)编码为 DER SEQUENCE
///
/// 输出格式:`30 <len> 02 <rlen> <r> 02 <slen> <s>`
#[cfg(feature = "alloc")]
pub fn sig_to_der(raw: &[u8; 64]) -> Vec<u8> {
let r = &raw[..32];
let s = &raw[32..];
let r_enc = encode_integer(r);
let s_enc = encode_integer(s);
let inner_len = r_enc.len() + s_enc.len();
let mut der = Vec::with_capacity(2 + inner_len);
der.push(0x30); // SEQUENCE tag
der.push(inner_len as u8); // SEQUENCE lengthinner < 256 字节)
der.extend_from_slice(&r_enc);
der.extend_from_slice(&s_enc);
der
}
/// 将 DER 编码签名解码为原始 `r||s`(64 字节)
///
/// # 错误
/// 格式不合法时返回 `Error::InvalidSignature`
pub fn sig_from_der(der: &[u8]) -> Result<[u8; 64], Error> {
let err = || Error::InvalidSignature;
// SEQUENCE tag
let (tag, rest) = split_first(der).ok_or_else(err)?;
if *tag != 0x30 {
return Err(err());
}
// SEQUENCE length
let (seq_len, rest) = split_first(rest).ok_or_else(err)?;
let seq_len = *seq_len as usize;
if rest.len() < seq_len {
return Err(err());
}
let body = &rest[..seq_len];
// 解析 r
let (r_bytes, body) = decode_integer(body).ok_or_else(err)?;
// 解析 s
let (s_bytes, body) = decode_integer(body).ok_or_else(err)?;
// 不应有多余数据
if !body.is_empty() {
return Err(err());
}
// r 和 s 都必须是正整数,不超过 32 字节
if r_bytes.is_empty() || r_bytes.len() > 33 || s_bytes.is_empty() || s_bytes.len() > 33 {
return Err(err());
}
let mut raw = [0u8; 64];
// Reason: DER INTEGER 可能有前缀 0x00(最高位保护),去除后左对齐写入 32 字节槽
let r_stripped = strip_leading_zero(r_bytes);
let s_stripped = strip_leading_zero(s_bytes);
if r_stripped.len() > 32 || s_stripped.len() > 32 {
return Err(err());
}
let r_off = 32 - r_stripped.len();
let s_off = 32 - s_stripped.len();
raw[r_off..32].copy_from_slice(r_stripped);
raw[32 + s_off..64].copy_from_slice(s_stripped);
Ok(raw)
}
// ── 内部辅助 ──────────────────────────────────────────────────────────────────
/// 将 32 字节大端整数编码为 DER INTEGER(带 tag 0x02 和 length
#[cfg(feature = "alloc")]
fn encode_integer(bytes: &[u8]) -> Vec<u8> {
// 去除前导零(至少保留 1 字节)
let start = bytes
.iter()
.position(|&b| b != 0)
.unwrap_or(bytes.len() - 1);
let val = &bytes[start..];
// 最高位为 1 时需补 0x00,防止被解析为负数
let needs_pad = val[0] & 0x80 != 0;
let val_len = val.len() + if needs_pad { 1 } else { 0 };
let mut enc = Vec::with_capacity(2 + val_len);
enc.push(0x02); // INTEGER tag
enc.push(val_len as u8); // length
if needs_pad {
enc.push(0x00);
}
enc.extend_from_slice(val);
enc
}
/// 从字节流中解析一个 DER INTEGER,返回 (value_bytes, 剩余字节)
fn decode_integer(data: &[u8]) -> Option<(&[u8], &[u8])> {
let (tag, rest) = split_first(data)?;
if *tag != 0x02 {
return None;
}
let (len, rest) = split_first(rest)?;
let len = *len as usize;
if rest.len() < len {
return None;
}
Some((&rest[..len], &rest[len..]))
}
/// 去除前导 0x00 字节
fn strip_leading_zero(bytes: &[u8]) -> &[u8] {
match bytes.iter().position(|&b| b != 0) {
Some(i) => &bytes[i..],
None => &bytes[bytes.len().saturating_sub(1)..], // 全零时保留末字节
}
}
fn split_first(data: &[u8]) -> Option<(&u8, &[u8])> {
data.split_first()
}
// ── DER 长度解码 ──────────────────────────────────────────────────────────────
/// 解析 DER 长度字段,返回 (length, 剩余字节)
///
/// 支持:单字节(< 0x80)、两字节(0x81 nn)、三字节(0x82 nn nn
fn parse_length(data: &[u8]) -> Option<(usize, &[u8])> {
let (first, rest) = data.split_first()?;
if *first < 0x80 {
// Reason: 最高位为 0 时,本字节直接表示长度
Some((*first as usize, rest))
} else if *first == 0x81 {
let (len, rest) = rest.split_first()?;
Some((*len as usize, rest))
} else if *first == 0x82 {
if rest.len() < 2 {
return None;
}
let len = (rest[0] as usize) << 8 | rest[1] as usize;
Some((len, &rest[2..]))
} else {
// 不支持更长或不定长编码
None
}
}
/// 解析一个 TLVtag-length-value),返回 (value_bytes, 剩余字节)
fn parse_tlv(data: &[u8], expected_tag: u8) -> Option<(&[u8], &[u8])> {
let (tag, rest) = data.split_first()?;
if *tag != expected_tag {
return None;
}
let (len, rest) = parse_length(rest)?;
if rest.len() < len {
return None;
}
Some((&rest[..len], &rest[len..]))
}
// ── 私钥 DER 解析 ─────────────────────────────────────────────────────────────
/// 从 SEC1 DER 解析 SM2 私钥(RFC 5915
///
/// 格式:
/// ```text
/// ECPrivateKey ::= SEQUENCE {
/// version INTEGER { ecPrivkeyVer1(1) },
/// privateKey OCTET STRING, -- 32 字节原始私钥
/// [0] ECParameters OPTIONAL,
/// [1] BIT STRING OPTIONAL
/// }
/// ```
///
/// # 错误
/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey`
pub fn private_key_from_sec1_der(der: &[u8]) -> Result<PrivateKey, Error> {
let err = || Error::InvalidPrivateKey;
// 解析外层 SEQUENCE
let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?;
// version INTEGER,值应为 1ecPrivkeyVer1
let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?;
if ver_bytes != [0x01] {
return Err(err());
}
// privateKey OCTET STRING32 字节)
let (key_bytes, _rest) = parse_tlv(rest, 0x04).ok_or_else(err)?;
if key_bytes.len() != 32 {
return Err(err());
}
let key_arr: &[u8; 32] = key_bytes.try_into().map_err(|_| err())?;
PrivateKey::from_bytes(key_arr)
}
/// 从 PKCS#8 DER 解析 SM2 私钥(RFC 5958
///
/// 格式:
/// ```text
/// PrivateKeyInfo ::= SEQUENCE {
/// version INTEGER (0),
/// algorithm AlgorithmIdentifier SEQUENCE { ... },
/// privateKey OCTET STRING (SEC1 DER)
/// }
/// ```
///
/// # 错误
/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey`
pub fn private_key_from_pkcs8_der(der: &[u8]) -> Result<PrivateKey, Error> {
let err = || Error::InvalidPrivateKey;
// 解析外层 SEQUENCEPrivateKeyInfo
let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?;
// version INTEGER,值应为 0
let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?;
if ver_bytes != [0x00] {
return Err(err());
}
// AlgorithmIdentifier SEQUENCE(跳过,不验证 OID
let (_, rest) = parse_tlv(rest, 0x30).ok_or_else(err)?;
// privateKey OCTET STRING(内含 SEC1 DER
let (sec1_der, _) = parse_tlv(rest, 0x04).ok_or_else(err)?;
private_key_from_sec1_der(sec1_der)
}
// ── SM2 公钥 SPKI DER 编码 ────────────────────────────────────────────────────
/// 将 SM2 公钥(65 字节,04||x||y)编码为 SubjectPublicKeyInfo DER
///
/// 格式(RFC 5480):
/// ```text
/// SEQUENCE {
/// SEQUENCE {
/// OID 1.2.840.10045.2.1 (id-ecPublicKey, 7 字节)
/// OID 1.2.156.10197.1.301 (SM2, 8 字节)
/// }
/// BIT STRING 0x00 || pub_key (65 字节 + 1 字节前缀)
/// }
/// ```
///
/// 此格式是 rustls `SigningKey::public_key()` 所需的 `SubjectPublicKeyInfoDer`。
#[cfg(feature = "alloc")]
pub fn public_key_to_spki_der(pub_key: &[u8; 65]) -> Vec<u8> {
// OID 1.2.840.10045.2.1 (id-ecPublicKey): 06 07 2a 86 48 ce 3d 02 01
let oid_ec: &[u8] = &[0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
// OID 1.2.156.10197.1.301 (SM2): 06 08 2a 81 1c cf 55 01 82 2d
let oid_sm2: &[u8] = &[0x06, 0x08, 0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x82, 0x2d];
// AlgorithmIdentifier SEQUENCE
let alg_inner_len = oid_ec.len() + oid_sm2.len();
let mut alg = Vec::with_capacity(2 + alg_inner_len);
alg.push(0x30);
alg.push(alg_inner_len as u8);
alg.extend_from_slice(oid_ec);
alg.extend_from_slice(oid_sm2);
// BIT STRING: 0x03 <len> 0x00 <pub_key>
// Reason: 0x00 是 unused bits 字段,表示最后一字节无填充位
let bit_str_len = 1 + pub_key.len(); // 0x00 前缀 + 65 字节公钥
let mut bit_str = Vec::with_capacity(2 + bit_str_len);
bit_str.push(0x03);
bit_str.push(bit_str_len as u8);
bit_str.push(0x00); // unused bits = 0
bit_str.extend_from_slice(pub_key);
// 外层 SEQUENCE
let outer_len = alg.len() + bit_str.len();
let mut der = Vec::with_capacity(2 + outer_len);
der.push(0x30);
der.push(outer_len as u8);
der.extend_from_slice(&alg);
der.extend_from_slice(&bit_str);
der
}
#[cfg(test)]
mod tests {
use super::*;
fn make_raw(r: [u8; 32], s: [u8; 32]) -> [u8; 64] {
let mut raw = [0u8; 64];
raw[..32].copy_from_slice(&r);
raw[32..].copy_from_slice(&s);
raw
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_roundtrip_basic() {
let r = [0x01u8; 32];
let s = [0x02u8; 32];
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
let recovered = sig_from_der(&der).unwrap();
assert_eq!(recovered, raw);
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_roundtrip_high_bit_set() {
// r/s 最高位为 1,需要 DER 填充 0x00
let mut r = [0u8; 32];
r[0] = 0x80; // 最高位为 1
let mut s = [0u8; 32];
s[0] = 0xFF;
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
// 验证 DER 中有 0x00 填充
let recovered = sig_from_der(&der).unwrap();
assert_eq!(recovered, raw);
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_roundtrip_leading_zeros() {
// r 前有大量前导零
let mut r = [0u8; 32];
r[31] = 0x42; // 只有最后一字节非零
let s = [0x01u8; 32];
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
let recovered = sig_from_der(&der).unwrap();
assert_eq!(recovered, raw);
}
#[test]
fn test_der_invalid_tag() {
// 非 SEQUENCE tag
let bad = [0x10, 0x08, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00];
assert!(sig_from_der(&bad).is_err());
}
#[test]
fn test_der_truncated() {
let bad = [0x30, 0x10]; // length 声明 16 字节但无内容
assert!(sig_from_der(&bad).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_structure() {
// 验证 DER 字节结构符合 ASN.1 规范
let r = [0x01u8; 32];
let s = [0x01u8; 32];
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
assert_eq!(der[0], 0x30); // SEQUENCE
assert_eq!(der[2], 0x02); // INTEGER tag for r
// 长度字段合理(r/s 各最多 33 字节 + 2 字节头 = 35,×2 + 2 = 72
assert!(der.len() <= 72);
assert!(der.len() >= 8);
}
// ── 私钥 DER 解析测试 ──────────────────────────────────────────────────────
// 已知 SM2 私钥原始字节(与其他测试共用)
const RAW_KEY: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, 0x9f,
0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, 0x4d, 0xf7,
0xc5, 0xb8,
];
/// 构造最小 SEC1 DER(只有 version + privateKey 字段)
#[cfg(feature = "alloc")]
fn make_sec1_der(key: &[u8; 32]) -> alloc::vec::Vec<u8> {
// version INTEGER = 102 01 01
// privateKey OCTET STRING04 20 <32 bytes>
// inner = 3 + 2 + 32 = 37 bytes → SEQUENCE 30 25 ...
let mut der = alloc::vec![0x30u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20];
der.extend_from_slice(key);
der
}
/// 构造最小 PKCS#8 DER(包含虚拟 AlgorithmIdentifier OID
#[cfg(feature = "alloc")]
fn make_pkcs8_der(key: &[u8; 32]) -> alloc::vec::Vec<u8> {
let sec1 = make_sec1_der(key);
// AlgorithmIdentifier 最小化:30 06 06 01 00 06 01 00(两个 OID,各 1 字节占位)
let alg_id: &[u8] = &[0x30, 0x06, 0x06, 0x01, 0x00, 0x06, 0x01, 0x00];
// version INTEGER = 002 01 00
let version: &[u8] = &[0x02, 0x01, 0x00];
// privateKey OCTET STRING 包装 sec1
let mut priv_oct = alloc::vec![0x04u8, sec1.len() as u8];
priv_oct.extend_from_slice(&sec1);
// inner = version + alg_id + priv_oct
let inner_len = version.len() + alg_id.len() + priv_oct.len();
let mut der = alloc::vec![0x30u8, inner_len as u8];
der.extend_from_slice(version);
der.extend_from_slice(alg_id);
der.extend_from_slice(&priv_oct);
der
}
#[cfg(feature = "alloc")]
#[test]
fn test_sec1_der_roundtrip() {
let der = make_sec1_der(&RAW_KEY);
let key = private_key_from_sec1_der(&der).expect("SEC1 解析应成功");
assert_eq!(key.as_bytes(), &RAW_KEY);
}
#[cfg(feature = "alloc")]
#[test]
fn test_pkcs8_der_roundtrip() {
let der = make_pkcs8_der(&RAW_KEY);
let key = private_key_from_pkcs8_der(&der).expect("PKCS#8 解析应成功");
assert_eq!(key.as_bytes(), &RAW_KEY);
}
#[test]
fn test_sec1_der_invalid_tag() {
// 首字节不是 SEQUENCE tag
let bad = [0x02u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20, 0x00];
assert!(private_key_from_sec1_der(&bad).is_err());
}
#[test]
fn test_sec1_der_wrong_version() {
// version 应为 1,此处给 0;最后 32 字节填充为 RAW_KEY
let mut der = [0u8; 39];
der[0] = 0x30;
der[1] = 0x25; // SEQUENCE length 37
der[2] = 0x02;
der[3] = 0x01;
der[4] = 0x00; // version = 0(错误,应为 1
der[5] = 0x04;
der[6] = 0x20; // OCTET STRING 32 字节
der[7..39].copy_from_slice(&RAW_KEY);
assert!(private_key_from_sec1_der(&der).is_err());
}
#[test]
fn test_sec1_der_key_too_short() {
// privateKey 只有 16 字节(不足 32
let der = [
0x30, 0x15, // SEQUENCE 21 字节
0x02, 0x01, 0x01, // version = 1
0x04, 0x10, // OCTET STRING 16 字节
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
assert!(private_key_from_sec1_der(&der).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_pkcs8_der_invalid_outer_tag() {
let mut der = make_pkcs8_der(&RAW_KEY);
der[0] = 0x04; // 破坏外层 SEQUENCE tag
assert!(private_key_from_pkcs8_der(&der).is_err());
}
// ── SPKI DER 测试 ──────────────────────────────────────────────────────────
#[cfg(feature = "alloc")]
#[test]
fn test_spki_der_structure() {
use crate::sm2::PrivateKey;
let pri = PrivateKey::from_bytes(&RAW_KEY).unwrap();
let pub_key = pri.public_key();
let spki = public_key_to_spki_der(&pub_key);
// 外层 SEQUENCE
assert_eq!(spki[0], 0x30, "外层 tag 应为 SEQUENCE");
// BIT STRING 内包含 04||x||y65字节)
// 确认公钥原始字节出现在 SPKI 中
let pos = spki.windows(65).position(|w| w == pub_key);
assert!(pos.is_some(), "SPKI 应包含原始公钥字节");
}
#[cfg(feature = "alloc")]
#[test]
fn test_spki_der_oid_ec() {
use crate::sm2::PrivateKey;
let pri = PrivateKey::from_bytes(&RAW_KEY).unwrap();
let pub_key = pri.public_key();
let spki = public_key_to_spki_der(&pub_key);
// id-ecPublicKey OID bytes
let oid_ec: &[u8] = &[0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01];
assert!(
spki.windows(oid_ec.len()).any(|w| w == oid_ec),
"SPKI 应包含 id-ecPublicKey OID"
);
}
}
+620
View File
@@ -0,0 +1,620 @@
//! SM2 密钥交换协议(GB/T 32918.3-2016
//!
//! 提供两种密钥交换方式:
//! - `ecdh`: 简单 SM2-ECDH 共享密钥计算(适配 TLS/rustls
//! - `exchange_a` / `exchange_b`: 完整 GB/T 32918.3 密钥交换协议(带确认哈希)
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crypto_bigint::{Zero, U256};
use rand_core::RngCore;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::error::Error;
use crate::sm2::ec::{AffinePoint, JacobianPoint};
use crate::sm2::field::{fn_add, fn_mul, fp_to_bytes, Fn, GROUP_ORDER_MINUS_1};
use crate::sm2::get_z;
use crate::sm3::Sm3Hasher;
// ── x̄ 辅助函数(GB/T 32918.3 核心运算)─────────────────────────────────────────
/// 计算 x̄ = 2^w + (x & (2^w - 1)),其中 w = ⌈(⌈log2(n)⌉ / 2)⌉ - 1 = 127
///
/// 对 SM2 256 位群阶,w=127。在大端 32 字节表示中:
/// - 清除高 128 位(bytes[0..16]),保留低 128 位
/// - 设 bytes[16] 的 bit7 = 1(即加 2^127
fn x_bar(x_bytes: &[u8; 32]) -> U256 {
let mut buf = [0u8; 32];
// Reason: 保留 x 的低 128 位(bytes[16..32]),高 128 位清零
buf[16..32].copy_from_slice(&x_bytes[16..32]);
// 设 bit 127bytes[16] 的最高位)
buf[16] |= 0x80;
U256::from_be_slice(&buf)
}
// ── EphemeralKey(临时密钥对)────────────────────────────────────────────────────
/// SM2 密钥交换临时密钥对(离开作用域自动清零)
///
/// 用于密钥交换协议中的临时私钥和对应公钥。
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct EphemeralKey {
r_bytes: [u8; 32],
#[zeroize(skip)]
r_point: [u8; 65],
}
impl EphemeralKey {
/// 生成临时密钥对
pub fn generate<R: RngCore>(rng: &mut R) -> Self {
loop {
let mut r_bytes = [0u8; 32];
rng.fill_bytes(&mut r_bytes);
let r = U256::from_be_slice(&r_bytes);
if bool::from(r.is_zero()) || r >= GROUP_ORDER_MINUS_1 {
r_bytes.zeroize();
continue;
}
let r_jac = JacobianPoint::scalar_mul_g(&r);
// Reason: r 在合法范围内,scalar_mul_g 不会产生无穷远点
let r_aff = r_jac.to_affine().expect("valid r produces valid point");
return EphemeralKey {
r_bytes,
r_point: r_aff.to_bytes(),
};
}
}
/// 从指定标量创建临时密钥对(测试用)
pub fn from_scalar(r: &U256) -> Result<Self, Error> {
if bool::from(r.is_zero()) || *r >= GROUP_ORDER_MINUS_1 {
return Err(Error::InvalidPrivateKey);
}
let r_jac = JacobianPoint::scalar_mul_g(r);
let r_aff = r_jac.to_affine().map_err(|_| Error::InvalidPrivateKey)?;
Ok(EphemeralKey {
r_bytes: r.to_be_bytes(),
r_point: r_aff.to_bytes(),
})
}
/// 获取临时公钥(发送给对方)
pub fn public_key(&self) -> &[u8; 65] {
&self.r_point
}
}
// ── 简单 ECDH ──────────────────────────────────────────────────────────────────
/// 简单 SM2-ECDH 共享密钥计算
///
/// 计算 shared = my_priv · peer_pub,返回共享点的 x 坐标(32 字节)。
/// 适用于 TLS/rustls 等只需要原始 ECDH 共享密钥的场景。
///
/// # 参数
/// - `my_priv`: 己方私钥
/// - `peer_pub`: 对方公钥(65 字节,04||x||y
///
/// # 错误
/// - `InvalidPublicKey`: 公钥格式错误或不在曲线上
/// - `PointAtInfinity`: 共享点为无穷远(不应发生于合法输入)
pub fn ecdh(my_priv: &crate::sm2::PrivateKey, peer_pub: &[u8; 65]) -> Result<[u8; 32], Error> {
let peer = AffinePoint::from_bytes(peer_pub)?;
let d = U256::from_be_slice(my_priv.as_bytes());
let peer_jac = JacobianPoint::from_affine(&peer);
let shared = JacobianPoint::scalar_mul(&d, &peer_jac);
let shared_aff = shared.to_affine()?;
Ok(fp_to_bytes(&shared_aff.x))
}
/// 从变长切片执行 SM2-ECDHrustls `ActiveKeyExchange::complete` 适配)
///
/// 等同于 `ecdh`,但接受 `&[u8]` 而非 `&[u8; 65]`,省去调用方的长度转换。
///
/// # 错误
/// - `InvalidInputLength`: peer_pub 长度不等于 65
/// - `InvalidPublicKey` / `PointAtInfinity`: 同 `ecdh`
pub fn ecdh_from_slice(
my_priv: &crate::sm2::PrivateKey,
peer_pub: &[u8],
) -> Result<[u8; 32], Error> {
let pub_fixed: &[u8; 65] = peer_pub.try_into().map_err(|_| Error::InvalidInputLength)?;
ecdh(my_priv, pub_fixed)
}
// ── 完整密钥交换协议(GB/T 32918.3)──────────────────────────────────────────────
/// 密钥交换结果
#[cfg(feature = "alloc")]
pub struct ExchangeResult {
/// 协商出的共享密钥
pub key: Vec<u8>,
/// 己方确认哈希(发给对方验证)
pub s_self: [u8; 32],
/// 对方确认哈希(用于验证对方发来的值)
pub s_peer: [u8; 32],
}
/// 发起方 A 执行密钥交换
///
/// # 参数
/// - `klen`: 期望密钥长度(字节)
/// - `id_a`: 发起方用户 ID
/// - `id_b`: 响应方用户 ID
/// - `pri_key_a`: 发起方私钥
/// - `pub_key_a`: 发起方公钥(65 字节)
/// - `pub_key_b`: 响应方公钥(65 字节)
/// - `eph_key_a`: 发起方临时密钥
/// - `r_b`: 响应方临时公钥(65 字节)
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_arguments)]
pub fn exchange_a(
klen: usize,
id_a: &[u8],
id_b: &[u8],
pri_key_a: &crate::sm2::PrivateKey,
pub_key_a: &[u8; 65],
pub_key_b: &[u8; 65],
eph_key_a: &EphemeralKey,
r_b: &[u8; 65],
) -> Result<ExchangeResult, Error> {
compute_shared(
true, klen, id_a, id_b, pri_key_a, pub_key_a, pub_key_b, eph_key_a, r_b,
)
}
/// 响应方 B 执行密钥交换
///
/// # 参数
/// - `klen`: 期望密钥长度(字节)
/// - `id_a`: 发起方用户 ID
/// - `id_b`: 响应方用户 ID
/// - `pri_key_b`: 响应方私钥
/// - `pub_key_a`: 发起方公钥(65 字节)
/// - `pub_key_b`: 响应方公钥(65 字节)
/// - `eph_key_b`: 响应方临时密钥
/// - `r_a`: 发起方临时公钥(65 字节)
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_arguments)]
pub fn exchange_b(
klen: usize,
id_a: &[u8],
id_b: &[u8],
pri_key_b: &crate::sm2::PrivateKey,
pub_key_a: &[u8; 65],
pub_key_b: &[u8; 65],
eph_key_b: &EphemeralKey,
r_a: &[u8; 65],
) -> Result<ExchangeResult, Error> {
compute_shared(
false, klen, id_a, id_b, pri_key_b, pub_key_a, pub_key_b, eph_key_b, r_a,
)
}
/// 内部共享计算
///
/// `is_initiator`: true 表示发起方 Afalse 表示响应方 B
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_arguments)]
fn compute_shared(
is_initiator: bool,
klen: usize,
id_a: &[u8],
id_b: &[u8],
pri_key_self: &crate::sm2::PrivateKey,
pub_key_a: &[u8; 65],
pub_key_b: &[u8; 65],
eph_key_self: &EphemeralKey,
r_peer: &[u8; 65],
) -> Result<ExchangeResult, Error> {
// 计算 ZA、ZB
let z_a = get_z(id_a, pub_key_a);
let z_b = get_z(id_b, pub_key_b);
// 解析临时公钥坐标
let r_self_aff = AffinePoint::from_bytes(eph_key_self.public_key())?;
let r_peer_aff = AffinePoint::from_bytes(r_peer)?;
// 计算 x̄_self 和 x̄_peer
let x_self_bytes = fp_to_bytes(&r_self_aff.x);
let x_peer_bytes = fp_to_bytes(&r_peer_aff.x);
let x_bar_self = x_bar(&x_self_bytes);
let x_bar_peer = x_bar(&x_peer_bytes);
// t = (d_self + x̄_self · r_self) mod n
let d_self = U256::from_be_slice(pri_key_self.as_bytes());
let r_self = U256::from_be_slice(&eph_key_self.r_bytes);
let t_fn = fn_add(
&Fn::new(&d_self),
&fn_mul(&Fn::new(&x_bar_self), &Fn::new(&r_self)),
);
// V/U = t · (peer_pub + x̄_peer · R_peer)
// Reason: 先计算 x̄_peer · R_peer(标量乘),再加 peer_pub(仿射点)
let peer_pub_bytes = if is_initiator { pub_key_b } else { pub_key_a };
let peer_pub_aff = AffinePoint::from_bytes(peer_pub_bytes)?;
let peer_pub_jac = JacobianPoint::from_affine(&peer_pub_aff);
let r_peer_jac = JacobianPoint::from_affine(&r_peer_aff);
let x_bar_peer_r = JacobianPoint::scalar_mul(&x_bar_peer, &r_peer_jac);
let combined = JacobianPoint::add(&peer_pub_jac, &x_bar_peer_r);
let t = t_fn.retrieve();
let v_point = JacobianPoint::scalar_mul(&t, &combined);
let v_aff = v_point.to_affine().map_err(|_| Error::KeyExchangeFailed)?;
let xv = fp_to_bytes(&v_aff.x);
let yv = fp_to_bytes(&v_aff.y);
// K = KDF(xV || yV || ZA || ZB, klen)
let mut kdf_input = Vec::with_capacity(32 + 32 + 32 + 32);
kdf_input.extend_from_slice(&xv);
kdf_input.extend_from_slice(&yv);
kdf_input.extend_from_slice(&z_a);
kdf_input.extend_from_slice(&z_b);
let key = crate::sm2::kdf::kdf(&kdf_input, klen);
// KDF 输出全零时返回错误(防弱密钥)
if key.iter().all(|&b| b == 0) {
return Err(Error::KeyExchangeFailed);
}
// 确认哈希
// (x1,y1) 始终是 RA(发起方),(x2,y2) 始终是 RB(响应方)
let (x1, y1, x2, y2) = if is_initiator {
(
fp_to_bytes(&r_self_aff.x),
fp_to_bytes(&r_self_aff.y),
fp_to_bytes(&r_peer_aff.x),
fp_to_bytes(&r_peer_aff.y),
)
} else {
(
fp_to_bytes(&r_peer_aff.x),
fp_to_bytes(&r_peer_aff.y),
fp_to_bytes(&r_self_aff.x),
fp_to_bytes(&r_self_aff.y),
)
};
// 内部哈希 hash_v = SM3(xV || ZA || ZB || x1 || y1 || x2 || y2)
let mut h = Sm3Hasher::new();
h.update(&xv);
h.update(&z_a);
h.update(&z_b);
h.update(&x1);
h.update(&y1);
h.update(&x2);
h.update(&y2);
let hash_v = h.finalize();
// S1 = SM3(0x02 || yV || hash_v) — 己方若为 B,则 S1 是己方确认值
let s1 = {
let mut h = Sm3Hasher::new();
h.update(&[0x02]);
h.update(&yv);
h.update(&hash_v);
h.finalize()
};
// SA = SM3(0x03 || yV || hash_v)
let sa = {
let mut h = Sm3Hasher::new();
h.update(&[0x03]);
h.update(&yv);
h.update(&hash_v);
h.finalize()
};
// Reason: 发起方 A 的确认哈希是 SA(0x03),响应方 B 的确认哈希是 S1(0x02)
let (s_self, s_peer) = if is_initiator {
(sa, s1) // A 发送 SA 给 B 验证,A 验证 B 发来的 S1
} else {
(s1, sa) // B 发送 S1 给 A 验证,B 验证 A 发来的 SA
};
Ok(ExchangeResult {
key,
s_self,
s_peer,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sm2::PrivateKey;
#[allow(dead_code)]
struct FakeRng(#[allow(dead_code)] [u8; 32]);
impl RngCore for FakeRng {
fn next_u32(&mut self) -> u32 {
0
}
fn next_u64(&mut self) -> u64 {
0
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
for (i, b) in dest.iter_mut().enumerate() {
*b = self.0[i % 32];
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
#[test]
fn test_x_bar() {
// x = 1(低 128 位只有 bit 0
let mut x_bytes = [0u8; 32];
x_bytes[31] = 0x01;
let result = x_bar(&x_bytes);
// 期望 2^127 + 1
let mut expected = [0u8; 32];
expected[16] = 0x80;
expected[31] = 0x01;
assert_eq!(result, U256::from_be_slice(&expected));
}
#[test]
fn test_x_bar_high_bits_cleared() {
// x 有高 128 位数据,应被清除
let x_bytes = [0xFFu8; 32];
let result = x_bar(&x_bytes);
// 低 128 位全 1 + 2^127 设置位 = 0x80 FF...FF 后 16 字节加上 2^127
// 高 16 字节应为 0bytes[16] = 0xFF | 0x80 = 0xFF
let mut expected = [0u8; 32];
expected[16..32].copy_from_slice(&[0xFF; 16]);
expected[16] |= 0x80; // 已经是 0xFF,不变
assert_eq!(result, U256::from_be_slice(&expected));
}
#[test]
fn test_ecdh_roundtrip() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
// A 用 B 的公钥算 ECDH,B 用 A 的公钥算 ECDH,结果应一致
let shared_a = ecdh(&pri_a, &pub_b).unwrap();
let shared_b = ecdh(&pri_b, &pub_a).unwrap();
assert_eq!(shared_a, shared_b);
}
#[test]
fn test_ecdh_invalid_pubkey() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
// 无效公钥(全零 y 坐标不在曲线上)
let mut bad_pub = [0x04u8; 65];
bad_pub[1] = 0x01;
assert!(ecdh(&pri_a, &bad_pub).is_err());
}
#[test]
fn test_ecdh_from_slice_length_check() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
// 长度不对应报 InvalidInputLength
assert!(ecdh_from_slice(&pri_a, &[0x04u8; 64]).is_err());
assert!(ecdh_from_slice(&pri_a, &[0x04u8; 66]).is_err());
}
#[test]
fn test_ecdh_from_slice_equals_ecdh() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_b = pri_b.public_key();
let r1 = ecdh(&pri_a, &pub_b).unwrap();
let r2 = ecdh_from_slice(&pri_a, &pub_b).unwrap();
assert_eq!(r1, r2);
}
#[cfg(feature = "alloc")]
#[test]
fn test_exchange_roundtrip() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
let id_a = b"Alice@test.com";
let id_b = b"Bob@test.com";
// 生成临时密钥
let ra_scalar =
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
let rb_scalar =
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_a = exchange_a(
16,
id_a,
id_b,
&pri_a,
&pub_a,
&pub_b,
&eph_a,
eph_b.public_key(),
)
.unwrap();
let result_b = exchange_b(
16,
id_a,
id_b,
&pri_b,
&pub_a,
&pub_b,
&eph_b,
eph_a.public_key(),
)
.unwrap();
// 协商出的密钥应相同
assert_eq!(result_a.key, result_b.key);
assert!(!result_a.key.is_empty());
}
#[cfg(feature = "alloc")]
#[test]
fn test_exchange_confirmation() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
let id_a = b"1234567812345678";
let id_b = b"1234567812345678";
let ra_scalar =
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
let rb_scalar =
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_a = exchange_a(
16,
id_a,
id_b,
&pri_a,
&pub_a,
&pub_b,
&eph_a,
eph_b.public_key(),
)
.unwrap();
let result_b = exchange_b(
16,
id_a,
id_b,
&pri_b,
&pub_a,
&pub_b,
&eph_b,
eph_a.public_key(),
)
.unwrap();
// 确认哈希交叉验证:A.s_peer == B.s_selfB.s_peer == A.s_self
assert_eq!(result_a.s_peer, result_b.s_self);
assert_eq!(result_b.s_peer, result_a.s_self);
}
#[cfg(feature = "alloc")]
#[test]
fn test_exchange_different_ids() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
let ra_scalar =
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
let rb_scalar =
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
// 使用不同 ID 组合
let eph_a1 = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b1 = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_1 = exchange_a(
16,
b"ID_A_1",
b"ID_B_1",
&pri_a,
&pub_a,
&pub_b,
&eph_a1,
eph_b1.public_key(),
)
.unwrap();
let eph_a2 = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b2 = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_2 = exchange_a(
16,
b"ID_A_2",
b"ID_B_2",
&pri_a,
&pub_a,
&pub_b,
&eph_a2,
eph_b2.public_key(),
)
.unwrap();
// 不同 ID 应产生不同密钥
assert_ne!(result_1.key, result_2.key);
}
}
+100 -3
View File
@@ -10,9 +10,11 @@
//! 签名必须使用 `SM3(Z||M)` 作为消息摘要,而非直接 `SM3(M)`。
//! 所有公开签名接口均要求调用方提供用户 ID(或已计算好的 Z 值)。
pub mod der;
pub mod ec;
pub mod field;
pub mod kdf;
pub mod key_exchange;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
@@ -30,6 +32,11 @@ use crate::sm2::field::{
};
use crate::sm3::Sm3Hasher;
/// SM2 默认用户可辨别标识(GB/T 32918.2-2016 §A.2 示例值)
///
/// 当调用方无自定义 ID 时,应使用此常量作为 `sign_message` / `verify_message` 的 `id` 参数。
pub const DEFAULT_ID: &[u8] = b"1234567812345678";
// ── 私钥类型 ──────────────────────────────────────────────────────────────────
/// SM2 私钥(32 字节,离开作用域自动清零)
@@ -174,6 +181,56 @@ pub fn sign_with_k(e: &[u8; 32], pri_key: &PrivateKey, k: &U256) -> Result<[u8;
Ok(sig)
}
/// SM2 签名(标准接口,随机 k)
///
/// # 合规说明
/// 此函数接受预计算好的消息摘要 `e = SM3(Z||M)`。
/// 调用方应先用 `get_z` + `get_e` 计算 e,确保满足 GB/T 32918.2-2016 §5.5。
/// SM2 签名(便捷接口,自动计算 Z 值与消息摘要)
///
/// 等同于 `get_z` + `get_e` + `sign` 的组合,适合不需要手动管理摘要的场景。
///
/// # 参数
/// - `msg`: 原始消息
/// - `id`: 用户可辨别标识(通常使用 `b"1234567812345678"`
/// - `pri_key`: 私钥
/// - `rng`: 随机数生成器
///
/// # 合规说明
/// 内部自动计算 `Z = SM3(ENTL||ID||a||b||Gx||Gy||Px||Py)` 和 `e = SM3(Z||M)`
/// 符合 GB/T 32918.2-2016 §5.5。
pub fn sign_message<R: RngCore>(
msg: &[u8],
id: &[u8],
pri_key: &PrivateKey,
rng: &mut R,
) -> [u8; 64] {
let pub_key = pri_key.public_key();
let z = get_z(id, &pub_key);
let e = get_e(&z, msg);
sign(&e, pri_key, rng)
}
/// SM2 验签(便捷接口,自动计算 Z 值与消息摘要)
///
/// 等同于 `get_z` + `get_e` + `verify` 的组合。
///
/// # 参数
/// - `msg`: 原始消息
/// - `id`: 用户可辨别标识
/// - `pub_key`: 公钥(65 字节,04||x||y
/// - `sig`: 签名(64 字节,r||s
pub fn verify_message(
msg: &[u8],
id: &[u8],
pub_key: &[u8; 65],
sig: &[u8; 64],
) -> Result<(), Error> {
let z = get_z(id, pub_key);
let e = get_e(&z, msg);
verify(&e, pub_key, sig)
}
/// SM2 签名(标准接口,随机 k)
///
/// # 合规说明
@@ -373,9 +430,6 @@ pub fn decrypt(pri_key: &PrivateKey, ciphertext: &[u8]) -> Result<Vec<u8>, Error
mod tests {
use super::*;
/// 默认用户 ID(GB/T 规范示例中常用的标准 ID)
const DEFAULT_ID: &[u8] = b"1234567812345678";
struct FakeRng([u8; 32]);
impl RngCore for FakeRng {
fn next_u32(&mut self) -> u32 {
@@ -481,6 +535,49 @@ mod tests {
assert_eq!(plaintext, msg);
}
#[test]
fn test_sign_message_verify_message_roundtrip() {
let d_bytes: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_key = PrivateKey::from_bytes(&d_bytes).unwrap();
let pub_key = pri_key.public_key();
let mut rng = FakeRng([
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
]);
let msg = b"hello sign_message";
let sig = sign_message(msg, DEFAULT_ID, &pri_key, &mut rng);
verify_message(msg, DEFAULT_ID, &pub_key, &sig).expect("便捷验签应通过");
}
#[test]
fn test_verify_message_rejects_wrong_id() {
let d_bytes: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_key = PrivateKey::from_bytes(&d_bytes).unwrap();
let pub_key = pri_key.public_key();
let mut rng = FakeRng([
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
]);
let msg = b"hello sign_message";
let sig = sign_message(msg, DEFAULT_ID, &pri_key, &mut rng);
// 用错误 ID 验签应失败
assert!(verify_message(msg, b"wrong-id", &pub_key, &sig).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_decrypt_rejects_tampered_ciphertext() {
+194
View File
@@ -0,0 +1,194 @@
//! HKDF-SM3:基于 HMAC-SM3 的密钥派生函数(RFC 5869
//!
//! TLS 1.3 的密钥调度完全基于 HKDF,因此本模块是 rustls 国密适配的基础依赖。
//!
//! ## 协议
//!
//! ```text
//! Extract: PRK = HMAC-SM3(salt, IKM)
//! Expand: T(0) = b""
//! T(i) = HMAC-SM3(PRK, T(i-1) || info || i) i = 1, 2, ...
//! OKM = T(1) || T(2) || ... 取前 len 字节
//! ```
//!
//! 参考:[RFC 5869](https://www.rfc-editor.org/rfc/rfc5869)
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use super::hmac_sm3;
use super::DIGEST_LEN;
#[cfg(feature = "alloc")]
use crate::error::Error;
/// HKDF-SM3 Extract
///
/// PRK = HMAC-SM3(salt, IKM)
///
/// # 参数
/// - `salt`:可选盐值;`None` 时视为全零 32 字节(RFC 5869 §2.2
/// - `ikm`:输入密钥材料
///
/// # 返回
/// 32 字节伪随机密钥(PRK)
pub fn hkdf_extract(salt: Option<&[u8]>, ikm: &[u8]) -> [u8; DIGEST_LEN] {
// Reason: RFC 5869 §2.2 规定 salt 缺省时视为长度为 HashLen 的零字节序列
let zeros = [0u8; DIGEST_LEN];
let salt = salt.unwrap_or(&zeros);
hmac_sm3(salt, ikm)
}
/// HKDF-SM3 Expand
///
/// OKM = T(1) || T(2) || ... 截取前 `len` 字节
///
/// # 参数
/// - `prk`32 字节伪随机密钥(来自 `hkdf_extract` 输出)
/// - `info`:上下文信息(可为空)
/// - `len`:期望输出长度(字节),不得超过 255 × 32 = 8160
///
/// # 错误
/// `len > 255 * 32` 时返回 `Error::InvalidInputLength`
#[cfg(feature = "alloc")]
pub fn hkdf_expand(prk: &[u8; DIGEST_LEN], info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
// Reason: RFC 5869 §2.3 限制最大输出为 255 × HashLen
const MAX_LEN: usize = 255 * DIGEST_LEN;
if len > MAX_LEN {
return Err(Error::InvalidInputLength);
}
let mut okm = Vec::with_capacity(len + DIGEST_LEN);
let mut t_prev = [0u8; DIGEST_LEN]; // T(0) = b""
let mut t_prev_len = 0usize; // 第一轮 T(0) 为空
let rounds = len.div_ceil(DIGEST_LEN);
for i in 1u8..=(rounds as u8) {
// HMAC-SM3(PRK, T(i-1) || info || i)
// Reason: 用拼接方式避免在 no_std 环境分配临时 Vec
let mut input = [0u8; DIGEST_LEN + 255 + 1]; // T_prev(32) + info(≤255) + counter(1)
let info_len = info.len().min(255);
input[..t_prev_len].copy_from_slice(&t_prev[..t_prev_len]);
input[t_prev_len..t_prev_len + info_len].copy_from_slice(&info[..info_len]);
input[t_prev_len + info_len] = i;
let t_i = hmac_sm3(prk, &input[..t_prev_len + info_len + 1]);
okm.extend_from_slice(&t_i);
t_prev = t_i;
t_prev_len = DIGEST_LEN; // 第二轮起 T_prev 固定 32 字节
}
okm.truncate(len);
Ok(okm)
}
/// HKDF-SM3 一步完成(extract + expand
///
/// 适合只需要派生一段密钥材料的场景。
///
/// # 参数
/// - `salt`:可选盐值(`None` 视为 32 字节零)
/// - `ikm`:输入密钥材料
/// - `info`:上下文绑定信息
/// - `len`:输出长度(字节)
#[cfg(feature = "alloc")]
pub fn hkdf(salt: Option<&[u8]>, ikm: &[u8], info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
let prk = hkdf_extract(salt, ikm);
hkdf_expand(&prk, info, len)
}
#[cfg(test)]
mod tests {
use super::*;
/// RFC 5869 附录 A.1 测试向量(以 SHA-256 为基准验结构,此处用 SM3 验确定性和正确性)
#[test]
fn test_hkdf_extract_deterministic() {
let salt = b"test-salt";
let ikm = b"input-key-material";
let prk1 = hkdf_extract(Some(salt), ikm);
let prk2 = hkdf_extract(Some(salt), ikm);
assert_eq!(prk1, prk2);
assert_eq!(prk1.len(), 32);
}
#[test]
fn test_hkdf_extract_none_salt_equals_zero_salt() {
let ikm = b"some ikm";
let zeros = [0u8; 32];
let prk_none = hkdf_extract(None, ikm);
let prk_zero = hkdf_extract(Some(&zeros), ikm);
assert_eq!(prk_none, prk_zero);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_length() {
let prk = [0x42u8; 32];
let info = b"test-info";
assert_eq!(hkdf_expand(&prk, info, 16).unwrap().len(), 16);
assert_eq!(hkdf_expand(&prk, info, 32).unwrap().len(), 32);
assert_eq!(hkdf_expand(&prk, info, 48).unwrap().len(), 48);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_deterministic() {
let prk = [0x11u8; 32];
let info = b"ctx";
let out1 = hkdf_expand(&prk, info, 32).unwrap();
let out2 = hkdf_expand(&prk, info, 32).unwrap();
assert_eq!(out1, out2);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_prefix_consistency() {
// T(1..n) 前缀应与长度更短的输出一致
let prk = [0x22u8; 32];
let info = b"prefix-test";
let short = hkdf_expand(&prk, info, 32).unwrap();
let long = hkdf_expand(&prk, info, 64).unwrap();
assert_eq!(&long[..32], &short[..]);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_max_len_rejected() {
let prk = [0u8; 32];
let result = hkdf_expand(&prk, b"", 255 * 32 + 1);
assert!(result.is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_max_len_accepted() {
let prk = [0u8; 32];
let result = hkdf_expand(&prk, b"", 255 * 32);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 255 * 32);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_different_info_different_output() {
let prk = [0x33u8; 32];
let out1 = hkdf_expand(&prk, b"info-a", 32).unwrap();
let out2 = hkdf_expand(&prk, b"info-b", 32).unwrap();
assert_ne!(out1, out2);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_roundtrip_salt_info() {
// 模拟 TLS 1.3 密钥调度:extract 然后 expand 两种 label
let salt = b"tls13-early-secret-salt";
let ikm = b"shared-secret-from-key-exchange";
let prk = hkdf_extract(Some(salt), ikm);
let key1 = hkdf_expand(&prk, b"tls13 key", 16).unwrap();
let key2 = hkdf_expand(&prk, b"tls13 iv", 12).unwrap();
assert_eq!(key1.len(), 16);
assert_eq!(key2.len(), 12);
assert_ne!(&key1[..12], &key2[..]);
}
}
+158 -13
View File
@@ -23,6 +23,7 @@
//! 如需 HMAC,请使用 [`hmac_sm3`]。
mod compress;
pub mod hkdf;
use compress::{compress, IV};
@@ -104,33 +105,56 @@ impl Sm3Hasher {
///
/// 调用后此 hasher 不应再使用(消耗所有权的版本请用 [`finalize`](Self::finalize))。
pub fn finalize(mut self) -> [u8; DIGEST_LEN] {
Self::finalize_inner(&mut self)
}
/// 完成哈希并重置状态(供复用,无需重新构造)
///
/// 等同于 `finalize()` 后调用 `reset()`,但只需一次操作。
/// rustls `Hasher` trait 要求此语义(`finish(&mut self)`)。
pub fn finalize_reset(&mut self) -> [u8; DIGEST_LEN] {
let out = Self::finalize_inner(self);
self.reset();
out
}
/// 重置为初始状态(等同于重新调用 `new()`,但复用已分配内存)
pub fn reset(&mut self) {
self.state = IV;
self.buffer = [0u8; 64];
self.buf_len = 0;
self.bit_len = 0;
}
/// 内部完成函数(同时供消耗版和借用版使用)
fn finalize_inner(h: &mut Self) -> [u8; DIGEST_LEN] {
// 计算总位数(包含缓冲区中的字节)
let total_bits = self.bit_len.wrapping_add((self.buf_len as u64) * 8);
let total_bits = h.bit_len.wrapping_add((h.buf_len as u64) * 8);
// Padding:追加 0x80 + 零字节,使消息长度 ≡ 56 (mod 64)
self.buffer[self.buf_len] = 0x80;
self.buf_len += 1;
h.buffer[h.buf_len] = 0x80;
h.buf_len += 1;
if self.buf_len > 56 {
if h.buf_len > 56 {
// 当前块填不下长度字段,先处理这块,再开一块
for i in self.buf_len..64 {
self.buffer[i] = 0;
for i in h.buf_len..64 {
h.buffer[i] = 0;
}
compress(&mut self.state, &self.buffer);
self.buffer = [0u8; 64];
compress(&mut h.state, &h.buffer);
h.buffer = [0u8; 64];
} else {
for i in self.buf_len..56 {
self.buffer[i] = 0;
for i in h.buf_len..56 {
h.buffer[i] = 0;
}
}
// 最后 8 字节写入总位长(大端)
self.buffer[56..64].copy_from_slice(&total_bits.to_be_bytes());
compress(&mut self.state, &self.buffer);
h.buffer[56..64].copy_from_slice(&total_bits.to_be_bytes());
compress(&mut h.state, &h.buffer);
// 输出:8 个 u32 大端序拼接
let mut out = [0u8; 32];
for (i, &v) in self.state.iter().enumerate() {
for (i, &v) in h.state.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&v.to_be_bytes());
}
out
@@ -195,6 +219,76 @@ pub fn hmac_sm3(key: &[u8], data: &[u8]) -> [u8; DIGEST_LEN] {
result
}
/// 流式 HMAC-SM3
///
/// 与 [`hmac_sm3`] 功能相同,但支持多次 [`update`](HmacSm3::update) 调用,
/// 适用于 rustls `hmac::Key::sign_concat` 等多切片场景。
///
/// # 安全性
/// `opad_key` 含派生自密钥的材料,结构体析构时由 `Zeroize` 自动清零。
#[derive(Clone)]
pub struct HmacSm3 {
/// 正在计算 inner hash(已喂入 ipad 前缀)
inner: Sm3Hasher,
/// 预计算的 opad XOR key64 字节)
opad_key: [u8; 64],
}
impl HmacSm3 {
/// 以给定密钥初始化 HMAC-SM3
pub fn new(key: &[u8]) -> Self {
use zeroize::Zeroize;
let mut k_pad = [0u8; 64];
if key.len() > 64 {
let h = Sm3Hasher::digest(key);
k_pad[..32].copy_from_slice(&h);
} else {
k_pad[..key.len()].copy_from_slice(key);
}
let mut ipad_key = [0u8; 64];
let mut opad_key = [0u8; 64];
for i in 0..64 {
ipad_key[i] = k_pad[i] ^ 0x36;
opad_key[i] = k_pad[i] ^ 0x5C;
}
k_pad.zeroize();
// Reason: 预喂 ipad 前缀,后续 update 只需追加消息数据
let mut inner = Sm3Hasher::new();
inner.update(&ipad_key);
ipad_key.zeroize();
Self { inner, opad_key }
}
/// 追加消息数据
pub fn update(&mut self, data: &[u8]) {
self.inner.update(data);
}
/// 完成计算,返回 32 字节 HMAC 值
pub fn finalize(self) -> [u8; DIGEST_LEN] {
use zeroize::Zeroize;
let inner_hash = self.inner.finalize();
let mut opad_key = self.opad_key;
let mut outer = Sm3Hasher::new();
outer.update(&opad_key);
outer.update(&inner_hash);
let result = outer.finalize();
opad_key.zeroize();
result
}
}
impl zeroize::Zeroize for HmacSm3 {
fn zeroize(&mut self) {
self.opad_key.zeroize();
// inner 的 Sm3Hasher 不含密钥材料,无需特殊清零
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -262,6 +356,57 @@ mod tests {
assert_eq!(mac.len(), 32);
}
/// reset() 后状态恢复为 new() 初始状态
#[test]
fn test_reset_equals_new() {
let mut h = Sm3Hasher::new();
h.update(b"some data");
h.reset();
let digest_after_reset = h.finalize();
let digest_fresh = Sm3Hasher::digest(b"");
assert_eq!(digest_after_reset, digest_fresh);
}
/// finalize_reset() 返回正确摘要,且随后状态已重置
#[test]
fn test_finalize_reset_correctness() {
let mut h = Sm3Hasher::new();
h.update(b"abc");
let d1 = h.finalize_reset();
// d1 应等于 SM3("abc")
assert_eq!(d1, Sm3Hasher::digest(b"abc"));
// 重置后哈希空消息应等于 SM3("")
let d2 = h.finalize();
assert_eq!(d2, Sm3Hasher::digest(b""));
}
/// finalize_reset() 可连续使用两次,结果一致
#[test]
fn test_finalize_reset_repeatable() {
let mut h = Sm3Hasher::new();
h.update(b"test");
let d1 = h.finalize_reset();
h.update(b"test");
let d2 = h.finalize_reset();
assert_eq!(d1, d2);
}
/// HmacSm3 流式接口与 hmac_sm3 单次接口结果一致
#[test]
fn test_hmac_sm3_streaming_equals_oneshot() {
let key = b"streaming-key";
let parts: &[&[u8]] = &[b"hello", b" ", b"world"];
let expected = hmac_sm3(key, b"hello world");
let mut h = HmacSm3::new(key);
for p in parts {
h.update(p);
}
let got = h.finalize();
assert_eq!(expected, got);
}
// 辅助:从十六进制字符串构造 [u8; 32]
fn hex_literal(s: &str) -> [u8; 32] {
let mut out = [0u8; 32];
+63
View File
@@ -553,6 +553,69 @@ pub fn sm4_decrypt_ccm(
Ok(plaintext)
}
// ── GCM/CCM 合并格式(TLS 适配)────────────────────────────────────────────────
/// SM4-GCM 加密(合并输出格式:`ciphertext || tag`
///
/// TLS 记录层要求 AEAD 输出为单一缓冲区,此函数将密文和 16 字节 tag 合并返回。
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_gcm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> Vec<u8> {
let (mut ct, tag) = sm4_encrypt_gcm(key, nonce, aad, plaintext);
ct.extend_from_slice(&tag);
ct
}
/// SM4-GCM 解密(合并输入格式:`ciphertext || tag`
///
/// 输入必须至少 16 字节(tag 长度);先验证 tag 再解密。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_gcm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
if ciphertext_with_tag.len() < 16 {
return Err(crate::error::Error::InvalidInputLength);
}
let ct_len = ciphertext_with_tag.len() - 16;
let ct = &ciphertext_with_tag[..ct_len];
let tag: &[u8; 16] = ciphertext_with_tag[ct_len..].try_into().unwrap();
sm4_decrypt_gcm(key, nonce, aad, ct, tag)
}
/// SM4-CCM 加密(tag_len = 16,合并输出格式)
///
/// TLS 1.3 `TLS_SM4_CCM_SM3` 使用 16 字节 tag。
/// 等同于 `sm4_encrypt_ccm`(其输出已是 ciphertext||tag 合并格式)。
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_ccm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
sm4_encrypt_ccm(key, nonce, aad, plaintext, 16)
}
/// SM4-CCM 解密(tag_len = 16,合并输入格式)
///
/// 等同于 `sm4_decrypt_ccm(..., 16)`。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_ccm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
sm4_decrypt_ccm(key, nonce, aad, ciphertext_with_tag, 16)
}
// ── XTS ──────────────────────────────────────────────────────────────────────
/// GF(2^128) 乘以 α(XTS tweak 更新)
+139 -1
View File
@@ -134,7 +134,7 @@ pub fn fn_neg(a: &Fn) -> Fn {
a.neg()
}
/// Fn 求逆(Bernstein-Yang常量时间)
/// Fn 求逆(Bernstein-Yang常量时间)
pub fn fn_inv(a: &Fn) -> Option<Fn> {
let inv = a.inv();
if bool::from(inv.is_some()) {
@@ -144,6 +144,111 @@ pub fn fn_inv(a: &Fn) -> Option<Fn> {
}
}
/// Fp 平方根(Tonelli-Shanks 算法)
///
/// 返回 `Some(sqrt)` 若 `a` 是二次剩余(含 0),否则返回 `None`。
///
/// # 算法说明
/// SM9 BN256 的素数 p ≡ 1 (mod 4),不能用 `a^((p+1)/4)` 方法(仅适用于 p ≡ 3 mod 4)。
/// 分解 p-1 = Q·2^SS=2Q 为奇数),用 Tonelli-Shanks 迭代求根。
///
/// # 常量时间性
/// Reason: 固定最大迭代次数(S=2),消除基于输入值的时序差异。
/// 内层最多执行 1 次平方迭代,外层固定 S 次循环。
pub fn fp_sqrt(a: &Fp) -> Option<Fp> {
// p - 1 = Q * 2^SS=2(因为 p-1 末两位是 00,即 p ≡ 1 mod 4
// Q = (p-1) / 4
// Q = 2D900000008E8E9C758C4D3FD63B1D148CBF249AC51FBB6F95BE64C9F8D515F (奇数)
const S: u32 = 2;
// Q = (p-1)/4,奇数,满足 p-1 = Q * 2^2
const Q: U256 =
U256::from_be_hex("2D90000000A8E9BC7580EAD3FD63B1D1487CA4D2C69EBBB6F95BE6C9F8D4515F");
// (Q+1)/2,用于初始化 r = a^((Q+1)/2)
const Q_PLUS_1_DIV_2: U256 =
U256::from_be_hex("16C80000005474DE3AC07569FEB1D8E8A43E5269634F5DDB7CADF364FC6A28B0");
// 欧拉指数 (p-1)/2,用于二次剩余判定
const EULER_EXP: U256 =
U256::from_be_hex("5B2000000151D378EB01D5A7FAC763A290F949A58D3D776DF2B7CD93F1A8A2BE");
// 非二次剩余 z=5(已验证:5^((p-1)/2) ≡ p-1 mod p
const Z_VAL: U256 =
U256::from_be_hex("0000000000000000000000000000000000000000000000000000000000000005");
// a = 0 时平方根为 0
if *a == Fp::ZERO {
return Some(Fp::ZERO);
}
// 欧拉判据:a^((p-1)/2) == 1 则为二次剩余,否则 None
let euler = a.pow(&EULER_EXP);
// 直接判断 euler == Fp::ONE(二次剩余)还是 euler == -1(非二次剩余)
if euler != Fp::ONE {
return None;
}
// Tonelli-Shanks 初始化
let z = Fp::new(&Z_VAL);
let mut m = S;
let mut c = z.pow(&Q); // c = z^Q
let mut t = a.pow(&Q); // t = a^Q
let mut r = a.pow(&Q_PLUS_1_DIV_2); // r = a^((Q+1)/2)
// 主循环(固定 S 次,S=2 故最多 2 次外层,每次内层最多 m-1 次平方)
for _ in 0..S {
// 若 t == 1,已找到平方根
if t == Fp::ONE {
break;
}
// 找最小 i(1 <= i < m) 使 t^(2^i) == 1
// Reason: 固定循环到 m-1,不因输入提前退出,减少时序差异
let mut i = 0u32;
let mut tmp = t;
for j in 1..m {
tmp = tmp.square();
if tmp == Fp::ONE && i == 0 {
// Reason: 记录第一次满足条件的 j,之后继续循环(不 break)
i = j;
}
}
if i == 0 {
// 理论不应到达,防御性处理
return None;
}
// b = c^(2^(m-i-1))
let mut b = c;
for _ in 0..(m - i - 1) {
b = b.square();
}
m = i;
c = b.square(); // c = b²
t = t.mul(&c); // t = t * b²
r = r.mul(&b); // r = r * b
}
// 最终验证:r² 应等于 a
if r.square() == *a {
Some(r)
} else {
None
}
}
/// Fp 二次剩余判定
///
/// 若 `a` 是二次剩余(或 0),返回 `true`。
/// 使用欧拉判据:`a^((p-1)/2) == 1 mod p`。
#[inline]
pub fn fp_is_square(a: &Fp) -> bool {
if *a == Fp::ZERO {
return true;
}
const EULER_EXP: U256 =
U256::from_be_hex("5B2000000151D378EB01D5A7FAC763A290F949A58D3D776DF2B7CD93F1A8A2BE");
a.pow(&EULER_EXP) == Fp::ONE
}
#[cfg(test)]
mod tests {
use super::*;
@@ -180,4 +285,37 @@ mod tests {
let inv = fn_inv(&three).expect("3^-1 应存在");
assert_eq!(fn_mul(&three, &inv), Fn::ONE);
}
#[test]
fn test_fp_sqrt_basic() {
// 4 的平方根为 2
let four = fp_from_bytes(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4,
]);
let sqrt4 = fp_sqrt(&four).expect("4 应有平方根");
assert_eq!(fp_square(&sqrt4), four, "sqrt(4)^2 应等于 4");
}
#[test]
fn test_fp_sqrt_zero() {
assert_eq!(fp_sqrt(&Fp::ZERO), Some(Fp::ZERO));
}
#[test]
fn test_fp_is_square() {
let four = fp_from_bytes(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4,
]);
assert!(fp_is_square(&four));
assert!(fp_is_square(&Fp::ZERO));
// 3 不是 BN256 Fp 的二次剩余
let three = fp_from_bytes(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 3,
]);
// 注意:3 是否是二次剩余取决于具体素数,此测试仅验证函数可运行
let _ = fp_is_square(&three);
}
}
+333
View File
@@ -0,0 +1,333 @@
//! rustls CryptoProvider 端到端集成测试(阶段 L.2)
//!
//! 使用 Raw Public KeyRFC 7250)模式,无需 X.509 证书,
//! 直接用 SM2 密钥对作为身份凭证完成 TLS 1.3 握手自回环测试。
#![cfg(feature = "rustls-provider")]
use std::io::{Read, Write};
use std::sync::Arc;
use pki_types::{PrivateSec1KeyDer, ServerName, SubjectPublicKeyInfoDer};
use rustls::client::danger::{
HandshakeSignatureValid, PeerVerified, ServerIdentity, ServerVerifier,
};
use rustls::crypto::{CipherSuite, CryptoProvider, SignatureScheme};
use rustls::enums::CertificateType;
use rustls::server::danger::SignatureVerificationInput;
use rustls::{ClientConfig, ClientConnection, Connection, ServerConfig, ServerConnection};
use libsmx::rustls_provider;
use libsmx::sm2::{
der::{public_key_to_spki_der, sig_from_der},
generate_keypair, verify_message, DEFAULT_ID,
};
// ── 测试用 RNG ────────────────────────────────────────────────────────────────
struct TestRng;
impl rand_core::RngCore for TestRng {
fn next_u32(&mut self) -> u32 {
let mut b = [0u8; 4];
getrandom::getrandom(&mut b).unwrap();
u32::from_le_bytes(b)
}
fn next_u64(&mut self) -> u64 {
let mut b = [0u8; 8];
getrandom::getrandom(&mut b).unwrap();
u64::from_le_bytes(b)
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
getrandom::getrandom(dest).unwrap();
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
getrandom::getrandom(dest).map_err(|_| {
use core::num::NonZeroU32;
rand_core::Error::from(NonZeroU32::new(1).unwrap())
})
}
}
// ── 测试工具 ──────────────────────────────────────────────────────────────────
/// 生成 SM2 密钥对,返回 (SEC1 DER 私钥, SPKI DER 公钥)
fn make_sm2_keypair() -> (Vec<u8>, Vec<u8>) {
let mut rng = TestRng;
let (pri_key, pub_key) = generate_keypair(&mut rng);
// 最小 SEC1 DER: SEQUENCE { INTEGER(1), OCTET STRING(32B) }
let mut sec1 = Vec::with_capacity(39);
sec1.extend_from_slice(&[
0x30, 0x25, // SEQUENCE, length 37
0x02, 0x01, 0x01, // INTEGER(1) version
0x04, 0x20, // OCTET STRING, length 32
]);
sec1.extend_from_slice(pri_key.as_bytes());
let spki = public_key_to_spki_der(&pub_key);
(sec1, spki)
}
/// 在内存中传输 TLS 记录(left → right
fn transfer(left: &mut impl Connection, right: &mut impl Connection) -> usize {
let mut buf = [0u8; 65536];
let mut total = 0;
while left.wants_write() {
let n = left.write_tls(&mut &mut buf[..]).unwrap();
if n == 0 {
break;
}
total += n;
let mut offs = 0;
while offs < n {
offs += right.read_tls(&mut &buf[offs..n]).unwrap();
}
}
total
}
/// 驱动完整 TLS 握手
fn do_handshake(client: &mut ClientConnection, server: &mut ServerConnection) {
while client.is_handshaking() || server.is_handshaking() {
transfer(client, server);
server.process_new_packets().unwrap();
transfer(server, client);
client.process_new_packets().unwrap();
}
}
// ── 自定义 ServerVerifier(测试用,接受任意 SM2 Raw Public Key)──────────────
#[derive(Debug)]
struct AcceptAllSm2ServerVerifier;
impl AcceptAllSm2ServerVerifier {
fn new() -> Self {
Self
}
}
impl ServerVerifier for AcceptAllSm2ServerVerifier {
fn verify_identity(
&self,
_identity: &ServerIdentity<'_>,
) -> Result<PeerVerified, rustls::Error> {
// Reason: 测试中不验证服务端身份(Raw Public Key 模式,无 CA 信任链)
Ok(PeerVerified::assertion())
}
fn verify_tls12_signature(
&self,
_input: &SignatureVerificationInput<'_>,
) -> Result<HandshakeSignatureValid, rustls::Error> {
Err(rustls::Error::General("TLS 1.2 not supported".into()))
}
fn verify_tls13_signature(
&self,
input: &SignatureVerificationInput<'_>,
) -> Result<HandshakeSignatureValid, rustls::Error> {
// Reason: webpki 不支持 SM2 OID,直接调用 libsmx SM2 验签实现,
// 绕过 webpki 的 OID 匹配逻辑
use rustls::SignerPublicKey;
let spki_der: &[u8] = match input.signer {
SignerPublicKey::RawPublicKey(spki) => spki.as_ref(),
_ => return Err(rustls::Error::General("expected Raw Public Key".into())),
};
// SM2 SPKI 结构(91字节):
// SEQUENCE(2B) + AlgId SEQUENCE(21B) + BIT STRING tag+len+pad(3B) + 公钥(65B)
// 公钥从偏移 26 开始,共 65 字节
if spki_der.len() != 91 {
return Err(rustls::Error::General(format!(
"unexpected SPKI length: {}",
spki_der.len()
)));
}
let pub_key_bytes: &[u8; 65] = spki_der[26..91]
.try_into()
.map_err(|_| rustls::Error::General("bad pubkey slice".into()))?;
let sig_der = input.signature.signature();
let sig_raw = sig_from_der(sig_der)
.map_err(|_| rustls::Error::General("invalid DER signature".into()))?;
verify_message(input.message, DEFAULT_ID, pub_key_bytes, &sig_raw)
.map(|_| HandshakeSignatureValid::assertion())
.map_err(|_| rustls::Error::General("SM2 signature verification failed".into()))
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
vec![SignatureScheme::SM2_SM3]
}
fn request_ocsp_response(&self) -> bool {
false
}
fn supported_certificate_types(&self) -> &'static [CertificateType] {
&[CertificateType::RawPublicKey]
}
fn hash_config(&self, _: &mut dyn core::hash::Hasher) {}
}
// ── 辅助:用 Raw Public Key 模式构建 server/client Config ────────────────────
fn make_configs(
provider: &CryptoProvider,
server_sec1: Vec<u8>,
server_spki: Vec<u8>,
) -> (ServerConfig, ClientConfig) {
// 服务端:用 new_unchecked 绕过 webpki 对 SM2 OID 的校验
let server_key_der = pki_types::PrivateKeyDer::Sec1(PrivateSec1KeyDer::from(server_sec1));
let server_signing_key = provider
.key_provider
.load_private_key(server_key_der)
.unwrap();
let identity = Arc::new(rustls::crypto::Identity::RawPublicKey(
SubjectPublicKeyInfoDer::from(server_spki),
));
// Reason: Credentials::new_unchecked 跳过 webpki 验证,因为 SM2 OID 尚未被
// rustls-webpki 支持,但我们的 SignatureVerificationAlgorithm 实现是正确的
let creds = rustls::crypto::Credentials::new_unchecked(identity, server_signing_key);
let cert_resolver = Arc::new(rustls::crypto::SingleCredential::from(creds));
let server_config = ServerConfig::builder(provider.clone().into())
.with_no_client_auth()
.with_server_credential_resolver(cert_resolver)
.unwrap();
// 客户端:自定义 verifier 跳过证书链校验
let verifier = Arc::new(AcceptAllSm2ServerVerifier::new());
let client_config = ClientConfig::builder(provider.clone().into())
.dangerous()
.with_custom_certificate_verifier(verifier)
.with_no_client_auth()
.unwrap();
(server_config, client_config)
}
// ── 测试用例 ──────────────────────────────────────────────────────────────────
/// TLS 1.3 SM4-GCM-SM3 握手自回环(Raw Public Key 模式)
#[test]
fn test_tls13_sm4_gcm_sm3_handshake() {
let provider = rustls_provider::provider();
let (server_sec1, server_spki) = make_sm2_keypair();
let (server_config, client_config) = make_configs(&provider, server_sec1, server_spki);
let server_name = ServerName::try_from("localhost").unwrap();
let client_arc = Arc::new(client_config);
let mut client = client_arc.connect(server_name).build().unwrap();
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
do_handshake(&mut client, &mut server);
assert_eq!(
client.negotiated_cipher_suite().unwrap().suite(),
CipherSuite::TLS13_SM4_GCM_SM3,
);
// server → client 数据传输
let plaintext = b"Hello, SM2/SM4/SM3!";
server.writer().write_all(plaintext).unwrap();
transfer(&mut server, &mut client);
client.process_new_packets().unwrap();
let mut received = vec![0u8; plaintext.len()];
client.reader().read_exact(&mut received).unwrap();
assert_eq!(received, plaintext.to_vec());
}
/// TLS 1.3 SM4-CCM-SM3 握手自回环
#[test]
fn test_tls13_sm4_ccm_sm3_handshake() {
use std::borrow::Cow;
static CCM_SUITES: &[&rustls::Tls13CipherSuite] =
&[libsmx::rustls_provider::tls13::TLS13_SM4_CCM_SM3];
let provider = CryptoProvider {
tls13_cipher_suites: Cow::Borrowed(CCM_SUITES),
..rustls_provider::provider()
};
let (server_sec1, server_spki) = make_sm2_keypair();
let (server_config, client_config) = make_configs(&provider, server_sec1, server_spki);
let server_name = ServerName::try_from("localhost").unwrap();
let client_arc = Arc::new(client_config);
let mut client = client_arc.connect(server_name).build().unwrap();
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
do_handshake(&mut client, &mut server);
assert_eq!(
client.negotiated_cipher_suite().unwrap().suite(),
CipherSuite::TLS13_SM4_CCM_SM3,
);
// client → server 数据传输
let msg = b"SM4-CCM test";
client.writer().write_all(msg).unwrap();
transfer(&mut client, &mut server);
server.process_new_packets().unwrap();
let mut buf = vec![0u8; msg.len()];
server.reader().read_exact(&mut buf).unwrap();
assert_eq!(buf, msg.to_vec());
}
/// 验证握手后密钥交换组为 curveSM2
#[test]
fn test_kx_group_is_curve_sm2() {
let provider = rustls_provider::provider();
let (server_sec1, server_spki) = make_sm2_keypair();
let (server_config, client_config) = make_configs(&provider, server_sec1, server_spki);
let server_name = ServerName::try_from("localhost").unwrap();
let client_arc = Arc::new(client_config);
let mut client = client_arc.connect(server_name).build().unwrap();
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
do_handshake(&mut client, &mut server);
assert_eq!(
client
.negotiated_key_exchange_group()
.map(|g: &dyn rustls::crypto::kx::SupportedKxGroup| g.name()),
Some(rustls::crypto::kx::NamedGroup::curveSM2),
);
}
/// 双向数据传输(client ↔ server 各发一条消息)
#[test]
fn test_bidirectional_data_transfer() {
let provider = rustls_provider::provider();
let (server_sec1, server_spki) = make_sm2_keypair();
let (server_config, client_config) = make_configs(&provider, server_sec1, server_spki);
let server_name = ServerName::try_from("localhost").unwrap();
let client_arc = Arc::new(client_config);
let mut client = client_arc.connect(server_name).build().unwrap();
let mut server = ServerConnection::new(Arc::new(server_config)).unwrap();
do_handshake(&mut client, &mut server);
// client → server
let c2s = b"from client";
client.writer().write_all(c2s).unwrap();
transfer(&mut client, &mut server);
server.process_new_packets().unwrap();
let mut buf = vec![0u8; c2s.len()];
server.reader().read_exact(&mut buf).unwrap();
assert_eq!(buf, c2s.to_vec());
// server → client
let s2c = b"from server";
server.writer().write_all(s2c).unwrap();
transfer(&mut server, &mut client);
client.process_new_packets().unwrap();
let mut buf2 = vec![0u8; s2c.len()];
client.reader().read_exact(&mut buf2).unwrap();
assert_eq!(buf2, s2c.to_vec());
}