添加 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:
+5
-3
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
[](LICENSE)
|
||||
[](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
@@ -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"></></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<()></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>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
@@ -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"></></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 |
@@ -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 不消耗 ctx,fork 后原 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 SPKI:alg(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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
// 将明文写回 payload(in-place),然后截断
|
||||
let plain_len = plaintext.len();
|
||||
|
||||
@@ -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 验签(使用默认 ID,GB/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
@@ -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 示例 1:SM3("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 {
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
//! rustls CryptoProvider 端到端集成测试(阶段 L.2)
|
||||
//!
|
||||
//! 使用 Raw Public Key(RFC 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());
|
||||
}
|
||||
Reference in New Issue
Block a user