发布 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 更新版本支持表
This commit is contained in:
@@ -5,6 +5,38 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased] - v0.2.0
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **BLS signatures** (`bls` module, requires `alloc` feature)
|
||||||
|
- `bls_keygen` / `bls_sign` / `bls_verify`: minimal-signature-size variant (sig ∈ G1, pk ∈ G2)
|
||||||
|
- `bls_aggregate` / `bls_aggregate_verify`: multi-message aggregate signatures
|
||||||
|
- `bls_fast_aggregate_verify`: fast aggregate verification for same-message multi-signer
|
||||||
|
- `BlsSignature::to_bytes` / `from_bytes`: 65-byte serialization (uncompressed G1 point)
|
||||||
|
- `BlsPubKey::to_bytes` / `from_bytes`: 128-byte serialization (uncompressed G2 point)
|
||||||
|
- **BLS threshold signatures** (`bls::threshold` module)
|
||||||
|
- `bls_threshold_keygen`: Trusted Dealer mode, Shamir polynomial secret sharing
|
||||||
|
- `bls_partial_sign` / `bls_combine_signatures`: Lagrange interpolation based aggregation
|
||||||
|
- Supports (t+1, n) threshold configurations
|
||||||
|
- **Hash-to-Curve** (`bls::hash_to_curve` module)
|
||||||
|
- `hash_to_g1`: RFC 9380 compliant, maps arbitrary message to BN256 G1 point
|
||||||
|
- `expand_message_xmd`: RFC 9380 §5.3.1, message expansion using SM3 as hash
|
||||||
|
- `map_to_curve_svdw`: Shallue-van de Woestijne mapping for BN256 (a=0 curve)
|
||||||
|
- **`fp_sqrt`** in `sm9::fields::fp`
|
||||||
|
- Tonelli-Shanks modular square root for SM9 BN256 Fp (p ≡ 1 mod 4)
|
||||||
|
- `fp_is_square`: Euler criterion based quadratic residue test
|
||||||
|
- **FPE format-preserving encryption** (`fpe` module)
|
||||||
|
- `FpeKey`: 7-round Luby-Rackoff Feistel cipher based on SM4
|
||||||
|
- Supports 1~128 bit plaintext/ciphertext domains
|
||||||
|
- `expand_tweak`: arbitrary-length tweak via SM4 hash
|
||||||
|
- Automatic key zeroization on drop (`ZeroizeOnDrop`)
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- BLS signature DST separation: signing uses `BLS_SIG_SM9G1_XMD:SM3_SVDW_RO_NUL_`, PoP uses a different tag
|
||||||
|
- BN256 security note: ~100-bit actual security level documented in API docs
|
||||||
|
|
||||||
## [0.1.1] - 2025-03-07
|
## [0.1.1] - 2025-03-07
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -70,5 +102,6 @@ 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
|
- 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
|
- SM9 `hash_to_range`: replaced variable-iteration `while` loop with constant-time conditional select
|
||||||
|
|
||||||
|
[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.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
|
||||||
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
|
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
|
||||||
|
|||||||
@@ -5,6 +5,38 @@
|
|||||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
||||||
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
||||||
|
|
||||||
|
## [Unreleased] - v0.2.0
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
|
||||||
|
- **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 Fp,p ≡ 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
|
## [0.1.1] - 2025-03-07
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
@@ -70,5 +102,6 @@
|
|||||||
- XTS:拒绝非 16 字节对齐输入,而非静默截断
|
- XTS:拒绝非 16 字节对齐输入,而非静默截断
|
||||||
- SM9 `hash_to_range`:用常量时间条件选择替换可变迭代 `while` 循环
|
- SM9 `hash_to_range`:用常量时间条件选择替换可变迭代 `while` 循环
|
||||||
|
|
||||||
|
[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.1]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.1
|
||||||
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
|
[0.1.0]: https://github.com/kintaiW/libsmx/releases/tag/v0.1.0
|
||||||
|
|||||||
Generated
+1
-1
@@ -422,7 +422,7 @@ checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsmx"
|
name = "libsmx"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"crypto-bigint",
|
"crypto-bigint",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libsmx"
|
name = "libsmx"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.83.0"
|
rust-version = "1.83.0"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ Pure-Rust, `#![no_std]` implementation of Chinese commercial cryptography standa
|
|||||||
| **SM3** | GB/T 32905-2016 | Cryptographic Hash Algorithm (256-bit) |
|
| **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) |
|
| **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) |
|
| **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
|
## Features
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ Add to `Cargo.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsmx = "0.1"
|
libsmx = "0.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
### SM3 Hash
|
### SM3 Hash
|
||||||
@@ -154,7 +156,7 @@ For `no_std` without `alloc`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsmx = { version = "0.1", default-features = false }
|
libsmx = { version = "0.2", default-features = false }
|
||||||
```
|
```
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|||||||
+4
-2
@@ -15,6 +15,8 @@
|
|||||||
| **SM3** | GB/T 32905-2016 | 密码杂凑算法(256 位) |
|
| **SM3** | GB/T 32905-2016 | 密码杂凑算法(256 位) |
|
||||||
| **SM4** | GB/T 32907-2016 | 分组密码(128 位密钥,ECB/CBC/CTR/GCM/CCM/XTS) |
|
| **SM4** | GB/T 32907-2016 | 分组密码(128 位密钥,ECB/CBC/CTR/GCM/CCM/XTS) |
|
||||||
| **SM9** | GB/T 38635.1-2-2020 | 标识密码(BN256 双线性配对) |
|
| **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
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsmx = "0.1"
|
libsmx = "0.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
### SM3 哈希
|
### SM3 哈希
|
||||||
@@ -186,7 +188,7 @@ assert_eq!(decrypted, plaintext);
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libsmx = { version = "0.1", default-features = false }
|
libsmx = { version = "0.2", default-features = false }
|
||||||
```
|
```
|
||||||
|
|
||||||
无 `alloc` 时,SM3 哈希、SM3 HMAC、SM2 签名/验签、SM4 ECB 仍可用(固定大小数组 API)。
|
无 `alloc` 时,SM3 哈希、SM3 HMAC、SM2 签名/验签、SM4 ECB 仍可用(固定大小数组 API)。
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
|---------|-----------|
|
|---------|-----------|
|
||||||
|
| 0.2.x | Yes |
|
||||||
| 0.1.x | Yes |
|
| 0.1.x | Yes |
|
||||||
| < 0.1 | No |
|
| < 0.1 | No |
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
| 版本 | 是否支持 |
|
| 版本 | 是否支持 |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
|
| 0.2.x | 是 |
|
||||||
| 0.1.x | 是 |
|
| 0.1.x | 是 |
|
||||||
| < 0.1 | 否 |
|
| < 0.1 | 否 |
|
||||||
|
|
||||||
|
|||||||
@@ -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³ + 5(a=0,b=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=0,b=5:
|
||||||
|
// g(Z) = Z³ + 5 = -1 + 5 = 4
|
||||||
|
// c1 = g(Z) = 4
|
||||||
|
// c2 = -Z / 2 = 1/2 mod p(Z=-1 时,-Z=1,1/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_xmd(RFC 9380 §5.3.1)
|
||||||
|
///
|
||||||
|
/// 使用 SM3(b_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 = 32(SM3 输出长度),r_in_bytes = 64(SM3 块大小)
|
||||||
|
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^256,384 - 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 + r,r = 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 → G1(RFC 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³ + 5,Z = -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 p(Z=-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=0,inv0(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³ + b(a=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-G1(RFC 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
@@ -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_im,128 字节)
|
||||||
|
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 * P2(G2 基点)
|
||||||
|
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_i(G2 点加法)
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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`:总份额数 n(n >= 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 > j:diff = -(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_i(G1 标量乘)
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
//! FNR(Flexible 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 位为 val(0 或 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 字节输入:tweak(15字节)|| round(1字节)
|
||||||
|
let mut block = [0u8; 16];
|
||||||
|
block[..15].copy_from_slice(tweak);
|
||||||
|
block[15] = round as u8;
|
||||||
|
|
||||||
|
// XOR half 到 block(half 最多 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 / 2,right_bits = n - left_bits(right >= 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 来自旧 r(right_bits 位)→ 取前 left_bits 位
|
||||||
|
// 新 r 是 new_r(left_bits 位)→ 作为新右半(right_bits 位)
|
||||||
|
// 由于 right_bits >= left_bits,r 的有效位取前 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
@@ -0,0 +1,293 @@
|
|||||||
|
//! FPE(Format-Preserving Encryption)保留格式加密
|
||||||
|
//!
|
||||||
|
//! 基于 FNR(Flexible 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 扩展 tweak(15 字节)
|
||||||
|
///
|
||||||
|
/// 由 `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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,3 +56,8 @@ pub mod sm2;
|
|||||||
pub mod sm3;
|
pub mod sm3;
|
||||||
pub mod sm4;
|
pub mod sm4;
|
||||||
pub mod sm9;
|
pub mod sm9;
|
||||||
|
|
||||||
|
#[cfg(feature = "alloc")]
|
||||||
|
pub mod bls;
|
||||||
|
|
||||||
|
pub mod fpe;
|
||||||
|
|||||||
+139
-1
@@ -134,7 +134,7 @@ pub fn fn_neg(a: &Fn) -> Fn {
|
|||||||
a.neg()
|
a.neg()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fn 求逆(Bernstein-Yang��常量时间)
|
/// Fn 求逆(Bernstein-Yang,常量时间)
|
||||||
pub fn fn_inv(a: &Fn) -> Option<Fn> {
|
pub fn fn_inv(a: &Fn) -> Option<Fn> {
|
||||||
let inv = a.inv();
|
let inv = a.inv();
|
||||||
if bool::from(inv.is_some()) {
|
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^S(S=2,Q 为奇数),用 Tonelli-Shanks 迭代求根。
|
||||||
|
///
|
||||||
|
/// # 常量时间性
|
||||||
|
/// Reason: 固定最大迭代次数(S=2),消除基于输入值的时序差异。
|
||||||
|
/// 内层最多执行 1 次平方迭代,外层固定 S 次循环。
|
||||||
|
pub fn fp_sqrt(a: &Fp) -> Option<Fp> {
|
||||||
|
// p - 1 = Q * 2^S,S=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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -180,4 +285,37 @@ mod tests {
|
|||||||
let inv = fn_inv(&three).expect("3^-1 应存在");
|
let inv = fn_inv(&three).expect("3^-1 应存在");
|
||||||
assert_eq!(fn_mul(&three, &inv), Fn::ONE);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user