发布 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:
+139
-1
@@ -134,7 +134,7 @@ pub fn fn_neg(a: &Fn) -> Fn {
|
||||
a.neg()
|
||||
}
|
||||
|
||||
/// Fn 求逆(Bernstein-Yang��常量时间)
|
||||
/// Fn 求逆(Bernstein-Yang,常量时间)
|
||||
pub fn fn_inv(a: &Fn) -> Option<Fn> {
|
||||
let inv = a.inv();
|
||||
if bool::from(inv.is_some()) {
|
||||
@@ -144,6 +144,111 @@ pub fn fn_inv(a: &Fn) -> Option<Fn> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fp 平方根(Tonelli-Shanks 算法)
|
||||
///
|
||||
/// 返回 `Some(sqrt)` 若 `a` 是二次剩余(含 0),否则返回 `None`。
|
||||
///
|
||||
/// # 算法说明
|
||||
/// SM9 BN256 的素数 p ≡ 1 (mod 4),不能用 `a^((p+1)/4)` 方法(仅适用于 p ≡ 3 mod 4)。
|
||||
/// 分解 p-1 = Q·2^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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -180,4 +285,37 @@ mod tests {
|
||||
let inv = fn_inv(&three).expect("3^-1 应存在");
|
||||
assert_eq!(fn_mul(&three, &inv), Fn::ONE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fp_sqrt_basic() {
|
||||
// 4 的平方根为 2
|
||||
let four = fp_from_bytes(&[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 4,
|
||||
]);
|
||||
let sqrt4 = fp_sqrt(&four).expect("4 应有平方根");
|
||||
assert_eq!(fp_square(&sqrt4), four, "sqrt(4)^2 应等于 4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fp_sqrt_zero() {
|
||||
assert_eq!(fp_sqrt(&Fp::ZERO), Some(Fp::ZERO));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fp_is_square() {
|
||||
let four = fp_from_bytes(&[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 4,
|
||||
]);
|
||||
assert!(fp_is_square(&four));
|
||||
assert!(fp_is_square(&Fp::ZERO));
|
||||
// 3 不是 BN256 Fp 的二次剩余
|
||||
let three = fp_from_bytes(&[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 3,
|
||||
]);
|
||||
// 注意:3 是否是二次剩余取决于具体素数,此测试仅验证函数可运行
|
||||
let _ = fp_is_square(&three);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user