添加 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
This commit is contained in:
huangxt
2026-03-09 18:51:40 +08:00
parent 7c159343f8
commit 21e7e65b32
16 changed files with 1860 additions and 35 deletions
+5 -3
View File
@@ -52,6 +52,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **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
@@ -92,9 +97,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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
### CI
- Optimized sanity_check.sh to skip test code, avoiding false positives
## [0.1.0] - 2025-03-07
Generated
+137 -2
View File
@@ -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]]
@@ -437,6 +453,12 @@ dependencies = [
"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"
@@ -638,12 +660,29 @@ 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",
]
@@ -657,6 +696,17 @@ 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"
@@ -715,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"
@@ -775,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"
@@ -858,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]]
@@ -867,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"
@@ -876,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"
+7 -3
View File
@@ -3,13 +3,14 @@ name = "libsmx"
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,7 +36,7 @@ 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 = { version = "0.24.0-dev.0", path = "../rustls/rustls", optional = true, 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 }
@@ -51,6 +52,9 @@ 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"
+17 -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 |
|-----------|----------|-------------|
@@ -151,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`:
@@ -159,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.
@@ -211,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.
+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

+56
View File
@@ -49,3 +49,59 @@ impl crypto::hash::Context for Sm3Context {
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);
}
}
+38
View File
@@ -43,3 +43,41 @@ impl hmac::Key for Sm3HmacKey {
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);
}
}
+54 -2
View File
@@ -3,7 +3,9 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use rustls::crypto::kx::{ActiveKeyExchange, NamedGroup, SharedSecret, StartedKeyExchange, SupportedKxGroup};
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};
@@ -77,7 +79,57 @@ impl rand_core::RngCore for Sm2Rng {
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()))
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());
}
}
+3 -1
View File
@@ -71,6 +71,8 @@ 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()))
Err(Error::General(
"SM ticket factory not yet implemented".into(),
))
}
}
+89 -4
View File
@@ -18,10 +18,14 @@ 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()))?,
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 }))
@@ -78,3 +82,84 @@ impl Signer for Sm2Signer {
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();
}
}
+7 -7
View File
@@ -4,9 +4,8 @@ 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,
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;
@@ -144,10 +143,11 @@ impl MessageDecrypter for Sm4GcmDecrypter {
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)?;
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();
+55 -5
View File
@@ -1,7 +1,7 @@
//! SM2 验签 → rustls `SignatureVerificationAlgorithm`
use pki_types::{AlgorithmIdentifier, SignatureVerificationAlgorithm};
use rustls::crypto::{WebPkiSupportedAlgorithms, SignatureScheme};
use rustls::crypto::{SignatureScheme, WebPkiSupportedAlgorithms};
use crate::sm2::{der::sig_from_der, verify_message, DEFAULT_ID};
@@ -31,8 +31,7 @@ impl SignatureVerificationAlgorithm for Sm2Sm3Algorithm {
AlgorithmIdentifier::from_slice(&[
0x30, 0x13, // SEQUENCE
0x06, 0x07, // OID id-ecPublicKey
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
0x06, 0x08, // OID SM2
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, // OID SM2
0x2a, 0x81, 0x1c, 0xcf, 0x55, 0x01, 0x82, 0x2d,
])
}
@@ -53,11 +52,62 @@ impl SignatureVerificationAlgorithm for Sm2Sm3Algorithm {
.map_err(|_| pki_types::InvalidSignature)?;
// DER 解码签名
let sig_raw =
sig_from_der(signature).map_err(|_| pki_types::InvalidSignature)?;
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());
}
}
+1 -7
View File
@@ -291,8 +291,6 @@ impl zeroize::Zeroize for HmacSm3 {
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
extern crate alloc;
use super::*;
/// GB/T 32905-2016 附录 A 示例 1SM3("abc")
@@ -399,11 +397,7 @@ mod tests {
let key = b"streaming-key";
let parts: &[&[u8]] = &[b"hello", b" ", b"world"];
let mut all = alloc::vec![];
for p in parts {
all.extend_from_slice(p);
}
let expected = hmac_sm3(key, &all);
let expected = hmac_sm3(key, b"hello world");
let mut h = HmacSm3::new(key);
for p in parts {
+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());
}