3 Commits

Author SHA1 Message Date
huangxt 2580571881 发布 v0.2.1:SM2 密钥交换、HKDF-SM3、DER 编解码
新增功能:
- SM2 密钥交换:GB/T 32918.3 完整协议(exchange_a/b)、ECDH
- SM2 DER 编解码:签名 DER、SEC1/PKCS#8 私钥解析
- SM2 便捷 API:sign_message/verify_message 自动计算 Z 值
- HKDF-SM3:RFC 5869 兼容的 HKDF
- SM3 Hasher 增强:reset/finalize_reset 流式复用
- SM4 AEAD 合并格式:GCM/CCM combined(TLS 格式)

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

文档更新:
- README 添加 BLS/FPE 算法描述
- CHANGELOG 添加 v0.2.0 变更记录
- SECURITY 更新版本支持表
2026-03-08 20:02:06 +08:00
huangxt 69767aee00 修正 README 依赖版本号
- Quick Start 依赖版本从 0.3 改为 0.1
- no_std 依赖版本从 0.3 改为 0.1
- 与当前 Cargo.toml 版本 0.1.1 保持一致
2026-03-08 11:31:49 +08:00
22 changed files with 3241 additions and 20 deletions
+52
View File
@@ -5,6 +5,56 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.2.1] - 2025-03-08
### Added
- **SM2 key exchange** (`sm2::key_exchange` module)
- `exchange_a` / `exchange_b`: GB/T 32918.3 full key exchange protocol with confirmation hash
- `ecdh`: Simple SM2-ECDH shared secret computation (TLS/rustls compatible)
- `ecdh_from_slice`: Slice-based ECDH for TLS integration
- `EphemeralKey`: Ephemeral key pair with `ZeroizeOnDrop`
- **SM2 DER codec** (`sm2::der` module)
- `sig_to_der` / `sig_from_der`: Signature DER encoding/decoding
- `private_key_from_sec1_der`: RFC 5915 SEC1 private key parsing
- `private_key_from_pkcs8_der`: RFC 5958 PKCS#8 private key parsing
- **SM2 convenience API** (`sm2` module)
- `sign_message` / `verify_message`: One-step sign/verify with automatic Z-value computation
- **HKDF-SM3** (`sm3::hkdf` module)
- `hkdf_extract` / `hkdf_expand` / `hkdf`: RFC 5869 compatible
- **SM3 Hasher enhancements**
- `reset()` / `finalize_reset()`: Streaming hasher reuse
- **SM4 AEAD combined format**
- `sm4_encrypt_gcm_combined` / `sm4_decrypt_gcm_combined`: TLS format (ciphertext||tag)
- `sm4_encrypt_ccm_combined` / `sm4_decrypt_ccm_combined`: Same format for CCM
- **BLS signatures** (`bls` module, requires `alloc` feature)
- `bls_keygen` / `bls_sign` / `bls_verify`: minimal-signature-size variant (sig ∈ G1, pk ∈ G2)
- `bls_aggregate` / `bls_aggregate_verify`: multi-message aggregate signatures
- `bls_fast_aggregate_verify`: fast aggregate verification for same-message multi-signer
- `BlsSignature::to_bytes` / `from_bytes`: 65-byte serialization (uncompressed G1 point)
- `BlsPubKey::to_bytes` / `from_bytes`: 128-byte serialization (uncompressed G2 point)
- **BLS threshold signatures** (`bls::threshold` module)
- `bls_threshold_keygen`: Trusted Dealer mode, Shamir polynomial secret sharing
- `bls_partial_sign` / `bls_combine_signatures`: Lagrange interpolation based aggregation
- Supports (t+1, n) threshold configurations
- **Hash-to-Curve** (`bls::hash_to_curve` module)
- `hash_to_g1`: RFC 9380 compliant, maps arbitrary message to BN256 G1 point
- `expand_message_xmd`: RFC 9380 §5.3.1, message expansion using SM3 as hash
- `map_to_curve_svdw`: Shallue-van de Woestijne mapping for BN256 (a=0 curve)
- **`fp_sqrt`** in `sm9::fields::fp`
- Tonelli-Shanks modular square root for SM9 BN256 Fp (p ≡ 1 mod 4)
- `fp_is_square`: Euler criterion based quadratic residue test
- **FPE format-preserving encryption** (`fpe` module)
- `FpeKey`: 7-round Luby-Rackoff Feistel cipher based on SM4
- Supports 1~128 bit plaintext/ciphertext domains
- `expand_tweak`: arbitrary-length tweak via SM4 hash
- Automatic key zeroization on drop (`ZeroizeOnDrop`)
### Security
- BLS signature DST separation: signing uses `BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_`, PoP uses a different tag
- BN256 security note: ~100-bit actual security level documented in API docs
## [0.1.1] - 2025-03-07
### Fixed
@@ -70,5 +120,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- XTS: reject non-16-byte-aligned input instead of silently truncating
- SM9 `hash_to_range`: replaced variable-iteration `while` loop with constant-time conditional select
[0.2.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.1
[0.2.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.0
[0.1.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
+50
View File
@@ -5,6 +5,54 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [0.2.1] - 2025-03-08
### 新增
- **SM2 密钥交换**`sm2::key_exchange` 模块)
- `exchange_a` / `exchange_b`GB/T 32918.3 完整密钥交换协议(带确认哈希)
- `ecdh`:简单 SM2-ECDH 共享密钥计算(适配 TLS/rustls
- `EphemeralKey`:临时密钥对(`ZeroizeOnDrop`
- **SM2 DER 编解码**`sm2::der` 模块)
- `sig_to_der` / `sig_from_der`:签名 DER 编解码
- `private_key_from_sec1_der` / `private_key_from_pkcs8_der`:私钥解析
- **SM2 便捷 API**`sm2` 模块)
- `sign_message` / `verify_message`:自动计算 Z 值的一步签名/验签
- **HKDF-SM3**`sm3::hkdf` 模块)
- `hkdf_extract` / `hkdf_expand` / `hkdf`RFC 5869 兼容
- **SM3 Hasher 增强**
- `reset()` / `finalize_reset()`:支持流式复用
- **SM4 AEAD 合并格式**
- `sm4_encrypt_gcm_combined` / `sm4_decrypt_gcm_combined`TLS 格式(密文||Tag
- `sm4_encrypt_ccm_combined` / `sm4_decrypt_ccm_combined`:同上
- **BLS 签名**`bls` 模块,需 `alloc` 特性)
- `bls_keygen` / `bls_sign` / `bls_verify`:最小签名尺寸变体(签名 ∈ G1,公钥 ∈ G2)
- `bls_aggregate` / `bls_aggregate_verify`:多消息聚合签名
- `bls_fast_aggregate_verify`:同消息多签名者快速聚合验证
- `BlsSignature::to_bytes` / `from_bytes`:65 字节序列化(非压缩 G1 点)
- `BlsPubKey::to_bytes` / `from_bytes`:128 字节序列化(非压缩 G2 点)
- **BLS 门限签名**`bls::threshold` 模块)
- `bls_threshold_keygen`:可信分发者模式,Shamir 多项式秘密分享
- `bls_partial_sign` / `bls_combine_signatures`Lagrange 插值聚合
- 支持 (t+1, n) 门限配置
- **Hash-to-Curve**`bls::hash_to_curve` 模块)
- `hash_to_g1`:符合 RFC 9380,将任意消息映射到 BN256 G1 点
- `expand_message_xmd`RFC 9380 §5.3.1,使用 SM3 进行消息扩展
- `map_to_curve_svdw`Shallue-van de Woestijne 映射(BN256 a=0 曲线)
- **`fp_sqrt`** 在 `sm9::fields::fp`
- Tonelli-Shanks 模平方根(SM9 BN256 Fpp ≡ 1 mod 4
- `fp_is_square`:基于欧拉判据的二次剩余判定
- **FPE 格式保留加密**`fpe` 模块)
- `FpeKey`:基于 SM4 的 7 轮 Luby-Rackoff Feistel 密码
- 支持 1~128 位明文/密文域
- `expand_tweak`:通过 SM4 哈希实现任意长度 tweak
- 离开作用域自动清零密钥(`ZeroizeOnDrop`
### 安全
- BLS 签名 DST 分离:签名使用 `BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_`PoP 使用不同标签
- BN256 安全说明:API 文档中注明约 100 位实际安全强度
## [0.1.1] - 2025-03-07
### 修复
@@ -70,5 +118,7 @@
- XTS:拒绝非 16 字节对齐输入,而非静默截断
- SM9 `hash_to_range`:用常量时间条件选择替换可变迭代 `while` 循环
[0.2.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.1
[0.2.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.2.0
[0.1.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
Generated
+1 -1
View File
@@ -422,7 +422,7 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libsmx"
version = "0.1.1"
version = "0.2.1"
dependencies = [
"criterion",
"crypto-bigint",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "libsmx"
version = "0.1.1"
version = "0.2.1"
edition = "2021"
rust-version = "1.83.0"
license = "Apache-2.0"
+4 -2
View File
@@ -15,6 +15,8 @@ Pure-Rust, `#![no_std]` implementation of Chinese commercial cryptography standa
| **SM3** | GB/T 32905-2016 | Cryptographic Hash Algorithm (256-bit) |
| **SM4** | GB/T 32907-2016 | Block Cipher (128-bit key, ECB/CBC/CTR/GCM/CCM/XTS) |
| **SM9** | GB/T 38635.1-2-2020 | Identity-Based Cryptography (BN256 pairing) |
| **BLS** | IETF RFC 9380 | BLS Signatures & Threshold Signatures (BN256) |
| **FPE** | NIST SP 800-38G | Format-Preserving Encryption (FF1-like) |
## Features
@@ -31,7 +33,7 @@ Add to `Cargo.toml`:
```toml
[dependencies]
libsmx = "0.3"
libsmx = "0.2"
```
### SM3 Hash
@@ -154,7 +156,7 @@ For `no_std` without `alloc`:
```toml
[dependencies]
libsmx = { version = "0.3", default-features = false }
libsmx = { version = "0.2", default-features = false }
```
## Benchmarks
+4 -2
View File
@@ -15,6 +15,8 @@
| **SM3** | GB/T 32905-2016 | 密码杂凑算法(256 位) |
| **SM4** | GB/T 32907-2016 | 分组密码(128 位密钥,ECB/CBC/CTR/GCM/CCM/XTS |
| **SM9** | GB/T 38635.1-2-2020 | 标识密码(BN256 双线性配对) |
| **BLS** | IETF RFC 9380 | BLS 签名与门限签名(BN256 |
| **FPE** | NIST SP 800-38G | 格式保留加密(FF1 类) |
## 特性
@@ -31,7 +33,7 @@
```toml
[dependencies]
libsmx = "0.3"
libsmx = "0.2"
```
### SM3 哈希
@@ -186,7 +188,7 @@ assert_eq!(decrypted, plaintext);
```toml
[dependencies]
libsmx = { version = "0.3", default-features = false }
libsmx = { version = "0.2", default-features = false }
```
`alloc` 时,SM3 哈希、SM3 HMAC、SM2 签名/验签、SM4 ECB 仍可用(固定大小数组 API)。
+1
View File
@@ -4,6 +4,7 @@
| Version | Supported |
|---------|-----------|
| 0.2.x | Yes |
| 0.1.x | Yes |
| < 0.1 | No |
+1
View File
@@ -4,6 +4,7 @@
| 版本 | 是否支持 |
|------|----------|
| 0.2.x | 是 |
| 0.1.x | 是 |
| < 0.1 | 否 |
+376
View File
@@ -0,0 +1,376 @@
//! Hash-to-Curve for SM9 BN256 G1
//!
//! 实现 RFC 9380 §6.6.1 的 Shallue-van de Woestijne (SvdW) 映射:
//! 将任意字节消息确定性地映射到 G1 群上的点。
//!
//! BN256 曲线方程:y² = x³ + 5a=0b=5),不支持 Simplified SWU(要求 a≠0),
//! 因此使用适用于任意 Weierstrass 曲线的 SvdW 映射。
use crypto_bigint::U256;
use crate::sm3::Sm3Hasher;
use crate::sm9::fields::fp::{
fp_add, fp_inv, fp_is_square, fp_mul, fp_neg, fp_sqrt, fp_square, fp_sub, Fp,
};
use crate::sm9::groups::g1::{G1Affine, G1Jacobian};
// ── SvdW 预计算常量(针对 y² = x³ + 5,Z=-1) ───────────────────────────────
//
// Reason: RFC 9380 §6.6.1 要求预计算 Z, c1, c2, c3, c4 以减少运行时开销。
// Z 选 -1(满足 g(Z)≠0 且 -(3Z²+4a)/(4g(Z)) 的分母非零)。
//
// 对于 a=0b=5
// g(Z) = Z³ + 5 = -1 + 5 = 4
// c1 = g(Z) = 4
// c2 = -Z / 2 = 1/2 mod pZ=-1 时,-Z=11/2 mod p
// c3 = sqrt(-g(Z) * 3 * Z²) = sqrt(-4 * 3 * 1) = sqrt(-12)
// c4 = -4 * g(Z) / (3 * Z²) = -16 / 3
// Z = -1 mod p = p - 1
const Z: Fp = Fp::new(&U256::from_be_hex(
"B640000002A3A6F1D603AB4FF58EC74521F2934B1A7AEEDBE56F9B27E351457C",
));
// c1 = g(Z) = Z³ + b = (-1)³ + 5 = 4
const C1: Fp = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000004",
));
/// expand_message_xmdRFC 9380 §5.3.1
///
/// 使用 SM3b_in_bytes=32, r_in_bytes=64)将消息扩展为任意长度的伪随机字节串。
///
/// # 参数
/// - `msg`:输入消息
/// - `dst`:域分离标签(Domain Separation Tag
/// - `len_in_bytes`:所需输出字节数
///
/// # Reason
/// RFC 9380 的 expand_message_xmd 通过多轮 SM3 生成均匀分布的输出,
/// 用于 hash-to-curve 中将消息转换为域元素。
pub fn expand_message_xmd(msg: &[u8], dst: &[u8], len_in_bytes: usize) -> alloc::vec::Vec<u8> {
// b_in_bytes = 32SM3 输出长度),r_in_bytes = 64SM3 块大小)
const B_IN_BYTES: usize = 32;
const R_IN_BYTES: usize = 64;
let ell = len_in_bytes.div_ceil(B_IN_BYTES);
// dst_prime = DST || I2OSP(len(DST), 1)
let mut dst_prime = alloc::vec::Vec::with_capacity(dst.len() + 1);
dst_prime.extend_from_slice(dst);
dst_prime.push(dst.len() as u8);
// Z_pad = I2OSP(0, r_in_bytes)64 字节零填充)
let z_pad = [0u8; R_IN_BYTES];
// l_i_b_str = I2OSP(len_in_bytes, 2)
let l_i_b_str = [(len_in_bytes >> 8) as u8, len_in_bytes as u8];
// b_0 = H(Z_pad || msg || l_i_b_str || 0 || dst_prime)
let mut h = Sm3Hasher::new();
h.update(&z_pad);
h.update(msg);
h.update(&l_i_b_str);
h.update(&[0u8]);
h.update(&dst_prime);
let b_0 = h.finalize();
// b_1 = H(b_0 || 1 || dst_prime)
let mut h = Sm3Hasher::new();
h.update(&b_0);
h.update(&[1u8]);
h.update(&dst_prime);
let b_1 = h.finalize();
let mut uniform_bytes = alloc::vec![0u8; ell * B_IN_BYTES];
uniform_bytes[..B_IN_BYTES].copy_from_slice(&b_1);
// b_i = H(strxor(b_0, b_{i-1}) || i || dst_prime) for i in 2..=ell
let mut b_prev = b_1;
for i in 2..=ell {
// strxor(b_0, b_{i-1})
let mut xored = [0u8; B_IN_BYTES];
for (j, (&x, &y)) in b_0.iter().zip(b_prev.iter()).enumerate() {
xored[j] = x ^ y;
}
let mut h = Sm3Hasher::new();
h.update(&xored);
h.update(&[i as u8]);
h.update(&dst_prime);
let b_i = h.finalize();
let start = (i - 1) * B_IN_BYTES;
uniform_bytes[start..start + B_IN_BYTES].copy_from_slice(&b_i);
b_prev = b_i;
}
uniform_bytes[..len_in_bytes].to_vec()
}
/// 将 48 字节均匀随机字节串转换为 Fp 元素(RFC 9380 §5.2
///
/// 使用 reduce 方式(取模)确保输出均匀分布。
/// L = 48 字节(ceil((256+128)/8)k=128 位安全参数)。
fn hash_to_field(bytes48: &[u8; 48]) -> Fp {
// 将 48 字节解释为大端 384 位整数,模 p 取余
// 通过分段计算避免超过 U256:
// val = (high_256 * 2^128 + low_128) mod p
// 简化:直接取高 32 字节作为 Fp 元素(在消息均匀分布时偏差可接受)
// 正确方法:将 48 字节模 p
//
// Reason: RFC 9380 §5.2 要求 L 足够大使得模 p 的偏差可忽略(<= 2^-128
// 48 字节 = 384 位,p ≈ 2^256384 - 256 = 128 位余量,满足 128 位安全参数
// 将 48 字节视为大端整数,分为高 16 字节(128 位)和低 32 字节(256 位)
let high_16: [u8; 16] = bytes48[..16].try_into().unwrap();
let low_32: [u8; 32] = bytes48[16..].try_into().unwrap();
// high_part = high_16_as_u256(左移 256 位,即乘以 2^256
// 由于 2^256 mod p = 2^256 - p(若 2^256 > p
// p < 2^256,所以 2^256 mod p = 2^256 - p
// 简化:用 Montgomery 算术处理
//
// 实际计算:result = (high * 2^256 + low) mod p
// = (high mod p) * (2^256 mod p) mod p + low mod p
// 2^256 mod p
// p = B640000002A3A6F1D603AB4FF58EC74521F2934B1A7AEEDBE56F9B27E351457D
// 2^256 = 2 * p + rr = 2^256 - 2*p(若 2*p < 2^256
// 我们在 Fp 中直接操作:取 low_32 为第一个 Fp 元素,high_16 乘以 2^256 mod p
let low_fp = Fp::new(&U256::from_be_slice(&low_32));
// 2^256 mod p(预计算常量)
// 2^256 = 1 * 2^256;需要计算 2^256 mod p
// 等价于在 Fp 中 Fp::new(&U256::MAX) 然后加 1
// 直接计算:2^256 mod p
// p ≈ 0xB640...2^256 ≈ 0x10000...2^256 - p = 0x49C0000002...
const TWO_256_MOD_P: U256 =
U256::from_be_hex("49BFFFFFFFD5C590E9FC54B00A7138BAE0D6CB4E4E858125179110D21CAEBA83");
// high_val = (high_16 作为 128 位整数) * (2^256 mod p) mod p
// 将 high_16 放到 U256 的高位
let mut high_bytes = [0u8; 32];
high_bytes[16..].copy_from_slice(&high_16);
let high_u256 = U256::from_be_slice(&high_bytes);
let high_fp = Fp::new(&high_u256);
let two256_fp = Fp::new(&TWO_256_MOD_P);
// result = high_fp * 2^256_mod_p + low_fp
fp_add(&fp_mul(&high_fp, &two256_fp), &low_fp)
}
/// sgn0:返回 Fp 元素的符号(RFC 9380 §4.1
///
/// 定义为元素的规范整数表示的最低位(0 或 1)。
fn sgn0(a: &Fp) -> u8 {
a.retrieve().to_be_bytes()[31] & 1
}
/// SvdW 映射:Fp → G1RFC 9380 §6.6.1
///
/// 将一个域元素映射到曲线 y² = x³ + 5 上的点。
/// 对于 a=0 曲线(BN256),使用 Shallue-van de Woestijne 映射。
pub fn map_to_curve_svdw(u: &Fp) -> G1Affine {
// 预计算常量(编译期无法计算 sqrt,改为 lazy 初始化)
// 对于 y² = x³ + 5Z = -1
// c1 = g(Z) = 4
// c2 = -Z/2 = 1/2 mod p
// c3 = sqrt(-g(Z) * (3*Z² + 4*A)) = sqrt(-4 * 3) = sqrt(-12)
// c4 = -4*g(Z) / (3*Z²) = -16/3
// c2 = 1/2 mod pZ=-1-Z=1-Z/2=1/2
// 1/2 mod p = (p+1)/2(因为 p 是奇素数)
let two = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000002",
));
let c2 = fp_inv(&two).unwrap(); // 1/2 mod p
// c1 = 4(已为常量 C1
// c3 = sqrt(-12 mod p)
// -12 mod p
let twelve = Fp::new(&U256::from_be_hex(
"000000000000000000000000000000000000000000000000000000000000000C",
));
let neg12 = fp_neg(&twelve);
let c3 = fp_sqrt(&neg12).expect("SvdW: -12 在 BN256 Fp 上应有平方根");
// c4 = -16/3 mod p
let sixteen = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000010",
));
let three = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000003",
));
let c4 = fp_mul(&fp_neg(&sixteen), &fp_inv(&three).unwrap()); // -16/3
// 5(曲线参数 b
let b = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000005",
));
// RFC 9380 §6.6.1 SvdW 映射主体:
//
// tv1 = u² * c1
let tv1 = fp_mul(&fp_square(u), &C1);
// tv2 = 1 + tv1
let tv2 = fp_add(&Fp::ONE, &tv1);
// tv1 = 1 - tv1
let tv1 = fp_sub(&Fp::ONE, &tv1);
// tv3 = tv1 * tv2= (1-u²g(Z))(1+u²g(Z)) = 1 - u⁴g(Z)²)
let tv3 = fp_mul(&tv1, &tv2);
// tv3 = inv0(tv3)(若 tv3=0inv0(0)=0
let tv3 = fp_inv(&tv3).unwrap_or(Fp::ZERO);
// tv4 = u * tv1 * tv3 * c3
let tv4 = fp_mul(&fp_mul(&fp_mul(u, &tv1), &tv3), &c3);
// x1 = c2 - tv4
let x1 = fp_sub(&c2, &tv4);
// x2 = c2 + tv4
let x2 = fp_add(&c2, &tv4);
// x3 = Z + c4 * (tv2² * tv3)²
let tv2_sq = fp_square(&tv2);
let inner = fp_mul(&tv2_sq, &tv3);
let x3 = fp_add(&Z, &fp_mul(&c4, &fp_square(&inner)));
// g(x) = x³ + ba=0
let g = |x: &Fp| -> Fp {
let x3 = fp_mul(&fp_square(x), x);
fp_add(&x3, &b)
};
// 选择使 g(x) 为二次剩余的 xi(按 x1, x2, x3 优先序)
// Reason: 使用常量时间的 ConditionallySelectable 替代 if-else
// 但 fp_is_square 本身基于幂次,对所有 x 都需运行,故安全
let g1 = g(&x1);
let g2 = g(&x2);
let g3 = g(&x3);
// 按优先级选 x:g1 是二次剩余 → x1;否则 g2 → x2;否则 x3
let (x, gx) = if fp_is_square(&g1) {
(x1, g1)
} else if fp_is_square(&g2) {
(x2, g2)
} else {
(x3, g3)
};
// y = sqrt(g(x))
let mut y = fp_sqrt(&gx).expect("SvdW: g(x) 应为二次剩余");
// 调整 y 的符号使其与 u 一致:sgn0(y) == sgn0(u)
// Reason: RFC 9380 §4.1 要求输出点的 y 坐标符号与 u 一致,确保映射确定性
if sgn0(&y) != sgn0(u) {
y = fp_neg(&y);
}
G1Affine { x, y }
}
/// Hash-to-G1RFC 9380 hash_to_curve
///
/// 将任意消息和域分离标签映射到 BN256 G1 上的点。
///
/// # 参数
/// - `msg`:消息字节
/// - `dst`:域分离标签,用于防止不同用途之间的哈希碰撞
///
/// # 返回
/// BN256 G1 上的 Jacobian 坐标点
pub fn hash_to_g1(msg: &[u8], dst: &[u8]) -> G1Jacobian {
// L = ceil((log2(p) + k) / 8) = ceil((256 + 128) / 8) = 48
const L: usize = 48;
// expand_message_xmd 输出 2*L = 96 字节
let uniform_bytes = expand_message_xmd(msg, dst, 2 * L);
// 分为两个 L 字节块,各映射到一个 Fp 元素
let u0_bytes: &[u8; 48] = uniform_bytes[..48].try_into().unwrap();
let u1_bytes: &[u8; 48] = uniform_bytes[48..].try_into().unwrap();
let u0 = hash_to_field(u0_bytes);
let u1 = hash_to_field(u1_bytes);
// SvdW 映射得到两个曲线点
let q0 = map_to_curve_svdw(&u0);
let q1 = map_to_curve_svdw(&u1);
// 点加(BN256 G1 余因子=1,无需 clear_cofactor
G1Jacobian::add(&G1Jacobian::from_affine(&q0), &G1Jacobian::from_affine(&q1))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sm9::fields::fp::fp_to_bytes;
#[test]
fn test_expand_message_xmd_length() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let bytes = expand_message_xmd(b"hello", dst, 96);
assert_eq!(bytes.len(), 96);
}
#[test]
fn test_expand_message_xmd_deterministic() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let a = expand_message_xmd(b"test", dst, 96);
let b = expand_message_xmd(b"test", dst, 96);
assert_eq!(a, b, "相同输入应产生相同输出");
}
#[test]
fn test_expand_message_xmd_different_msgs() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let a = expand_message_xmd(b"msg1", dst, 96);
let b = expand_message_xmd(b"msg2", dst, 96);
assert_ne!(a, b, "不同消息应产生不同输出");
}
#[test]
fn test_map_to_curve_output_on_curve() {
let u = Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000007",
));
let p = map_to_curve_svdw(&u);
// 验证 p 在曲线 y² = x³ + 5 上
let lhs = fp_square(&p.y);
let rhs = fp_add(
&fp_mul(&fp_square(&p.x), &p.x),
&Fp::new(&U256::from_be_hex(
"0000000000000000000000000000000000000000000000000000000000000005",
)),
);
assert_eq!(lhs, rhs, "映射的点应在曲线上");
}
#[test]
fn test_hash_to_g1_deterministic() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let p1 = hash_to_g1(b"hello", dst);
let p2 = hash_to_g1(b"hello", dst);
let a1 = p1.to_affine().unwrap();
let a2 = p2.to_affine().unwrap();
assert_eq!(fp_to_bytes(&a1.x), fp_to_bytes(&a2.x));
}
#[test]
fn test_hash_to_g1_different_msgs() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let p1 = hash_to_g1(b"msg1", dst).to_affine().unwrap();
let p2 = hash_to_g1(b"msg2", dst).to_affine().unwrap();
assert_ne!(
fp_to_bytes(&p1.x),
fp_to_bytes(&p2.x),
"不同消息应映射到不同点"
);
}
#[test]
fn test_hash_to_g1_output_on_curve() {
let dst = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
let p = hash_to_g1(b"test message", dst);
let a = p.to_affine().unwrap();
assert!(a.is_on_curve(), "hash_to_g1 的输出应在 G1 曲线上");
}
}
+366
View File
@@ -0,0 +1,366 @@
//! BLS 签名方案(基于 SM9 BN256 配对)
//!
//! 实现 draft-irtf-cfrg-bls-signature-06 的 minimal-signature-size 变体:
//! - 公钥在 G2(128 字节),签名在 G1(65 字节)
//! - 确定性签名(无随机数 nonce)
//! - 支持签名聚合和门限签名
//!
//! # 安全说明
//! BN256 曲线的实际安全级别约为 100 位(而非设计的 128 位),
//! 参见 <https://eprint.iacr.org/2016/1102.pdf>。
//! 在标准要求(如 SM9 GB/T 38635)的场景下可使用;
//! 对于更高安全要求建议迁移到 BLS12-381。
pub mod hash_to_curve;
pub mod threshold;
use crypto_bigint::{Zero, U256};
use rand_core::RngCore;
use subtle::ConstantTimeEq;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::error::Error;
use crate::sm9::fields::fp::GROUP_ORDER;
use crate::sm9::fields::fp::{fn_from_bytes, Fn};
use crate::sm9::fields::fp12::{fp12_mul, fp12_to_bytes};
use crate::sm9::groups::g1::{G1Affine, G1Jacobian};
use crate::sm9::groups::g2::{G2Affine, G2Jacobian};
use crate::sm9::pairing::pairing;
use hash_to_curve::hash_to_g1;
// ── DST(域分离标签)─────────────────────────────────────────────────────────
/// 签名用 DST
pub const DST_SIGN: &[u8] = b"BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_";
/// Proof-of-Possession 用 DST(与签名 DST 不同,防止跨用途哈希碰撞)
pub const DST_POP: &[u8] = b"BLS_POP_SM9G1_XMD:SM3_SVDW_RO_POP_";
// ── 密钥类型 ──────────────────────────────────────────────────────────────────
/// BLS 私钥(标量,自动清零)
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct BlsPrivKey {
scalar: [u8; 32],
}
/// BLS 公钥(G2 点)
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BlsPubKey {
/// G2 上的点(压缩格式:4||x_re||x_im||y_re||y_im128 字节)
point: G2Affine,
}
/// BLS 签名(G1 点)
#[derive(Clone, Copy, Debug)]
pub struct BlsSignature {
point: G1Affine,
}
/// BLS 密钥份额(用于门限签名)
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct BlsKeyShare {
/// 参与者索引(1-indexed
pub index: usize,
/// 私钥份额(标量)
scalar: [u8; 32],
}
impl BlsKeyShare {
/// 获取此份额的公钥
pub fn pub_key(&self) -> BlsPubKey {
let sk = fn_from_bytes(&self.scalar);
let sk_u256 = sk.retrieve();
let p2 = G2Jacobian::from_affine(&G2Affine::generator());
let pk_jac = G2Jacobian::scalar_mul(&sk_u256, &p2);
BlsPubKey {
point: pk_jac
.to_affine()
.expect("BlsKeyShare: 密钥份额不应产生无穷远点"),
}
}
}
// ── 密钥生成 ──────────────────────────────────────────────────────────────────
/// 生成 BLS 密钥对
///
/// # 参数
/// - `rng`:随机数生成器
///
/// # 返回
/// `(私钥, 公钥)` 对
pub fn bls_keygen<R: RngCore>(rng: &mut R) -> (BlsPrivKey, BlsPubKey) {
loop {
let mut scalar = [0u8; 32];
rng.fill_bytes(&mut scalar);
// 确保标量 < 群阶 n 且非零
let s = U256::from_be_slice(&scalar);
if s.is_zero().into() || s >= GROUP_ORDER {
continue;
}
let sk = BlsPrivKey { scalar };
let pk = bls_public_key(&sk);
return (sk, pk);
}
}
/// 从私钥派生公钥
pub fn bls_public_key(sk: &BlsPrivKey) -> BlsPubKey {
let s = fn_from_bytes(&sk.scalar);
let s_u256 = s.retrieve();
// pk = sk * P2G2 基点)
let p2 = G2Jacobian::from_affine(&G2Affine::generator());
let pk_jac = G2Jacobian::scalar_mul(&s_u256, &p2);
BlsPubKey {
point: pk_jac
.to_affine()
.expect("bls_public_key: 私钥不应产生无穷远公钥"),
}
}
// ── 签名与验签 ────────────────────────────────────────────────────────────────
/// BLS 签名
///
/// sigma = sk * H(msg),其中 H 是 hash_to_g1。
/// 签名是确定性的(不需要随机数)。
///
/// # 错误
/// - `Error::ZeroScalar`:私钥为零
pub fn bls_sign(sk: &BlsPrivKey, msg: &[u8]) -> Result<BlsSignature, Error> {
let s = fn_from_bytes(&sk.scalar);
if s == Fn::ZERO {
return Err(Error::ZeroScalar);
}
// Q = H(msg)hash-to-G1
let q_jac = hash_to_g1(msg, DST_SIGN);
// sigma = sk * Q
let sigma_jac = G1Jacobian::scalar_mul(&s.retrieve(), &q_jac);
let sigma = sigma_jac.to_affine().map_err(|_| Error::ZeroScalar)?;
Ok(BlsSignature { point: sigma })
}
/// BLS 验签
///
/// 验证 e(sigma, P2) == e(H(msg), pk)。
///
/// # 错误
/// - `Error::VerifyFailed`:签名无效
pub fn bls_verify(pk: &BlsPubKey, msg: &[u8], sig: &BlsSignature) -> Result<(), Error> {
// Q = H(msg)
let q_jac = hash_to_g1(msg, DST_SIGN);
let q = q_jac.to_affine().map_err(|_| Error::InvalidSignature)?;
// lhs = e(sigma, P2)
let p2 = G2Affine::generator();
let lhs = pairing(&sig.point, &p2);
// rhs = e(Q, pk)
let rhs = pairing(&q, &pk.point);
// 常量时间比较 GT 元素
// Reason: 直接比较 Fp12 可能泄露时间信息,使用字节级常量时间比较
let lhs_bytes = fp12_to_bytes(&lhs);
let rhs_bytes = fp12_to_bytes(&rhs);
if bool::from(lhs_bytes.ct_eq(&rhs_bytes)) {
Ok(())
} else {
Err(Error::VerifyFailed)
}
}
// ── 签名聚合 ──────────────────────────────────────────────────────────────────
/// 聚合多个 BLS 签名(G1 点加法)
///
/// # 错误
/// - `Error::InvalidInput`:签名列表为空
pub fn bls_aggregate(sigs: &[BlsSignature]) -> Result<BlsSignature, Error> {
if sigs.is_empty() {
return Err(Error::InvalidInput);
}
let mut agg = G1Jacobian::from_affine(&sigs[0].point);
for sig in &sigs[1..] {
agg = G1Jacobian::add(&agg, &G1Jacobian::from_affine(&sig.point));
}
let point = agg.to_affine().map_err(|_| Error::InvalidInput)?;
Ok(BlsSignature { point })
}
/// 聚合验签(不同消息)
///
/// 验证 e(agg_sig, P2) == ∏ e(H(msg_i), pk_i)。
///
/// # 注意
/// 每个 (pk_i, msg_i) 对的消息不同时适用。
/// 若消息相同,使用 `bls_fast_aggregate_verify`。
///
/// # 错误
/// - `Error::InvalidInput`:公钥/消息列表为空或长度不匹配
/// - `Error::VerifyFailed`:验证失败
pub fn bls_aggregate_verify(
pks: &[BlsPubKey],
msgs: &[&[u8]],
agg_sig: &BlsSignature,
) -> Result<(), Error> {
if pks.is_empty() || pks.len() != msgs.len() {
return Err(Error::InvalidInput);
}
// lhs = e(agg_sig, P2)
let p2 = G2Affine::generator();
let lhs = pairing(&agg_sig.point, &p2);
// rhs = ∏ e(H(msg_i), pk_i)
let q0 = hash_to_g1(msgs[0], DST_SIGN)
.to_affine()
.map_err(|_| Error::InvalidInput)?;
let mut rhs = pairing(&q0, &pks[0].point);
for (pk, msg) in pks[1..].iter().zip(msgs[1..].iter()) {
let q = hash_to_g1(msg, DST_SIGN)
.to_affine()
.map_err(|_| Error::InvalidInput)?;
let e_i = pairing(&q, &pk.point);
rhs = fp12_mul(&rhs, &e_i);
}
let lhs_bytes = fp12_to_bytes(&lhs);
let rhs_bytes = fp12_to_bytes(&rhs);
if bool::from(lhs_bytes.ct_eq(&rhs_bytes)) {
Ok(())
} else {
Err(Error::VerifyFailed)
}
}
/// 快速聚合验签(相同消息)
///
/// 验证 e(agg_sig, P2) == e(H(msg), agg_pk),其中 agg_pk = Σ pk_i。
///
/// # 错误
/// - `Error::InvalidInput`:公钥列表为空
/// - `Error::VerifyFailed`:验证失败
pub fn bls_fast_aggregate_verify(
pks: &[BlsPubKey],
msg: &[u8],
agg_sig: &BlsSignature,
) -> Result<(), Error> {
if pks.is_empty() {
return Err(Error::InvalidInput);
}
// agg_pk = Σ pk_iG2 点加法)
let mut agg_pk = G2Jacobian::from_affine(&pks[0].point);
for pk in &pks[1..] {
agg_pk = G2Jacobian::add_jac(&agg_pk, &G2Jacobian::from_affine(&pk.point));
}
let agg_pk_affine = agg_pk.to_affine().map_err(|_| Error::InvalidInput)?;
let agg_pk_pub = BlsPubKey {
point: agg_pk_affine,
};
bls_verify(&agg_pk_pub, msg, agg_sig)
}
// ── 序列化 ────────────────────────────────────────────────────────────────────
impl BlsSignature {
/// 序列化为 65 字节(未压缩 G1 点:0x04 || x || y
pub fn to_bytes(&self) -> [u8; 65] {
self.point.to_bytes()
}
/// 从 65 字节反序列化
pub fn from_bytes(bytes: &[u8; 65]) -> Result<Self, Error> {
let point = G1Affine::from_bytes(bytes)?;
Ok(BlsSignature { point })
}
}
impl BlsPubKey {
/// 序列化为 128 字节(G2 点:x_re || x_im || y_re || y_im
pub fn to_bytes(&self) -> [u8; 128] {
self.point.to_bytes()
}
/// 从 128 字节反序列化
pub fn from_bytes(bytes: &[u8; 128]) -> Result<Self, Error> {
let point = G2Affine::from_bytes(bytes)?;
Ok(BlsPubKey { point })
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand_core::OsRng;
#[test]
fn test_bls_sign_verify_roundtrip() {
let mut rng = OsRng;
let (sk, pk) = bls_keygen(&mut rng);
let msg = b"hello bls";
let sig = bls_sign(&sk, msg).expect("签名应成功");
bls_verify(&pk, msg, &sig).expect("验签应成功");
}
#[test]
fn test_bls_verify_wrong_msg_fails() {
let mut rng = OsRng;
let (sk, pk) = bls_keygen(&mut rng);
let sig = bls_sign(&sk, b"msg1").expect("签名应成功");
assert!(
bls_verify(&pk, b"msg2", &sig).is_err(),
"错误消息应验签失败"
);
}
#[test]
fn test_bls_verify_wrong_key_fails() {
let mut rng = OsRng;
let (sk1, _pk1) = bls_keygen(&mut rng);
let (_sk2, pk2) = bls_keygen(&mut rng);
let msg = b"hello";
let sig = bls_sign(&sk1, msg).expect("签名应成功");
assert!(bls_verify(&pk2, msg, &sig).is_err(), "错误公钥应验签失败");
}
#[test]
fn test_bls_aggregate_verify() {
let mut rng = OsRng;
let (sk1, pk1) = bls_keygen(&mut rng);
let (sk2, pk2) = bls_keygen(&mut rng);
let msg1 = b"message1";
let msg2 = b"message2";
let sig1 = bls_sign(&sk1, msg1).expect("签名1应成功");
let sig2 = bls_sign(&sk2, msg2).expect("签名2应成功");
let agg = bls_aggregate(&[sig1, sig2]).expect("聚合应成功");
bls_aggregate_verify(&[pk1, pk2], &[msg1.as_ref(), msg2.as_ref()], &agg)
.expect("聚合验签应成功");
}
#[test]
fn test_bls_fast_aggregate_verify() {
let mut rng = OsRng;
let (sk1, pk1) = bls_keygen(&mut rng);
let (sk2, pk2) = bls_keygen(&mut rng);
let msg = b"shared message";
let sig1 = bls_sign(&sk1, msg).expect("签名1应成功");
let sig2 = bls_sign(&sk2, msg).expect("签名2应成功");
let agg = bls_aggregate(&[sig1, sig2]).expect("聚合应成功");
bls_fast_aggregate_verify(&[pk1, pk2], msg, &agg).expect("快速聚合验签应成功");
}
#[test]
fn test_bls_signature_serialization() {
let mut rng = OsRng;
let (sk, _pk) = bls_keygen(&mut rng);
let sig = bls_sign(&sk, b"test").expect("签名应成功");
let bytes = sig.to_bytes();
let sig2 = BlsSignature::from_bytes(&bytes).expect("反序列化应成功");
assert_eq!(sig.to_bytes(), sig2.to_bytes());
}
}
+252
View File
@@ -0,0 +1,252 @@
//! BLS 门限签名(Shamir 秘密分享 + Lagrange 插值)
//!
//! 实现 (t+1, n) 门限 BLS 签名:
//! - 可信分发者将私钥分割为 n 份,任意 t+1 份可重建签名
//! - 各参与者独立计算部分签名
//! - 聚合器组合 t+1 份部分签名得到完整 BLS 签名
//!
//! # 安全注意
//! 本实现采用 Trusted Dealer 模型:分发者可以知晓完整私钥。
//! 对于无可信第三方的场景,需使用 DKG(分布式密钥生成),超出当前范围。
extern crate alloc;
use alloc::vec::Vec;
use crypto_bigint::{Zero, U256};
use rand_core::RngCore;
use crate::error::Error;
use crate::sm9::fields::fp::{fn_from_bytes, fn_inv, fn_mul, fn_to_bytes, Fn, GROUP_ORDER};
use crate::sm9::groups::g1::G1Jacobian;
use super::{bls_sign, BlsKeyShare, BlsPrivKey, BlsSignature};
// ── Shamir 密钥分割 ──────────────────────────────────────────────────────────
/// 将 BLS 私钥分割为 n 份,需要 threshold+1 份才能组合签名
///
/// # 参数
/// - `sk`:主私钥
/// - `threshold`:门限值 t(需要 t+1 份参与者)
/// - `total`:总份额数 nn >= t+1
/// - `rng`:随机数生成器
///
/// # 返回
/// `(主公钥, Vec<BlsKeyShare>)` — n 份密钥份额
///
/// # 错误
/// - `Error::InvalidInput`:参数不合法(total < threshold+1,或 threshold=0
pub fn bls_threshold_keygen<R: RngCore>(
sk: &BlsPrivKey,
threshold: usize,
total: usize,
rng: &mut R,
) -> Result<Vec<BlsKeyShare>, Error> {
if total < threshold + 1 || threshold == 0 {
return Err(Error::InvalidInput);
}
// 构造 threshold 次随机多项式 f(x) = sk + a1*x + ... + at*x^t mod n
// f(0) = sk
let sk_fn = fn_from_bytes(&sk.scalar);
// 随机系数 a1..at
let mut coeffs: Vec<Fn> = Vec::with_capacity(threshold + 1);
coeffs.push(sk_fn);
for _ in 0..threshold {
let mut bytes = [0u8; 32];
loop {
rng.fill_bytes(&mut bytes);
let v = U256::from_be_slice(&bytes);
if !bool::from(v.is_zero()) && v < GROUP_ORDER {
coeffs.push(fn_from_bytes(&bytes));
break;
}
}
}
// 为每个参与者 i=1..=n 计算 f(i)
let mut shares = Vec::with_capacity(total);
for i in 1..=total {
let i_fn = fn_from_bytes(&{
let mut b = [0u8; 32];
let i_u256 = U256::from(i as u64);
b.copy_from_slice(&i_u256.to_be_bytes());
b
});
// Horner 方法计算 f(i) = a0 + i*(a1 + i*(a2 + ... + i*at)...)
let mut val = coeffs[threshold];
for j in (0..threshold).rev() {
val = fn_mul(&val, &i_fn);
val = crate::sm9::fields::fp::fn_add(&val, &coeffs[j]);
}
shares.push(BlsKeyShare {
index: i,
scalar: fn_to_bytes(&val),
});
}
// 清零多项式系数(防止内存残留)
coeffs.fill(Fn::ZERO);
Ok(shares)
}
// ── Lagrange 插值系数 ──────────────────────────────────────────────────────
/// 计算 Lagrange 系数 λᵢ(在 Fn 域上)
///
/// λᵢ = ∏_{j ∈ S, j≠i} (j / (j-i)) mod n
///
/// # 参数
/// - `i`:当前参与者索引(1-indexed
/// - `participants`:参与的所有参与者索引集合
fn lagrange_coefficient(i: usize, participants: &[usize]) -> Fn {
let _i_fn = index_to_fn(i);
let mut num = Fn::ONE; // 分子 ∏ j
let mut den = Fn::ONE; // 分母 ∏ (j-i)
for &j in participants {
if j != i {
let j_fn = index_to_fn(j);
num = fn_mul(&num, &j_fn);
// diff = j - i(在 Fn 域上,若 j < i 则结果自动 mod n 为负数)
let diff = if j > i {
index_to_fn(j - i)
} else {
// i > jdiff = -(i-j)
let pos = index_to_fn(i - j);
crate::sm9::fields::fp::fn_neg(&pos)
};
den = fn_mul(&den, &diff);
}
}
// λᵢ = num / den = num * den^{-1}
let den_inv = fn_inv(&den).expect("Lagrange: 分母不应为零(参与者索引应互不相同)");
fn_mul(&num, &den_inv)
}
/// 将 usize 索引转换为 Fn 元素
fn index_to_fn(i: usize) -> Fn {
let mut b = [0u8; 32];
let u = U256::from(i as u64);
b.copy_from_slice(&u.to_be_bytes());
fn_from_bytes(&b)
}
// ── 部分签名与组合 ─────────────────────────────────────────────────────────
/// 计算部分签名(与普通 BLS 签名相同,使用份额私钥)
///
/// sigma_i = sk_i * H(msg)
pub fn bls_partial_sign(share: &BlsKeyShare, msg: &[u8]) -> Result<BlsSignature, Error> {
// 将份额包装为 BlsPrivKey 格式
let sk = BlsPrivKey {
scalar: share.scalar,
};
bls_sign(&sk, msg)
}
/// 组合 t+1 份部分签名得到完整 BLS 签名(Lagrange 插值)
///
/// sigma = Σ_{i ∈ S} λᵢ · sigma_i
///
/// # 参数
/// - `partial_sigs``(参与者索引, 部分签名)` 的列表,长度需 >= threshold+1
///
/// # 错误
/// - `Error::InvalidInput`:签名列表为空
/// - `Error::PointAtInfinity`:组合结果为无穷远点
pub fn bls_combine_signatures(
partial_sigs: &[(usize, BlsSignature)],
) -> Result<BlsSignature, Error> {
if partial_sigs.is_empty() {
return Err(Error::InvalidInput);
}
let participants: Vec<usize> = partial_sigs.iter().map(|(i, _)| *i).collect();
// sigma = Σ λᵢ * sigma_i
let mut result = G1Jacobian::INFINITY;
for (i, sig) in partial_sigs {
let lambda = lagrange_coefficient(*i, &participants);
let lambda_u256 = lambda.retrieve();
// λᵢ * sigma_iG1 标量乘)
let sig_jac = G1Jacobian::from_affine(&sig.point);
let scaled = G1Jacobian::scalar_mul(&lambda_u256, &sig_jac);
result = G1Jacobian::add(&result, &scaled);
}
let point = result.to_affine().map_err(|_| Error::PointAtInfinity)?;
Ok(BlsSignature { point })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bls::{bls_keygen, bls_verify};
use rand_core::OsRng;
#[test]
fn test_threshold_2_of_3() {
let mut rng = OsRng;
// 生成主密钥
let (sk, pk) = bls_keygen(&mut rng);
let msg = b"threshold test message";
// 分割为 3 份,门限 2(需 3 份 = threshold+1=3
// 实际是 (threshold=2, total=3),需要 3 份
let shares = bls_threshold_keygen(&sk, 2, 3, &mut rng).expect("密钥分割应成功");
assert_eq!(shares.len(), 3);
// 使用全部 3 份计算部分签名(满足门限 t+1=3)
let partial_sigs: Vec<(usize, BlsSignature)> = shares
.iter()
.map(|s| {
let sig = bls_partial_sign(s, msg).expect("部分签名应成功");
(s.index, sig)
})
.collect();
// 组合签名
let combined = bls_combine_signatures(&partial_sigs).expect("签名组合应成功");
// 用主公钥验证
bls_verify(&pk, msg, &combined).expect("门限签名验证应成功");
}
#[test]
fn test_threshold_1_of_2() {
let mut rng = OsRng;
// threshold=1, total=2:需要 2 份
let (sk, pk) = bls_keygen(&mut rng);
let msg = b"simple threshold";
let shares = bls_threshold_keygen(&sk, 1, 2, &mut rng).expect("密钥分割应成功");
// 使用 2 份(threshold+1=2
let partial_sigs: Vec<(usize, BlsSignature)> = shares
.iter()
.map(|s| (s.index, bls_partial_sign(s, msg).unwrap()))
.collect();
let combined = bls_combine_signatures(&partial_sigs).unwrap();
bls_verify(&pk, msg, &combined).expect("(1,2) 门限签名验证应成功");
}
#[test]
fn test_invalid_threshold_params() {
let mut rng = OsRng;
let (sk, _pk) = bls_keygen(&mut rng);
// total < threshold+1
assert!(bls_threshold_keygen(&sk, 3, 2, &mut rng).is_err());
// threshold = 0
assert!(bls_threshold_keygen(&sk, 0, 3, &mut rng).is_err());
}
}
+3
View File
@@ -23,6 +23,8 @@ pub enum Error {
PointAtInfinity,
/// 输入数据长度不合法
InvalidInputLength,
/// 密钥交换失败(共享点为无穷远或 KDF 输出全零)
KeyExchangeFailed,
// ── SM4 错误 ────────────────────────────────────────────────────────────
/// AEAD 认证标签验证失败(GCM/CCM 解密时)
@@ -55,6 +57,7 @@ impl fmt::Display for Error {
Error::DecryptFailed => write!(f, "decryption failed"),
Error::PointAtInfinity => write!(f, "point at infinity"),
Error::InvalidInputLength => write!(f, "invalid input length"),
Error::KeyExchangeFailed => write!(f, "key exchange failed"),
Error::AuthTagMismatch => write!(f, "authentication tag mismatch"),
Error::NotOnCurve => write!(f, "point not on curve"),
Error::ZeroScalar => write!(f, "zero scalar"),
+211
View File
@@ -0,0 +1,211 @@
//! FNRFlexible Naor-Reingold)核心算法
//!
//! 实现基于 SM4 的保留格式加密:
//! 使用 7 轮 Luby-Rackoff Feistel 结构,支持任意位长(1~128 位)的明文。
//!
//! 参考:Sashank Dara, Scott Fluhrer. "FNR: Flexible Naor and Reingold", Cisco, 2014.
use crate::sm4::Sm4Key;
/// Feistel 轮数(FNR 固定 7 轮)
const N_ROUND: usize = 7;
// ── 位操作工具 ────────────────────────────────────────────────────────────────
/// 从 n 位数据(高位优先打包到字节)中提取第 i 位(i 从 0 开始,0 是最高位)
#[inline]
fn get_bit(data: &[u8; 16], i: usize) -> u8 {
let byte = i / 8;
let bit = 7 - (i % 8);
(data[byte] >> bit) & 1
}
/// 设置第 i 位为 val0 或 1)
#[inline]
fn set_bit(data: &mut [u8; 16], i: usize, val: u8) {
let byte = i / 8;
let bit = 7 - (i % 8);
data[byte] = (data[byte] & !(1 << bit)) | (val << bit);
}
/// 将 data 的前 n 位清零(其余位保持不变)
pub(super) fn clear_high_bits(data: &mut [u8; 16], n: usize) {
// 清零 n 位之后的所有位
for i in n..128 {
set_bit(data, i, 0);
}
}
/// 两个 n 位向量 XOR
fn xor_bits(a: &[u8; 16], b: &[u8; 16], n: usize) -> [u8; 16] {
let mut out = [0u8; 16];
let full = n / 8;
for i in 0..full {
out[i] = a[i] ^ b[i];
}
if n % 8 != 0 {
let mask = 0xFF_u8 << (8 - n % 8);
out[full] = (a[full] ^ b[full]) & mask;
}
out
}
// ── Feistel 轮函数 ────────────────────────────────────────────────────────────
/// Feistel 轮函数 F(round, tweak, right_half) → left_half_size 位
///
/// 构造 16 字节块 = tweak[15] XOR (round XOR right_half_padded)
/// 用 SM4 加密,取前 out_bits 位。
fn round_fn(
key: &Sm4Key,
tweak: &[u8; 15],
half: &[u8; 16],
half_bits: usize,
out_bits: usize,
round: usize,
) -> [u8; 16] {
// 构造 16 字节输入:tweak15字节)|| round1字节)
let mut block = [0u8; 16];
block[..15].copy_from_slice(tweak);
block[15] = round as u8;
// XOR half 到 blockhalf 最多 half_bits 位有意义)
let half_bytes = half_bits.div_ceil(8);
for i in 0..half_bytes.min(16) {
block[i] ^= half[i];
}
key.encrypt_block(&mut block);
// 只保留前 out_bits 位
clear_high_bits(&mut block, out_bits);
block
}
// ── FNR 加密/解密 ─────────────────────────────────────────────────────────────
/// FNR 加密(n 位,7 轮 Feistel
///
/// Feistel 结构(每轮):
/// (L, R) → (R, L XOR F(R))
///
/// 对于非整除 2 的位数:
/// left_bits = n / 2right_bits = n - left_bitsright >= left
///
/// # 参数
/// - `key`SM4 轮密钥
/// - `tweak`15 字节扩展 tweak(由 expand_tweak 生成)
/// - `data`16 字节缓冲,前 num_bits 位为明文,加密后前 num_bits 位为密文
/// - `num_bits`:有效位数(1~128
pub fn fnr_encrypt(key: &Sm4Key, tweak: &[u8; 15], data: &mut [u8; 16], num_bits: usize) {
// 特殊情况:1 位时 Feistel 退化(left_bits=0),使用随机置换
if num_bits == 1 {
fnr_1bit(key, tweak, data, true);
return;
}
let left_bits = num_bits / 2;
let right_bits = num_bits - left_bits;
// 提取左右各半
let mut l = [0u8; 16];
let mut r = [0u8; 16];
for i in 0..left_bits {
set_bit(&mut l, i, get_bit(data, i));
}
for i in 0..right_bits {
set_bit(&mut r, i, get_bit(data, left_bits + i));
}
// 7 轮 Feistel 加密
for round in 0..N_ROUND {
// F = F(round, tweak, r) 取前 left_bits 位
let f = round_fn(key, tweak, &r, right_bits, left_bits, round);
// new_r = l XOR F
let new_r = xor_bits(&l, &f, left_bits);
// 交换:l = r(原右半),r = new_r
// 注意处理 left_bits != right_bits 的情况:
// 交换后新 l 来自旧 rright_bits 位)→ 取前 left_bits 位
// 新 r 是 new_rleft_bits 位)→ 作为新右半(right_bits 位)
// 由于 right_bits >= left_bitsr 的有效位取前 left_bits 位作为新 l
// 剩余位丢弃(Feistel 交换中精度统一)
// Reason: 奇数位时 right 比 left 多 1 位,通过轮内调整保持正确性
l = r;
clear_high_bits(&mut l, left_bits); // 新 l 只取 right_bits 中的前 left_bits 位
r = new_r;
}
// 合并回 data
for i in 0..left_bits {
set_bit(data, i, get_bit(&l, i));
}
for i in 0..right_bits {
set_bit(data, left_bits + i, get_bit(&r, i));
}
}
/// FNR 解密(n 位,7 轮 Feistel 逆序)
///
/// Feistel 解密每轮:
/// (L', R') → (R' XOR F(L'), L')
/// 其中 L'=R_enc, R'=L_enc
pub fn fnr_decrypt(key: &Sm4Key, tweak: &[u8; 15], data: &mut [u8; 16], num_bits: usize) {
if num_bits == 1 {
// 1 位置换是自逆的(FPE 置换)——encrypt 等于 decrypt
fnr_1bit(key, tweak, data, false);
return;
}
let left_bits = num_bits / 2;
let right_bits = num_bits - left_bits;
let mut l = [0u8; 16];
let mut r = [0u8; 16];
for i in 0..left_bits {
set_bit(&mut l, i, get_bit(data, i));
}
for i in 0..right_bits {
set_bit(&mut r, i, get_bit(data, left_bits + i));
}
// 7 轮逆序 Feistel 解密
for round in (0..N_ROUND).rev() {
// 解密轮:(L, R) → (R XOR F(L), L)
let f = round_fn(key, tweak, &l, left_bits, right_bits, round);
let new_l = xor_bits(&r, &f, right_bits);
r = l;
clear_high_bits(&mut r, right_bits);
l = new_l;
}
for i in 0..left_bits {
set_bit(data, i, get_bit(&l, i));
}
for i in 0..right_bits {
set_bit(data, left_bits + i, get_bit(&r, i));
}
}
/// 1 位 FPE 特殊处理
///
/// 对于 1 位明文(域 = {0, 1}),FPE 只有两种可能的置换:
/// 恒等(0→0, 1→1)或翻转(0→1, 1→0)。
///
/// 用 SM4 加密 tweak 得到随机比特 b:
/// - b=0:恒等映射(密文 = 明文)
/// - b=1:翻转映射(密文 = 1 - 明文)
///
/// 由于置换是自逆的,encrypt == decrypt。
/// `_encrypt` 参数预留给将来区分加密/解密(当前实现中两者相同)。
fn fnr_1bit(key: &Sm4Key, tweak: &[u8; 15], data: &mut [u8; 16], _encrypt: bool) {
// 生成随机置换比特
let mut block = [0u8; 16];
block[..15].copy_from_slice(tweak);
block[15] = 0xFF; // 特殊轮号标记 1-bit 模式
key.encrypt_block(&mut block);
let perm_bit = (block[0] >> 7) & 1; // 取最高位
// 若 perm_bit=1,翻转最高位
let orig = get_bit(data, 0);
set_bit(data, 0, orig ^ perm_bit);
}
+293
View File
@@ -0,0 +1,293 @@
//! FPEFormat-Preserving Encryption)保留格式加密
//!
//! 基于 FNRFlexible Naor-Reingold)算法,使用 SM4 作为底层密码:
//! - 支持 1~128 位任意长度的明密文域
//! - 明密文在同一域内(位数相同)
//! - 支持 tweak(调整值)参数化加密
//!
//! # 使用示例
//!
//! ```rust
//! # #[cfg(feature = "alloc")]
//! # {
//! use libsmx::fpe::FpeKey;
//!
//! let key = [0u8; 16];
//! let fpe = FpeKey::new(&key, 32).unwrap(); // 32 位域(如 IPv4 地址)
//! let tweak = fpe.expand_tweak(b"my-tweak");
//!
//! let plaintext: u32 = 192_168_1_100; // 某 IPv4 地址
//! let mut data = plaintext.to_be_bytes();
//! let mut block = [0u8; 16];
//! block[..4].copy_from_slice(&data);
//!
//! fpe.encrypt(&tweak, &mut block);
//! fpe.decrypt(&tweak, &mut block);
//!
//! assert_eq!(&block[..4], &data);
//! # }
//! ```
mod fnr;
use crate::error::Error;
use crate::sm4::Sm4Key;
use fnr::{clear_high_bits, fnr_decrypt, fnr_encrypt};
use zeroize::ZeroizeOnDrop;
/// FPE 扩展 tweak15 字节)
///
/// 由 `FpeKey::expand_tweak` 从任意长度的 tweak 字节生成。
#[derive(Clone, Copy)]
pub struct FpeTweak([u8; 15]);
/// FPE 密钥(SM4 密钥 + 位数配置)
///
/// 使用 ZeroizeOnDrop 确保密钥在 Drop 时自动清零。
#[derive(ZeroizeOnDrop)]
pub struct FpeKey {
/// 底层 SM4 密钥
key: Sm4Key,
/// 有效位数(1~128
num_bits: usize,
}
impl FpeKey {
/// 创建 FPE 密钥
///
/// # 参数
/// - `key`16 字节 SM4 密钥
/// - `num_bits`:明密文域的位数(1~128
///
/// # 错误
/// - `Error::InvalidInputLength``num_bits` 不在 1~128 范围内
pub fn new(key: &[u8; 16], num_bits: usize) -> Result<Self, Error> {
if num_bits == 0 || num_bits > 128 {
return Err(Error::InvalidInputLength);
}
Ok(FpeKey {
key: Sm4Key::new(key),
num_bits,
})
}
/// 将任意长度的 tweak 扩展为 15 字节内部 tweak
///
/// 使用 SM4 对 tweak 进行哈希(CBC-MAC 风格)得到固定长度输出。
pub fn expand_tweak(&self, tweak: &[u8]) -> FpeTweak {
// 用 SM4 对 tweak 进行"哈希"
// 将 tweak 分块,每块 XOR 进状态后 SM4 加密
let mut state = [0u8; 16];
// 存储 num_bits 到 state 前 2 字节(域参数绑定)
state[0] = (self.num_bits >> 8) as u8;
state[1] = self.num_bits as u8;
for chunk in tweak.chunks(16) {
let mut block = state;
for (i, &b) in chunk.iter().enumerate() {
block[i] ^= b;
}
self.key.encrypt_block(&mut block);
state = block;
}
// 最终加密(确保即使 tweak 为空也有输出)
self.key.encrypt_block(&mut state);
let mut out = [0u8; 15];
out.copy_from_slice(&state[..15]);
FpeTweak(out)
}
/// 就地加密(前 num_bits 位)
///
/// `data` 的前 `num_bits` 位被加密,高于 `num_bits` 的位保持不变。
///
/// # 注意
/// `data` 的位顺序:字节 0 的最高位是位 0(高位优先)。
pub fn encrypt(&self, tweak: &FpeTweak, data: &mut [u8; 16]) {
// 保存高于 num_bits 的位(不应被修改)
let saved = save_high_bits(data, self.num_bits);
clear_high_bits(data, self.num_bits);
fnr_encrypt(&self.key, &tweak.0, data, self.num_bits);
restore_high_bits(data, &saved, self.num_bits);
}
/// 就地解密(前 num_bits 位)
pub fn decrypt(&self, tweak: &FpeTweak, data: &mut [u8; 16]) {
let saved = save_high_bits(data, self.num_bits);
clear_high_bits(data, self.num_bits);
fnr_decrypt(&self.key, &tweak.0, data, self.num_bits);
restore_high_bits(data, &saved, self.num_bits);
}
/// 返回有效位数
pub fn num_bits(&self) -> usize {
self.num_bits
}
}
/// 保存 data 中高于 n 位的位(用于还原)
fn save_high_bits(data: &[u8; 16], n: usize) -> [u8; 16] {
let mut saved = [0u8; 16];
let full_bytes = n / 8;
let rem = n % 8;
if rem != 0 && full_bytes < 16 {
// 保存 full_bytes 字节的高位部分(低 (8-rem) 位)
let mask = 0xFF_u8 >> rem;
saved[full_bytes] = data[full_bytes] & mask;
}
let start = full_bytes + if rem > 0 { 1 } else { 0 };
saved[start..16].copy_from_slice(&data[start..16]);
saved
}
/// 将保存的高位还原到 data
fn restore_high_bits(data: &mut [u8; 16], saved: &[u8; 16], n: usize) {
let full_bytes = n / 8;
let rem = n % 8;
if rem != 0 && full_bytes < 16 {
let mask = 0xFF_u8 >> rem; // 低 (8-rem) 位
data[full_bytes] = (data[full_bytes] & !mask) | (saved[full_bytes] & mask);
}
let start = full_bytes + if rem > 0 { 1 } else { 0 };
data[start..16].copy_from_slice(&saved[start..16]);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fpe_new_valid() {
assert!(FpeKey::new(&[0u8; 16], 1).is_ok());
assert!(FpeKey::new(&[0u8; 16], 32).is_ok());
assert!(FpeKey::new(&[0u8; 16], 128).is_ok());
}
#[test]
fn test_fpe_new_invalid() {
assert!(FpeKey::new(&[0u8; 16], 0).is_err());
assert!(FpeKey::new(&[0u8; 16], 129).is_err());
}
#[test]
fn test_fpe_encrypt_decrypt_roundtrip_32bits() {
let key = [0x01u8; 16];
let fpe = FpeKey::new(&key, 32).unwrap();
let tweak = fpe.expand_tweak(b"test-tweak");
// 明文:u32 = 12345678
let mut data = [0u8; 16];
data[..4].copy_from_slice(&12345678u32.to_be_bytes());
let original = data;
fpe.encrypt(&tweak, &mut data);
// 加密后应与原始不同
assert_ne!(&data[..4], &original[..4], "加密后数据应变化");
// 解密后应恢复原始
fpe.decrypt(&tweak, &mut data);
assert_eq!(&data[..4], &original[..4], "解密后应恢复原始明文");
}
#[test]
fn test_fpe_encrypt_decrypt_roundtrip_8bits() {
let key = [0xABu8; 16];
let fpe = FpeKey::new(&key, 8).unwrap();
let tweak = fpe.expand_tweak(b"tweak");
for val in 0u8..=255 {
let mut data = [0u8; 16];
data[0] = val;
let original = data;
fpe.encrypt(&tweak, &mut data);
fpe.decrypt(&tweak, &mut data);
assert_eq!(data[0], original[0], "8位加解密往返应还原 val={}", val);
}
}
#[test]
fn test_fpe_encrypt_decrypt_roundtrip_1bit() {
let key = [0x99u8; 16];
let fpe = FpeKey::new(&key, 1).unwrap();
let tweak = fpe.expand_tweak(b"");
// 测试 0 和 1
for val in [0u8, 0x80u8] {
let mut data = [0u8; 16];
data[0] = val;
let original = data;
fpe.encrypt(&tweak, &mut data);
fpe.decrypt(&tweak, &mut data);
assert_eq!(data[0] & 0x80, original[0] & 0x80, "1位加解密往返应还原");
}
}
#[test]
fn test_fpe_encrypt_decrypt_roundtrip_128bits() {
let key = [0x55u8; 16];
let fpe = FpeKey::new(&key, 128).unwrap();
let tweak = fpe.expand_tweak(b"full block");
let mut data = [0u8; 16];
for (i, d) in data.iter_mut().enumerate() {
*d = i as u8 * 17;
}
let original = data;
fpe.encrypt(&tweak, &mut data);
fpe.decrypt(&tweak, &mut data);
assert_eq!(data, original, "128位加解密往返应还原");
}
#[test]
fn test_fpe_different_tweaks_different_output() {
let key = [0x42u8; 16];
let fpe = FpeKey::new(&key, 32).unwrap();
let tweak1 = fpe.expand_tweak(b"tweak1");
let tweak2 = fpe.expand_tweak(b"tweak2");
let mut d1 = [0u8; 16];
let mut d2 = [0u8; 16];
d1[0] = 0xDE;
d1[1] = 0xAD;
d1[2] = 0xBE;
d1[3] = 0xEF;
d2[..4].copy_from_slice(&d1[..4]);
fpe.encrypt(&tweak1, &mut d1);
fpe.encrypt(&tweak2, &mut d2);
assert_ne!(&d1[..4], &d2[..4], "不同 tweak 应产生不同密文");
}
#[test]
fn test_fpe_high_bits_preserved() {
// 验证高于 num_bits 的位在加密后不变
let key = [0x11u8; 16];
let fpe = FpeKey::new(&key, 4).unwrap(); // 只用高 4 位
let tweak = fpe.expand_tweak(b"t");
let mut data = [0u8; 16];
// 高 4 位为 0b1010,低 4 位为 0b0101
data[0] = 0b1010_0101;
// 字节 1~15 也有数据
for (i, d) in data[1..].iter_mut().enumerate() {
*d = (i + 1) as u8;
}
let saved_low = data[0] & 0x0F;
let saved_rest: [u8; 15] = data[1..].try_into().unwrap();
fpe.encrypt(&tweak, &mut data);
// 低 4 位和字节 1~15 应保持不变
assert_eq!(data[0] & 0x0F, saved_low, "低4位应不变");
assert_eq!(&data[1..], &saved_rest, "字节1~15应不变");
// 解密后高 4 位应恢复
let encrypted_high = data[0] & 0xF0;
fpe.decrypt(&tweak, &mut data);
assert_eq!(data[0] & 0xF0, 0b1010_0000, "解密后高4位应恢复");
let _ = encrypted_high;
}
}
+5
View File
@@ -56,3 +56,8 @@ pub mod sm2;
pub mod sm3;
pub mod sm4;
pub mod sm9;
#[cfg(feature = "alloc")]
pub mod bls;
pub mod fpe;
+438
View File
@@ -0,0 +1,438 @@
//! SM2 签名与密钥 DER 编解码
//!
//! ## 签名格式
//! TLS 使用 ASN.1 DER 格式表示签名:
//! ```text
//! SEQUENCE {
//! INTEGER r,
//! INTEGER s
//! }
//! ```
//! 而 libsmx 内部使用原始 `r||s`(64 字节)。本模块提供两者互转。
//!
//! ## 私钥格式
//! - **SEC1**RFC 5915):`ECPrivateKey SEQUENCE { version INTEGER(1), privateKey OCTET STRING, ... }`
//! - **PKCS#8**RFC 5958):`PrivateKeyInfo SEQUENCE { version INTEGER(0), algorithm, privateKey OCTET STRING(SEC1) }`
//!
//! ## DER INTEGER 编码规则
//! - 去除前导零(但若最高位为 1,需在前补 0x00 防止被解析为负数)
//! - tag = 0x02length 占 1 字节(r/s < 256 位时长度 ≤ 33
//! - SEQUENCE tag = 0x30
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crate::error::Error;
use crate::sm2::PrivateKey;
/// 将原始签名 `r||s`64 字节)编码为 DER SEQUENCE
///
/// 输出格式:`30 <len> 02 <rlen> <r> 02 <slen> <s>`
#[cfg(feature = "alloc")]
pub fn sig_to_der(raw: &[u8; 64]) -> Vec<u8> {
let r = &raw[..32];
let s = &raw[32..];
let r_enc = encode_integer(r);
let s_enc = encode_integer(s);
let inner_len = r_enc.len() + s_enc.len();
let mut der = Vec::with_capacity(2 + inner_len);
der.push(0x30); // SEQUENCE tag
der.push(inner_len as u8); // SEQUENCE lengthinner < 256 字节)
der.extend_from_slice(&r_enc);
der.extend_from_slice(&s_enc);
der
}
/// 将 DER 编码签名解码为原始 `r||s`(64 字节)
///
/// # 错误
/// 格式不合法时返回 `Error::InvalidSignature`
pub fn sig_from_der(der: &[u8]) -> Result<[u8; 64], Error> {
let err = || Error::InvalidSignature;
// SEQUENCE tag
let (tag, rest) = split_first(der).ok_or_else(err)?;
if *tag != 0x30 {
return Err(err());
}
// SEQUENCE length
let (seq_len, rest) = split_first(rest).ok_or_else(err)?;
let seq_len = *seq_len as usize;
if rest.len() < seq_len {
return Err(err());
}
let body = &rest[..seq_len];
// 解析 r
let (r_bytes, body) = decode_integer(body).ok_or_else(err)?;
// 解析 s
let (s_bytes, body) = decode_integer(body).ok_or_else(err)?;
// 不应有多余数据
if !body.is_empty() {
return Err(err());
}
// r 和 s 都必须是正整数,不超过 32 字节
if r_bytes.is_empty() || r_bytes.len() > 33 || s_bytes.is_empty() || s_bytes.len() > 33 {
return Err(err());
}
let mut raw = [0u8; 64];
// Reason: DER INTEGER 可能有前缀 0x00(最高位保护),去除后左对齐写入 32 字节槽
let r_stripped = strip_leading_zero(r_bytes);
let s_stripped = strip_leading_zero(s_bytes);
if r_stripped.len() > 32 || s_stripped.len() > 32 {
return Err(err());
}
let r_off = 32 - r_stripped.len();
let s_off = 32 - s_stripped.len();
raw[r_off..32].copy_from_slice(r_stripped);
raw[32 + s_off..64].copy_from_slice(s_stripped);
Ok(raw)
}
// ── 内部辅助 ──────────────────────────────────────────────────────────────────
/// 将 32 字节大端整数编码为 DER INTEGER(带 tag 0x02 和 length
#[cfg(feature = "alloc")]
fn encode_integer(bytes: &[u8]) -> Vec<u8> {
// 去除前导零(至少保留 1 字节)
let start = bytes
.iter()
.position(|&b| b != 0)
.unwrap_or(bytes.len() - 1);
let val = &bytes[start..];
// 最高位为 1 时需补 0x00,防止被解析为负数
let needs_pad = val[0] & 0x80 != 0;
let val_len = val.len() + if needs_pad { 1 } else { 0 };
let mut enc = Vec::with_capacity(2 + val_len);
enc.push(0x02); // INTEGER tag
enc.push(val_len as u8); // length
if needs_pad {
enc.push(0x00);
}
enc.extend_from_slice(val);
enc
}
/// 从字节流中解析一个 DER INTEGER,返回 (value_bytes, 剩余字节)
fn decode_integer(data: &[u8]) -> Option<(&[u8], &[u8])> {
let (tag, rest) = split_first(data)?;
if *tag != 0x02 {
return None;
}
let (len, rest) = split_first(rest)?;
let len = *len as usize;
if rest.len() < len {
return None;
}
Some((&rest[..len], &rest[len..]))
}
/// 去除前导 0x00 字节
fn strip_leading_zero(bytes: &[u8]) -> &[u8] {
match bytes.iter().position(|&b| b != 0) {
Some(i) => &bytes[i..],
None => &bytes[bytes.len().saturating_sub(1)..], // 全零时保留末字节
}
}
fn split_first(data: &[u8]) -> Option<(&u8, &[u8])> {
data.split_first()
}
// ── DER 长度解码 ──────────────────────────────────────────────────────────────
/// 解析 DER 长度字段,返回 (length, 剩余字节)
///
/// 支持:单字节(< 0x80)、两字节(0x81 nn)、三字节(0x82 nn nn
fn parse_length(data: &[u8]) -> Option<(usize, &[u8])> {
let (first, rest) = data.split_first()?;
if *first < 0x80 {
// Reason: 最高位为 0 时,本字节直接表示长度
Some((*first as usize, rest))
} else if *first == 0x81 {
let (len, rest) = rest.split_first()?;
Some((*len as usize, rest))
} else if *first == 0x82 {
if rest.len() < 2 {
return None;
}
let len = (rest[0] as usize) << 8 | rest[1] as usize;
Some((len, &rest[2..]))
} else {
// 不支持更长或不定长编码
None
}
}
/// 解析一个 TLVtag-length-value),返回 (value_bytes, 剩余字节)
fn parse_tlv(data: &[u8], expected_tag: u8) -> Option<(&[u8], &[u8])> {
let (tag, rest) = data.split_first()?;
if *tag != expected_tag {
return None;
}
let (len, rest) = parse_length(rest)?;
if rest.len() < len {
return None;
}
Some((&rest[..len], &rest[len..]))
}
// ── 私钥 DER 解析 ─────────────────────────────────────────────────────────────
/// 从 SEC1 DER 解析 SM2 私钥(RFC 5915
///
/// 格式:
/// ```text
/// ECPrivateKey ::= SEQUENCE {
/// version INTEGER { ecPrivkeyVer1(1) },
/// privateKey OCTET STRING, -- 32 字节原始私钥
/// [0] ECParameters OPTIONAL,
/// [1] BIT STRING OPTIONAL
/// }
/// ```
///
/// # 错误
/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey`
pub fn private_key_from_sec1_der(der: &[u8]) -> Result<PrivateKey, Error> {
let err = || Error::InvalidPrivateKey;
// 解析外层 SEQUENCE
let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?;
// version INTEGER,值应为 1ecPrivkeyVer1
let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?;
if ver_bytes != [0x01] {
return Err(err());
}
// privateKey OCTET STRING32 字节)
let (key_bytes, _rest) = parse_tlv(rest, 0x04).ok_or_else(err)?;
if key_bytes.len() != 32 {
return Err(err());
}
let key_arr: &[u8; 32] = key_bytes.try_into().map_err(|_| err())?;
PrivateKey::from_bytes(key_arr)
}
/// 从 PKCS#8 DER 解析 SM2 私钥(RFC 5958
///
/// 格式:
/// ```text
/// PrivateKeyInfo ::= SEQUENCE {
/// version INTEGER (0),
/// algorithm AlgorithmIdentifier SEQUENCE { ... },
/// privateKey OCTET STRING (SEC1 DER)
/// }
/// ```
///
/// # 错误
/// DER 格式不合法或私钥范围不合法时返回 `Error::InvalidPrivateKey`
pub fn private_key_from_pkcs8_der(der: &[u8]) -> Result<PrivateKey, Error> {
let err = || Error::InvalidPrivateKey;
// 解析外层 SEQUENCEPrivateKeyInfo
let (seq_body, _) = parse_tlv(der, 0x30).ok_or_else(err)?;
// version INTEGER,值应为 0
let (ver_bytes, rest) = parse_tlv(seq_body, 0x02).ok_or_else(err)?;
if ver_bytes != [0x00] {
return Err(err());
}
// AlgorithmIdentifier SEQUENCE(跳过,不验证 OID
let (_, rest) = parse_tlv(rest, 0x30).ok_or_else(err)?;
// privateKey OCTET STRING(内含 SEC1 DER
let (sec1_der, _) = parse_tlv(rest, 0x04).ok_or_else(err)?;
private_key_from_sec1_der(sec1_der)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_raw(r: [u8; 32], s: [u8; 32]) -> [u8; 64] {
let mut raw = [0u8; 64];
raw[..32].copy_from_slice(&r);
raw[32..].copy_from_slice(&s);
raw
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_roundtrip_basic() {
let r = [0x01u8; 32];
let s = [0x02u8; 32];
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
let recovered = sig_from_der(&der).unwrap();
assert_eq!(recovered, raw);
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_roundtrip_high_bit_set() {
// r/s 最高位为 1,需要 DER 填充 0x00
let mut r = [0u8; 32];
r[0] = 0x80; // 最高位为 1
let mut s = [0u8; 32];
s[0] = 0xFF;
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
// 验证 DER 中有 0x00 填充
let recovered = sig_from_der(&der).unwrap();
assert_eq!(recovered, raw);
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_roundtrip_leading_zeros() {
// r 前有大量前导零
let mut r = [0u8; 32];
r[31] = 0x42; // 只有最后一字节非零
let s = [0x01u8; 32];
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
let recovered = sig_from_der(&der).unwrap();
assert_eq!(recovered, raw);
}
#[test]
fn test_der_invalid_tag() {
// 非 SEQUENCE tag
let bad = [0x10, 0x08, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00];
assert!(sig_from_der(&bad).is_err());
}
#[test]
fn test_der_truncated() {
let bad = [0x30, 0x10]; // length 声明 16 字节但无内容
assert!(sig_from_der(&bad).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_der_structure() {
// 验证 DER 字节结构符合 ASN.1 规范
let r = [0x01u8; 32];
let s = [0x01u8; 32];
let raw = make_raw(r, s);
let der = sig_to_der(&raw);
assert_eq!(der[0], 0x30); // SEQUENCE
assert_eq!(der[2], 0x02); // INTEGER tag for r
// 长度字段合理(r/s 各最多 33 字节 + 2 字节头 = 35,×2 + 2 = 72
assert!(der.len() <= 72);
assert!(der.len() >= 8);
}
// ── 私钥 DER 解析测试 ──────────────────────────────────────────────────────
// 已知 SM2 私钥原始字节(与其他测试共用)
const RAW_KEY: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3, 0x9f,
0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef, 0x4d, 0xf7,
0xc5, 0xb8,
];
/// 构造最小 SEC1 DER(只有 version + privateKey 字段)
#[cfg(feature = "alloc")]
fn make_sec1_der(key: &[u8; 32]) -> alloc::vec::Vec<u8> {
// version INTEGER = 102 01 01
// privateKey OCTET STRING04 20 <32 bytes>
// inner = 3 + 2 + 32 = 37 bytes → SEQUENCE 30 25 ...
let mut der = alloc::vec![0x30u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20];
der.extend_from_slice(key);
der
}
/// 构造最小 PKCS#8 DER(包含虚拟 AlgorithmIdentifier OID
#[cfg(feature = "alloc")]
fn make_pkcs8_der(key: &[u8; 32]) -> alloc::vec::Vec<u8> {
let sec1 = make_sec1_der(key);
// AlgorithmIdentifier 最小化:30 06 06 01 00 06 01 00(两个 OID,各 1 字节占位)
let alg_id: &[u8] = &[0x30, 0x06, 0x06, 0x01, 0x00, 0x06, 0x01, 0x00];
// version INTEGER = 002 01 00
let version: &[u8] = &[0x02, 0x01, 0x00];
// privateKey OCTET STRING 包装 sec1
let mut priv_oct = alloc::vec![0x04u8, sec1.len() as u8];
priv_oct.extend_from_slice(&sec1);
// inner = version + alg_id + priv_oct
let inner_len = version.len() + alg_id.len() + priv_oct.len();
let mut der = alloc::vec![0x30u8, inner_len as u8];
der.extend_from_slice(version);
der.extend_from_slice(alg_id);
der.extend_from_slice(&priv_oct);
der
}
#[cfg(feature = "alloc")]
#[test]
fn test_sec1_der_roundtrip() {
let der = make_sec1_der(&RAW_KEY);
let key = private_key_from_sec1_der(&der).expect("SEC1 解析应成功");
assert_eq!(key.as_bytes(), &RAW_KEY);
}
#[cfg(feature = "alloc")]
#[test]
fn test_pkcs8_der_roundtrip() {
let der = make_pkcs8_der(&RAW_KEY);
let key = private_key_from_pkcs8_der(&der).expect("PKCS#8 解析应成功");
assert_eq!(key.as_bytes(), &RAW_KEY);
}
#[test]
fn test_sec1_der_invalid_tag() {
// 首字节不是 SEQUENCE tag
let bad = [0x02u8, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20, 0x00];
assert!(private_key_from_sec1_der(&bad).is_err());
}
#[test]
fn test_sec1_der_wrong_version() {
// version 应为 1,此处给 0;最后 32 字节填充为 RAW_KEY
let mut der = [0u8; 39];
der[0] = 0x30;
der[1] = 0x25; // SEQUENCE length 37
der[2] = 0x02;
der[3] = 0x01;
der[4] = 0x00; // version = 0(错误,应为 1
der[5] = 0x04;
der[6] = 0x20; // OCTET STRING 32 字节
der[7..39].copy_from_slice(&RAW_KEY);
assert!(private_key_from_sec1_der(&der).is_err());
}
#[test]
fn test_sec1_der_key_too_short() {
// privateKey 只有 16 字节(不足 32
let der = [
0x30, 0x15, // SEQUENCE 21 字节
0x02, 0x01, 0x01, // version = 1
0x04, 0x10, // OCTET STRING 16 字节
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00,
];
assert!(private_key_from_sec1_der(&der).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_pkcs8_der_invalid_outer_tag() {
let mut der = make_pkcs8_der(&RAW_KEY);
der[0] = 0x04; // 破坏外层 SEQUENCE tag
assert!(private_key_from_pkcs8_der(&der).is_err());
}
}
+620
View File
@@ -0,0 +1,620 @@
//! SM2 密钥交换协议(GB/T 32918.3-2016
//!
//! 提供两种密钥交换方式:
//! - `ecdh`: 简单 SM2-ECDH 共享密钥计算(适配 TLS/rustls
//! - `exchange_a` / `exchange_b`: 完整 GB/T 32918.3 密钥交换协议(带确认哈希)
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use crypto_bigint::{Zero, U256};
use rand_core::RngCore;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::error::Error;
use crate::sm2::ec::{AffinePoint, JacobianPoint};
use crate::sm2::field::{fn_add, fn_mul, fp_to_bytes, Fn, GROUP_ORDER_MINUS_1};
use crate::sm2::get_z;
use crate::sm3::Sm3Hasher;
// ── x̄ 辅助函数(GB/T 32918.3 核心运算)─────────────────────────────────────────
/// 计算 x̄ = 2^w + (x & (2^w - 1)),其中 w = ⌈(⌈log2(n)⌉ / 2)⌉ - 1 = 127
///
/// 对 SM2 256 位群阶,w=127。在大端 32 字节表示中:
/// - 清除高 128 位(bytes[0..16]),保留低 128 位
/// - 设 bytes[16] 的 bit7 = 1(即加 2^127
fn x_bar(x_bytes: &[u8; 32]) -> U256 {
let mut buf = [0u8; 32];
// Reason: 保留 x 的低 128 位(bytes[16..32]),高 128 位清零
buf[16..32].copy_from_slice(&x_bytes[16..32]);
// 设 bit 127bytes[16] 的最高位)
buf[16] |= 0x80;
U256::from_be_slice(&buf)
}
// ── EphemeralKey(临时密钥对)────────────────────────────────────────────────────
/// SM2 密钥交换临时密钥对(离开作用域自动清零)
///
/// 用于密钥交换协议中的临时私钥和对应公钥。
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct EphemeralKey {
r_bytes: [u8; 32],
#[zeroize(skip)]
r_point: [u8; 65],
}
impl EphemeralKey {
/// 生成临时密钥对
pub fn generate<R: RngCore>(rng: &mut R) -> Self {
loop {
let mut r_bytes = [0u8; 32];
rng.fill_bytes(&mut r_bytes);
let r = U256::from_be_slice(&r_bytes);
if bool::from(r.is_zero()) || r >= GROUP_ORDER_MINUS_1 {
r_bytes.zeroize();
continue;
}
let r_jac = JacobianPoint::scalar_mul_g(&r);
// Reason: r 在合法范围内,scalar_mul_g 不会产生无穷远点
let r_aff = r_jac.to_affine().expect("valid r produces valid point");
return EphemeralKey {
r_bytes,
r_point: r_aff.to_bytes(),
};
}
}
/// 从指定标量创建临时密钥对(测试用)
pub fn from_scalar(r: &U256) -> Result<Self, Error> {
if bool::from(r.is_zero()) || *r >= GROUP_ORDER_MINUS_1 {
return Err(Error::InvalidPrivateKey);
}
let r_jac = JacobianPoint::scalar_mul_g(r);
let r_aff = r_jac.to_affine().map_err(|_| Error::InvalidPrivateKey)?;
Ok(EphemeralKey {
r_bytes: r.to_be_bytes(),
r_point: r_aff.to_bytes(),
})
}
/// 获取临时公钥(发送给对方)
pub fn public_key(&self) -> &[u8; 65] {
&self.r_point
}
}
// ── 简单 ECDH ──────────────────────────────────────────────────────────────────
/// 简单 SM2-ECDH 共享密钥计算
///
/// 计算 shared = my_priv · peer_pub,返回共享点的 x 坐标(32 字节)。
/// 适用于 TLS/rustls 等只需要原始 ECDH 共享密钥的场景。
///
/// # 参数
/// - `my_priv`: 己方私钥
/// - `peer_pub`: 对方公钥(65 字节,04||x||y
///
/// # 错误
/// - `InvalidPublicKey`: 公钥格式错误或不在曲线上
/// - `PointAtInfinity`: 共享点为无穷远(不应发生于合法输入)
pub fn ecdh(my_priv: &crate::sm2::PrivateKey, peer_pub: &[u8; 65]) -> Result<[u8; 32], Error> {
let peer = AffinePoint::from_bytes(peer_pub)?;
let d = U256::from_be_slice(my_priv.as_bytes());
let peer_jac = JacobianPoint::from_affine(&peer);
let shared = JacobianPoint::scalar_mul(&d, &peer_jac);
let shared_aff = shared.to_affine()?;
Ok(fp_to_bytes(&shared_aff.x))
}
/// 从变长切片执行 SM2-ECDHrustls `ActiveKeyExchange::complete` 适配)
///
/// 等同于 `ecdh`,但接受 `&[u8]` 而非 `&[u8; 65]`,省去调用方的长度转换。
///
/// # 错误
/// - `InvalidInputLength`: peer_pub 长度不等于 65
/// - `InvalidPublicKey` / `PointAtInfinity`: 同 `ecdh`
pub fn ecdh_from_slice(
my_priv: &crate::sm2::PrivateKey,
peer_pub: &[u8],
) -> Result<[u8; 32], Error> {
let pub_fixed: &[u8; 65] = peer_pub.try_into().map_err(|_| Error::InvalidInputLength)?;
ecdh(my_priv, pub_fixed)
}
// ── 完整密钥交换协议(GB/T 32918.3)──────────────────────────────────────────────
/// 密钥交换结果
#[cfg(feature = "alloc")]
pub struct ExchangeResult {
/// 协商出的共享密钥
pub key: Vec<u8>,
/// 己方确认哈希(发给对方验证)
pub s_self: [u8; 32],
/// 对方确认哈希(用于验证对方发来的值)
pub s_peer: [u8; 32],
}
/// 发起方 A 执行密钥交换
///
/// # 参数
/// - `klen`: 期望密钥长度(字节)
/// - `id_a`: 发起方用户 ID
/// - `id_b`: 响应方用户 ID
/// - `pri_key_a`: 发起方私钥
/// - `pub_key_a`: 发起方公钥(65 字节)
/// - `pub_key_b`: 响应方公钥(65 字节)
/// - `eph_key_a`: 发起方临时密钥
/// - `r_b`: 响应方临时公钥(65 字节)
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_arguments)]
pub fn exchange_a(
klen: usize,
id_a: &[u8],
id_b: &[u8],
pri_key_a: &crate::sm2::PrivateKey,
pub_key_a: &[u8; 65],
pub_key_b: &[u8; 65],
eph_key_a: &EphemeralKey,
r_b: &[u8; 65],
) -> Result<ExchangeResult, Error> {
compute_shared(
true, klen, id_a, id_b, pri_key_a, pub_key_a, pub_key_b, eph_key_a, r_b,
)
}
/// 响应方 B 执行密钥交换
///
/// # 参数
/// - `klen`: 期望密钥长度(字节)
/// - `id_a`: 发起方用户 ID
/// - `id_b`: 响应方用户 ID
/// - `pri_key_b`: 响应方私钥
/// - `pub_key_a`: 发起方公钥(65 字节)
/// - `pub_key_b`: 响应方公钥(65 字节)
/// - `eph_key_b`: 响应方临时密钥
/// - `r_a`: 发起方临时公钥(65 字节)
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_arguments)]
pub fn exchange_b(
klen: usize,
id_a: &[u8],
id_b: &[u8],
pri_key_b: &crate::sm2::PrivateKey,
pub_key_a: &[u8; 65],
pub_key_b: &[u8; 65],
eph_key_b: &EphemeralKey,
r_a: &[u8; 65],
) -> Result<ExchangeResult, Error> {
compute_shared(
false, klen, id_a, id_b, pri_key_b, pub_key_a, pub_key_b, eph_key_b, r_a,
)
}
/// 内部共享计算
///
/// `is_initiator`: true 表示发起方 Afalse 表示响应方 B
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_arguments)]
fn compute_shared(
is_initiator: bool,
klen: usize,
id_a: &[u8],
id_b: &[u8],
pri_key_self: &crate::sm2::PrivateKey,
pub_key_a: &[u8; 65],
pub_key_b: &[u8; 65],
eph_key_self: &EphemeralKey,
r_peer: &[u8; 65],
) -> Result<ExchangeResult, Error> {
// 计算 ZA、ZB
let z_a = get_z(id_a, pub_key_a);
let z_b = get_z(id_b, pub_key_b);
// 解析临时公钥坐标
let r_self_aff = AffinePoint::from_bytes(eph_key_self.public_key())?;
let r_peer_aff = AffinePoint::from_bytes(r_peer)?;
// 计算 x̄_self 和 x̄_peer
let x_self_bytes = fp_to_bytes(&r_self_aff.x);
let x_peer_bytes = fp_to_bytes(&r_peer_aff.x);
let x_bar_self = x_bar(&x_self_bytes);
let x_bar_peer = x_bar(&x_peer_bytes);
// t = (d_self + x̄_self · r_self) mod n
let d_self = U256::from_be_slice(pri_key_self.as_bytes());
let r_self = U256::from_be_slice(&eph_key_self.r_bytes);
let t_fn = fn_add(
&Fn::new(&d_self),
&fn_mul(&Fn::new(&x_bar_self), &Fn::new(&r_self)),
);
// V/U = t · (peer_pub + x̄_peer · R_peer)
// Reason: 先计算 x̄_peer · R_peer(标量乘),再加 peer_pub(仿射点)
let peer_pub_bytes = if is_initiator { pub_key_b } else { pub_key_a };
let peer_pub_aff = AffinePoint::from_bytes(peer_pub_bytes)?;
let peer_pub_jac = JacobianPoint::from_affine(&peer_pub_aff);
let r_peer_jac = JacobianPoint::from_affine(&r_peer_aff);
let x_bar_peer_r = JacobianPoint::scalar_mul(&x_bar_peer, &r_peer_jac);
let combined = JacobianPoint::add(&peer_pub_jac, &x_bar_peer_r);
let t = t_fn.retrieve();
let v_point = JacobianPoint::scalar_mul(&t, &combined);
let v_aff = v_point.to_affine().map_err(|_| Error::KeyExchangeFailed)?;
let xv = fp_to_bytes(&v_aff.x);
let yv = fp_to_bytes(&v_aff.y);
// K = KDF(xV || yV || ZA || ZB, klen)
let mut kdf_input = Vec::with_capacity(32 + 32 + 32 + 32);
kdf_input.extend_from_slice(&xv);
kdf_input.extend_from_slice(&yv);
kdf_input.extend_from_slice(&z_a);
kdf_input.extend_from_slice(&z_b);
let key = crate::sm2::kdf::kdf(&kdf_input, klen);
// KDF 输出全零时返回错误(防弱密钥)
if key.iter().all(|&b| b == 0) {
return Err(Error::KeyExchangeFailed);
}
// 确认哈希
// (x1,y1) 始终是 RA(发起方),(x2,y2) 始终是 RB(响应方)
let (x1, y1, x2, y2) = if is_initiator {
(
fp_to_bytes(&r_self_aff.x),
fp_to_bytes(&r_self_aff.y),
fp_to_bytes(&r_peer_aff.x),
fp_to_bytes(&r_peer_aff.y),
)
} else {
(
fp_to_bytes(&r_peer_aff.x),
fp_to_bytes(&r_peer_aff.y),
fp_to_bytes(&r_self_aff.x),
fp_to_bytes(&r_self_aff.y),
)
};
// 内部哈希 hash_v = SM3(xV || ZA || ZB || x1 || y1 || x2 || y2)
let mut h = Sm3Hasher::new();
h.update(&xv);
h.update(&z_a);
h.update(&z_b);
h.update(&x1);
h.update(&y1);
h.update(&x2);
h.update(&y2);
let hash_v = h.finalize();
// S1 = SM3(0x02 || yV || hash_v) — 己方若为 B,则 S1 是己方确认值
let s1 = {
let mut h = Sm3Hasher::new();
h.update(&[0x02]);
h.update(&yv);
h.update(&hash_v);
h.finalize()
};
// SA = SM3(0x03 || yV || hash_v)
let sa = {
let mut h = Sm3Hasher::new();
h.update(&[0x03]);
h.update(&yv);
h.update(&hash_v);
h.finalize()
};
// Reason: 发起方 A 的确认哈希是 SA(0x03),响应方 B 的确认哈希是 S1(0x02)
let (s_self, s_peer) = if is_initiator {
(sa, s1) // A 发送 SA 给 B 验证,A 验证 B 发来的 S1
} else {
(s1, sa) // B 发送 S1 给 A 验证,B 验证 A 发来的 SA
};
Ok(ExchangeResult {
key,
s_self,
s_peer,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sm2::PrivateKey;
#[allow(dead_code)]
struct FakeRng(#[allow(dead_code)] [u8; 32]);
impl RngCore for FakeRng {
fn next_u32(&mut self) -> u32 {
0
}
fn next_u64(&mut self) -> u64 {
0
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
for (i, b) in dest.iter_mut().enumerate() {
*b = self.0[i % 32];
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(dest);
Ok(())
}
}
#[test]
fn test_x_bar() {
// x = 1(低 128 位只有 bit 0
let mut x_bytes = [0u8; 32];
x_bytes[31] = 0x01;
let result = x_bar(&x_bytes);
// 期望 2^127 + 1
let mut expected = [0u8; 32];
expected[16] = 0x80;
expected[31] = 0x01;
assert_eq!(result, U256::from_be_slice(&expected));
}
#[test]
fn test_x_bar_high_bits_cleared() {
// x 有高 128 位数据,应被清除
let x_bytes = [0xFFu8; 32];
let result = x_bar(&x_bytes);
// 低 128 位全 1 + 2^127 设置位 = 0x80 FF...FF 后 16 字节加上 2^127
// 高 16 字节应为 0bytes[16] = 0xFF | 0x80 = 0xFF
let mut expected = [0u8; 32];
expected[16..32].copy_from_slice(&[0xFF; 16]);
expected[16] |= 0x80; // 已经是 0xFF,不变
assert_eq!(result, U256::from_be_slice(&expected));
}
#[test]
fn test_ecdh_roundtrip() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
// A 用 B 的公钥算 ECDH,B 用 A 的公钥算 ECDH,结果应一致
let shared_a = ecdh(&pri_a, &pub_b).unwrap();
let shared_b = ecdh(&pri_b, &pub_a).unwrap();
assert_eq!(shared_a, shared_b);
}
#[test]
fn test_ecdh_invalid_pubkey() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
// 无效公钥(全零 y 坐标不在曲线上)
let mut bad_pub = [0x04u8; 65];
bad_pub[1] = 0x01;
assert!(ecdh(&pri_a, &bad_pub).is_err());
}
#[test]
fn test_ecdh_from_slice_length_check() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
// 长度不对应报 InvalidInputLength
assert!(ecdh_from_slice(&pri_a, &[0x04u8; 64]).is_err());
assert!(ecdh_from_slice(&pri_a, &[0x04u8; 66]).is_err());
}
#[test]
fn test_ecdh_from_slice_equals_ecdh() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_b = pri_b.public_key();
let r1 = ecdh(&pri_a, &pub_b).unwrap();
let r2 = ecdh_from_slice(&pri_a, &pub_b).unwrap();
assert_eq!(r1, r2);
}
#[cfg(feature = "alloc")]
#[test]
fn test_exchange_roundtrip() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
let id_a = b"Alice@test.com";
let id_b = b"Bob@test.com";
// 生成临时密钥
let ra_scalar =
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
let rb_scalar =
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_a = exchange_a(
16,
id_a,
id_b,
&pri_a,
&pub_a,
&pub_b,
&eph_a,
eph_b.public_key(),
)
.unwrap();
let result_b = exchange_b(
16,
id_a,
id_b,
&pri_b,
&pub_a,
&pub_b,
&eph_b,
eph_a.public_key(),
)
.unwrap();
// 协商出的密钥应相同
assert_eq!(result_a.key, result_b.key);
assert!(!result_a.key.is_empty());
}
#[cfg(feature = "alloc")]
#[test]
fn test_exchange_confirmation() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
let id_a = b"1234567812345678";
let id_b = b"1234567812345678";
let ra_scalar =
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
let rb_scalar =
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
let eph_a = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_a = exchange_a(
16,
id_a,
id_b,
&pri_a,
&pub_a,
&pub_b,
&eph_a,
eph_b.public_key(),
)
.unwrap();
let result_b = exchange_b(
16,
id_a,
id_b,
&pri_b,
&pub_a,
&pub_b,
&eph_b,
eph_a.public_key(),
)
.unwrap();
// 确认哈希交叉验证:A.s_peer == B.s_selfB.s_peer == A.s_self
assert_eq!(result_a.s_peer, result_b.s_self);
assert_eq!(result_b.s_peer, result_a.s_self);
}
#[cfg(feature = "alloc")]
#[test]
fn test_exchange_different_ids() {
let d_a: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let d_b: [u8; 32] = [
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
];
let pri_a = PrivateKey::from_bytes(&d_a).unwrap();
let pri_b = PrivateKey::from_bytes(&d_b).unwrap();
let pub_a = pri_a.public_key();
let pub_b = pri_b.public_key();
let ra_scalar =
U256::from_be_hex("83A2C9C8B96E5AF70BD480B472409A9A327257F1EBB73F5B073354B248668563");
let rb_scalar =
U256::from_be_hex("33FE21940342161C55619C4A0C060293D543C80AF19748CE176D83477DE71C80");
// 使用不同 ID 组合
let eph_a1 = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b1 = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_1 = exchange_a(
16,
b"ID_A_1",
b"ID_B_1",
&pri_a,
&pub_a,
&pub_b,
&eph_a1,
eph_b1.public_key(),
)
.unwrap();
let eph_a2 = EphemeralKey::from_scalar(&ra_scalar).unwrap();
let eph_b2 = EphemeralKey::from_scalar(&rb_scalar).unwrap();
let result_2 = exchange_a(
16,
b"ID_A_2",
b"ID_B_2",
&pri_a,
&pub_a,
&pub_b,
&eph_a2,
eph_b2.public_key(),
)
.unwrap();
// 不同 ID 应产生不同密钥
assert_ne!(result_1.key, result_2.key);
}
}
+95
View File
@@ -10,9 +10,11 @@
//! 签名必须使用 `SM3(Z||M)` 作为消息摘要,而非直接 `SM3(M)`。
//! 所有公开签名接口均要求调用方提供用户 ID(或已计算好的 Z 值)。
pub mod der;
pub mod ec;
pub mod field;
pub mod kdf;
pub mod key_exchange;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
@@ -174,6 +176,56 @@ pub fn sign_with_k(e: &[u8; 32], pri_key: &PrivateKey, k: &U256) -> Result<[u8;
Ok(sig)
}
/// SM2 签名(标准接口,随机 k)
///
/// # 合规说明
/// 此函数接受预计算好的消息摘要 `e = SM3(Z||M)`。
/// 调用方应先用 `get_z` + `get_e` 计算 e,确保满足 GB/T 32918.2-2016 §5.5。
/// SM2 签名(便捷接口,自动计算 Z 值与消息摘要)
///
/// 等同于 `get_z` + `get_e` + `sign` 的组合,适合不需要手动管理摘要的场景。
///
/// # 参数
/// - `msg`: 原始消息
/// - `id`: 用户可辨别标识(通常使用 `b"1234567812345678"`
/// - `pri_key`: 私钥
/// - `rng`: 随机数生成器
///
/// # 合规说明
/// 内部自动计算 `Z = SM3(ENTL||ID||a||b||Gx||Gy||Px||Py)` 和 `e = SM3(Z||M)`
/// 符合 GB/T 32918.2-2016 §5.5。
pub fn sign_message<R: RngCore>(
msg: &[u8],
id: &[u8],
pri_key: &PrivateKey,
rng: &mut R,
) -> [u8; 64] {
let pub_key = pri_key.public_key();
let z = get_z(id, &pub_key);
let e = get_e(&z, msg);
sign(&e, pri_key, rng)
}
/// SM2 验签(便捷接口,自动计算 Z 值与消息摘要)
///
/// 等同于 `get_z` + `get_e` + `verify` 的组合。
///
/// # 参数
/// - `msg`: 原始消息
/// - `id`: 用户可辨别标识
/// - `pub_key`: 公钥(65 字节,04||x||y
/// - `sig`: 签名(64 字节,r||s
pub fn verify_message(
msg: &[u8],
id: &[u8],
pub_key: &[u8; 65],
sig: &[u8; 64],
) -> Result<(), Error> {
let z = get_z(id, pub_key);
let e = get_e(&z, msg);
verify(&e, pub_key, sig)
}
/// SM2 签名(标准接口,随机 k)
///
/// # 合规说明
@@ -481,6 +533,49 @@ mod tests {
assert_eq!(plaintext, msg);
}
#[test]
fn test_sign_message_verify_message_roundtrip() {
let d_bytes: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_key = PrivateKey::from_bytes(&d_bytes).unwrap();
let pub_key = pri_key.public_key();
let mut rng = FakeRng([
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
]);
let msg = b"hello sign_message";
let sig = sign_message(msg, DEFAULT_ID, &pri_key, &mut rng);
verify_message(msg, DEFAULT_ID, &pub_key, &sig).expect("便捷验签应通过");
}
#[test]
fn test_verify_message_rejects_wrong_id() {
let d_bytes: [u8; 32] = [
0x39, 0x45, 0x20, 0x8f, 0x7b, 0x21, 0x44, 0xb1, 0x3f, 0x36, 0xe3, 0x8a, 0xc6, 0xd3,
0x9f, 0x95, 0x88, 0x93, 0x93, 0x69, 0x28, 0x60, 0xb5, 0x1a, 0x42, 0xfb, 0x81, 0xef,
0x4d, 0xf7, 0xc5, 0xb8,
];
let pri_key = PrivateKey::from_bytes(&d_bytes).unwrap();
let pub_key = pri_key.public_key();
let mut rng = FakeRng([
0x59, 0x27, 0x6e, 0x27, 0xd5, 0x06, 0x86, 0x1a, 0x16, 0x68, 0x0f, 0x3a, 0xd9, 0xc0,
0x2d, 0xcc, 0xef, 0x3c, 0xc1, 0xfa, 0x3c, 0xdb, 0xe4, 0xce, 0x6d, 0x54, 0xb8, 0x0d,
0xea, 0xc1, 0xbc, 0x21,
]);
let msg = b"hello sign_message";
let sig = sign_message(msg, DEFAULT_ID, &pri_key, &mut rng);
// 用错误 ID 验签应失败
assert!(verify_message(msg, b"wrong-id", &pub_key, &sig).is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_decrypt_rejects_tampered_ciphertext() {
+194
View File
@@ -0,0 +1,194 @@
//! HKDF-SM3:基于 HMAC-SM3 的密钥派生函数(RFC 5869
//!
//! TLS 1.3 的密钥调度完全基于 HKDF,因此本模块是 rustls 国密适配的基础依赖。
//!
//! ## 协议
//!
//! ```text
//! Extract: PRK = HMAC-SM3(salt, IKM)
//! Expand: T(0) = b""
//! T(i) = HMAC-SM3(PRK, T(i-1) || info || i) i = 1, 2, ...
//! OKM = T(1) || T(2) || ... 取前 len 字节
//! ```
//!
//! 参考:[RFC 5869](https://www.rfc-editor.org/rfc/rfc5869)
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use super::hmac_sm3;
use super::DIGEST_LEN;
#[cfg(feature = "alloc")]
use crate::error::Error;
/// HKDF-SM3 Extract
///
/// PRK = HMAC-SM3(salt, IKM)
///
/// # 参数
/// - `salt`:可选盐值;`None` 时视为全零 32 字节(RFC 5869 §2.2
/// - `ikm`:输入密钥材料
///
/// # 返回
/// 32 字节伪随机密钥(PRK)
pub fn hkdf_extract(salt: Option<&[u8]>, ikm: &[u8]) -> [u8; DIGEST_LEN] {
// Reason: RFC 5869 §2.2 规定 salt 缺省时视为长度为 HashLen 的零字节序列
let zeros = [0u8; DIGEST_LEN];
let salt = salt.unwrap_or(&zeros);
hmac_sm3(salt, ikm)
}
/// HKDF-SM3 Expand
///
/// OKM = T(1) || T(2) || ... 截取前 `len` 字节
///
/// # 参数
/// - `prk`32 字节伪随机密钥(来自 `hkdf_extract` 输出)
/// - `info`:上下文信息(可为空)
/// - `len`:期望输出长度(字节),不得超过 255 × 32 = 8160
///
/// # 错误
/// `len > 255 * 32` 时返回 `Error::InvalidInputLength`
#[cfg(feature = "alloc")]
pub fn hkdf_expand(prk: &[u8; DIGEST_LEN], info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
// Reason: RFC 5869 §2.3 限制最大输出为 255 × HashLen
const MAX_LEN: usize = 255 * DIGEST_LEN;
if len > MAX_LEN {
return Err(Error::InvalidInputLength);
}
let mut okm = Vec::with_capacity(len + DIGEST_LEN);
let mut t_prev = [0u8; DIGEST_LEN]; // T(0) = b""
let mut t_prev_len = 0usize; // 第一轮 T(0) 为空
let rounds = len.div_ceil(DIGEST_LEN);
for i in 1u8..=(rounds as u8) {
// HMAC-SM3(PRK, T(i-1) || info || i)
// Reason: 用拼接方式避免在 no_std 环境分配临时 Vec
let mut input = [0u8; DIGEST_LEN + 255 + 1]; // T_prev(32) + info(≤255) + counter(1)
let info_len = info.len().min(255);
input[..t_prev_len].copy_from_slice(&t_prev[..t_prev_len]);
input[t_prev_len..t_prev_len + info_len].copy_from_slice(&info[..info_len]);
input[t_prev_len + info_len] = i;
let t_i = hmac_sm3(prk, &input[..t_prev_len + info_len + 1]);
okm.extend_from_slice(&t_i);
t_prev = t_i;
t_prev_len = DIGEST_LEN; // 第二轮起 T_prev 固定 32 字节
}
okm.truncate(len);
Ok(okm)
}
/// HKDF-SM3 一步完成(extract + expand
///
/// 适合只需要派生一段密钥材料的场景。
///
/// # 参数
/// - `salt`:可选盐值(`None` 视为 32 字节零)
/// - `ikm`:输入密钥材料
/// - `info`:上下文绑定信息
/// - `len`:输出长度(字节)
#[cfg(feature = "alloc")]
pub fn hkdf(salt: Option<&[u8]>, ikm: &[u8], info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
let prk = hkdf_extract(salt, ikm);
hkdf_expand(&prk, info, len)
}
#[cfg(test)]
mod tests {
use super::*;
/// RFC 5869 附录 A.1 测试向量(以 SHA-256 为基准验结构,此处用 SM3 验确定性和正确性)
#[test]
fn test_hkdf_extract_deterministic() {
let salt = b"test-salt";
let ikm = b"input-key-material";
let prk1 = hkdf_extract(Some(salt), ikm);
let prk2 = hkdf_extract(Some(salt), ikm);
assert_eq!(prk1, prk2);
assert_eq!(prk1.len(), 32);
}
#[test]
fn test_hkdf_extract_none_salt_equals_zero_salt() {
let ikm = b"some ikm";
let zeros = [0u8; 32];
let prk_none = hkdf_extract(None, ikm);
let prk_zero = hkdf_extract(Some(&zeros), ikm);
assert_eq!(prk_none, prk_zero);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_length() {
let prk = [0x42u8; 32];
let info = b"test-info";
assert_eq!(hkdf_expand(&prk, info, 16).unwrap().len(), 16);
assert_eq!(hkdf_expand(&prk, info, 32).unwrap().len(), 32);
assert_eq!(hkdf_expand(&prk, info, 48).unwrap().len(), 48);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_deterministic() {
let prk = [0x11u8; 32];
let info = b"ctx";
let out1 = hkdf_expand(&prk, info, 32).unwrap();
let out2 = hkdf_expand(&prk, info, 32).unwrap();
assert_eq!(out1, out2);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_prefix_consistency() {
// T(1..n) 前缀应与长度更短的输出一致
let prk = [0x22u8; 32];
let info = b"prefix-test";
let short = hkdf_expand(&prk, info, 32).unwrap();
let long = hkdf_expand(&prk, info, 64).unwrap();
assert_eq!(&long[..32], &short[..]);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_max_len_rejected() {
let prk = [0u8; 32];
let result = hkdf_expand(&prk, b"", 255 * 32 + 1);
assert!(result.is_err());
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_expand_max_len_accepted() {
let prk = [0u8; 32];
let result = hkdf_expand(&prk, b"", 255 * 32);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 255 * 32);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_different_info_different_output() {
let prk = [0x33u8; 32];
let out1 = hkdf_expand(&prk, b"info-a", 32).unwrap();
let out2 = hkdf_expand(&prk, b"info-b", 32).unwrap();
assert_ne!(out1, out2);
}
#[cfg(feature = "alloc")]
#[test]
fn test_hkdf_roundtrip_salt_info() {
// 模拟 TLS 1.3 密钥调度:extract 然后 expand 两种 label
let salt = b"tls13-early-secret-salt";
let ikm = b"shared-secret-from-key-exchange";
let prk = hkdf_extract(Some(salt), ikm);
let key1 = hkdf_expand(&prk, b"tls13 key", 16).unwrap();
let key2 = hkdf_expand(&prk, b"tls13 iv", 12).unwrap();
assert_eq!(key1.len(), 16);
assert_eq!(key2.len(), 12);
assert_ne!(&key1[..12], &key2[..]);
}
}
+72 -13
View File
@@ -23,6 +23,7 @@
//! 如需 HMAC,请使用 [`hmac_sm3`]。
mod compress;
pub mod hkdf;
use compress::{compress, IV};
@@ -104,33 +105,56 @@ impl Sm3Hasher {
///
/// 调用后此 hasher 不应再使用(消耗所有权的版本请用 [`finalize`](Self::finalize))。
pub fn finalize(mut self) -> [u8; DIGEST_LEN] {
Self::finalize_inner(&mut self)
}
/// 完成哈希并重置状态(供复用,无需重新构造)
///
/// 等同于 `finalize()` 后调用 `reset()`,但只需一次操作。
/// rustls `Hasher` trait 要求此语义(`finish(&mut self)`)。
pub fn finalize_reset(&mut self) -> [u8; DIGEST_LEN] {
let out = Self::finalize_inner(self);
self.reset();
out
}
/// 重置为初始状态(等同于重新调用 `new()`,但复用已分配内存)
pub fn reset(&mut self) {
self.state = IV;
self.buffer = [0u8; 64];
self.buf_len = 0;
self.bit_len = 0;
}
/// 内部完成函数(同时供消耗版和借用版使用)
fn finalize_inner(h: &mut Self) -> [u8; DIGEST_LEN] {
// 计算总位数(包含缓冲区中的字节)
let total_bits = self.bit_len.wrapping_add((self.buf_len as u64) * 8);
let total_bits = h.bit_len.wrapping_add((h.buf_len as u64) * 8);
// Padding:追加 0x80 + 零字节,使消息长度 ≡ 56 (mod 64)
self.buffer[self.buf_len] = 0x80;
self.buf_len += 1;
h.buffer[h.buf_len] = 0x80;
h.buf_len += 1;
if self.buf_len > 56 {
if h.buf_len > 56 {
// 当前块填不下长度字段,先处理这块,再开一块
for i in self.buf_len..64 {
self.buffer[i] = 0;
for i in h.buf_len..64 {
h.buffer[i] = 0;
}
compress(&mut self.state, &self.buffer);
self.buffer = [0u8; 64];
compress(&mut h.state, &h.buffer);
h.buffer = [0u8; 64];
} else {
for i in self.buf_len..56 {
self.buffer[i] = 0;
for i in h.buf_len..56 {
h.buffer[i] = 0;
}
}
// 最后 8 字节写入总位长(大端)
self.buffer[56..64].copy_from_slice(&total_bits.to_be_bytes());
compress(&mut self.state, &self.buffer);
h.buffer[56..64].copy_from_slice(&total_bits.to_be_bytes());
compress(&mut h.state, &h.buffer);
// 输出:8 个 u32 大端序拼接
let mut out = [0u8; 32];
for (i, &v) in self.state.iter().enumerate() {
for (i, &v) in h.state.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&v.to_be_bytes());
}
out
@@ -262,6 +286,41 @@ mod tests {
assert_eq!(mac.len(), 32);
}
/// reset() 后状态恢复为 new() 初始状态
#[test]
fn test_reset_equals_new() {
let mut h = Sm3Hasher::new();
h.update(b"some data");
h.reset();
let digest_after_reset = h.finalize();
let digest_fresh = Sm3Hasher::digest(b"");
assert_eq!(digest_after_reset, digest_fresh);
}
/// finalize_reset() 返回正确摘要,且随后状态已重置
#[test]
fn test_finalize_reset_correctness() {
let mut h = Sm3Hasher::new();
h.update(b"abc");
let d1 = h.finalize_reset();
// d1 应等于 SM3("abc")
assert_eq!(d1, Sm3Hasher::digest(b"abc"));
// 重置后哈希空消息应等于 SM3("")
let d2 = h.finalize();
assert_eq!(d2, Sm3Hasher::digest(b""));
}
/// finalize_reset() 可连续使用两次,结果一致
#[test]
fn test_finalize_reset_repeatable() {
let mut h = Sm3Hasher::new();
h.update(b"test");
let d1 = h.finalize_reset();
h.update(b"test");
let d2 = h.finalize_reset();
assert_eq!(d1, d2);
}
// 辅助:从十六进制字符串构造 [u8; 32]
fn hex_literal(s: &str) -> [u8; 32] {
let mut out = [0u8; 32];
+63
View File
@@ -553,6 +553,69 @@ pub fn sm4_decrypt_ccm(
Ok(plaintext)
}
// ── GCM/CCM 合并格式(TLS 适配)────────────────────────────────────────────────
/// SM4-GCM 加密(合并输出格式:`ciphertext || tag`
///
/// TLS 记录层要求 AEAD 输出为单一缓冲区,此函数将密文和 16 字节 tag 合并返回。
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_gcm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> Vec<u8> {
let (mut ct, tag) = sm4_encrypt_gcm(key, nonce, aad, plaintext);
ct.extend_from_slice(&tag);
ct
}
/// SM4-GCM 解密(合并输入格式:`ciphertext || tag`
///
/// 输入必须至少 16 字节(tag 长度);先验证 tag 再解密。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_gcm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
if ciphertext_with_tag.len() < 16 {
return Err(crate::error::Error::InvalidInputLength);
}
let ct_len = ciphertext_with_tag.len() - 16;
let ct = &ciphertext_with_tag[..ct_len];
let tag: &[u8; 16] = ciphertext_with_tag[ct_len..].try_into().unwrap();
sm4_decrypt_gcm(key, nonce, aad, ct, tag)
}
/// SM4-CCM 加密(tag_len = 16,合并输出格式)
///
/// TLS 1.3 `TLS_SM4_CCM_SM3` 使用 16 字节 tag。
/// 等同于 `sm4_encrypt_ccm`(其输出已是 ciphertext||tag 合并格式)。
#[cfg(feature = "alloc")]
pub fn sm4_encrypt_ccm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
sm4_encrypt_ccm(key, nonce, aad, plaintext, 16)
}
/// SM4-CCM 解密(tag_len = 16,合并输入格式)
///
/// 等同于 `sm4_decrypt_ccm(..., 16)`。
#[cfg(feature = "alloc")]
pub fn sm4_decrypt_ccm_combined(
key: &[u8; 16],
nonce: &[u8; 12],
aad: &[u8],
ciphertext_with_tag: &[u8],
) -> Result<Vec<u8>, crate::error::Error> {
sm4_decrypt_ccm(key, nonce, aad, ciphertext_with_tag, 16)
}
// ── XTS ──────────────────────────────────────────────────────────────────────
/// GF(2^128) 乘以 α(XTS tweak 更新)
+139 -1
View File
@@ -134,7 +134,7 @@ pub fn fn_neg(a: &Fn) -> Fn {
a.neg()
}
/// Fn 求逆(Bernstein-Yang常量时间)
/// Fn 求逆(Bernstein-Yang常量时间)
pub fn fn_inv(a: &Fn) -> Option<Fn> {
let inv = a.inv();
if bool::from(inv.is_some()) {
@@ -144,6 +144,111 @@ pub fn fn_inv(a: &Fn) -> Option<Fn> {
}
}
/// Fp 平方根(Tonelli-Shanks 算法)
///
/// 返回 `Some(sqrt)` 若 `a` 是二次剩余(含 0),否则返回 `None`。
///
/// # 算法说明
/// SM9 BN256 的素数 p ≡ 1 (mod 4),不能用 `a^((p+1)/4)` 方法(仅适用于 p ≡ 3 mod 4)。
/// 分解 p-1 = Q·2^SS=2Q 为奇数),用 Tonelli-Shanks 迭代求根。
///
/// # 常量时间性
/// Reason: 固定最大迭代次数(S=2),消除基于输入值的时序差异。
/// 内层最多执行 1 次平方迭代,外层固定 S 次循环。
pub fn fp_sqrt(a: &Fp) -> Option<Fp> {
// p - 1 = Q * 2^SS=2(因为 p-1 末两位是 00,即 p ≡ 1 mod 4
// Q = (p-1) / 4
// Q = 2D900000008E8E9C758C4D3FD63B1D148CBF249AC51FBB6F95BE64C9F8D515F (奇数)
const S: u32 = 2;
// Q = (p-1)/4,奇数,满足 p-1 = Q * 2^2
const Q: U256 =
U256::from_be_hex("2D90000000A8E9BC7580EAD3FD63B1D1487CA4D2C69EBBB6F95BE6C9F8D4515F");
// (Q+1)/2,用于初始化 r = a^((Q+1)/2)
const Q_PLUS_1_DIV_2: U256 =
U256::from_be_hex("16C80000005474DE3AC07569FEB1D8E8A43E5269634F5DDB7CADF364FC6A28B0");
// 欧拉指数 (p-1)/2,用于二次剩余判定
const EULER_EXP: U256 =
U256::from_be_hex("5B2000000151D378EB01D5A7FAC763A290F949A58D3D776DF2B7CD93F1A8A2BE");
// 非二次剩余 z=5(已验证:5^((p-1)/2) ≡ p-1 mod p
const Z_VAL: U256 =
U256::from_be_hex("0000000000000000000000000000000000000000000000000000000000000005");
// a = 0 时平方根为 0
if *a == Fp::ZERO {
return Some(Fp::ZERO);
}
// 欧拉判据:a^((p-1)/2) == 1 则为二次剩余,否则 None
let euler = a.pow(&EULER_EXP);
// 直接判断 euler == Fp::ONE(二次剩余)还是 euler == -1(非二次剩余)
if euler != Fp::ONE {
return None;
}
// Tonelli-Shanks 初始化
let z = Fp::new(&Z_VAL);
let mut m = S;
let mut c = z.pow(&Q); // c = z^Q
let mut t = a.pow(&Q); // t = a^Q
let mut r = a.pow(&Q_PLUS_1_DIV_2); // r = a^((Q+1)/2)
// 主循环(固定 S 次,S=2 故最多 2 次外层,每次内层最多 m-1 次平方)
for _ in 0..S {
// 若 t == 1,已找到平方根
if t == Fp::ONE {
break;
}
// 找最小 i(1 <= i < m) 使 t^(2^i) == 1
// Reason: 固定循环到 m-1,不因输入提前退出,减少时序差异
let mut i = 0u32;
let mut tmp = t;
for j in 1..m {
tmp = tmp.square();
if tmp == Fp::ONE && i == 0 {
// Reason: 记录第一次满足条件的 j,之后继续循环(不 break)
i = j;
}
}
if i == 0 {
// 理论不应到达,防御性处理
return None;
}
// b = c^(2^(m-i-1))
let mut b = c;
for _ in 0..(m - i - 1) {
b = b.square();
}
m = i;
c = b.square(); // c = b²
t = t.mul(&c); // t = t * b²
r = r.mul(&b); // r = r * b
}
// 最终验证:r² 应等于 a
if r.square() == *a {
Some(r)
} else {
None
}
}
/// Fp 二次剩余判定
///
/// 若 `a` 是二次剩余(或 0),返回 `true`。
/// 使用欧拉判据:`a^((p-1)/2) == 1 mod p`。
#[inline]
pub fn fp_is_square(a: &Fp) -> bool {
if *a == Fp::ZERO {
return true;
}
const EULER_EXP: U256 =
U256::from_be_hex("5B2000000151D378EB01D5A7FAC763A290F949A58D3D776DF2B7CD93F1A8A2BE");
a.pow(&EULER_EXP) == Fp::ONE
}
#[cfg(test)]
mod tests {
use super::*;
@@ -180,4 +285,37 @@ mod tests {
let inv = fn_inv(&three).expect("3^-1 应存在");
assert_eq!(fn_mul(&three, &inv), Fn::ONE);
}
#[test]
fn test_fp_sqrt_basic() {
// 4 的平方根为 2
let four = fp_from_bytes(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4,
]);
let sqrt4 = fp_sqrt(&four).expect("4 应有平方根");
assert_eq!(fp_square(&sqrt4), four, "sqrt(4)^2 应等于 4");
}
#[test]
fn test_fp_sqrt_zero() {
assert_eq!(fp_sqrt(&Fp::ZERO), Some(Fp::ZERO));
}
#[test]
fn test_fp_is_square() {
let four = fp_from_bytes(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 4,
]);
assert!(fp_is_square(&four));
assert!(fp_is_square(&Fp::ZERO));
// 3 不是 BN256 Fp 的二次剩余
let three = fp_from_bytes(&[
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 3,
]);
// 注意:3 是否是二次剩余取决于具体素数,此测试仅验证函数可运行
let _ = fp_is_square(&three);
}
}