发布 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:
huangxt
2026-03-08 20:02:06 +08:00
parent 69767aee00
commit a58fe8275e
15 changed files with 1720 additions and 7 deletions
+33
View File
@@ -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
+33
View File
@@ -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 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 ## [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
View File
@@ -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
View File
@@ -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"
+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) | | **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
View File
@@ -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)。
+1
View File
@@ -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 |
+1
View File
@@ -4,6 +4,7 @@
| 版本 | 是否支持 | | 版本 | 是否支持 |
|------|----------| |------|----------|
| 0.2.x | 是 |
| 0.1.x | 是 | | 0.1.x | 是 |
| < 0.1 | 否 | | < 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());
}
}
+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 sm3;
pub mod sm4; pub mod sm4;
pub mod sm9; pub mod sm9;
#[cfg(feature = "alloc")]
pub mod bls;
pub mod fpe;
+139 -1
View File
@@ -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^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)] #[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);
}
} }