#secret-sharing #shamir-secret-sharing #secret #sharing #verifiable

no-std vsss-rs

可验证的秘密共享方案,用于分割、合并和验证秘密份额

62个版本 (40个稳定版)

4.3.8 2024年7月24日
4.3.0 2024年5月2日
4.0.0 2024年3月26日
3.4.0 2024年2月21日
0.1.0 2021年7月2日

密码学分类中排名第28

Download history 2979/week @ 2024-04-28 2484/week @ 2024-05-05 3373/week @ 2024-05-12 2534/week @ 2024-05-19 2481/week @ 2024-05-26 1815/week @ 2024-06-02 2166/week @ 2024-06-09 2757/week @ 2024-06-16 3539/week @ 2024-06-23 2052/week @ 2024-06-30 3046/week @ 2024-07-07 3658/week @ 2024-07-14 2593/week @ 2024-07-21 3429/week @ 2024-07-28 4048/week @ 2024-08-04 4671/week @ 2024-08-11

每月下载量15,213
14crate(6个直接)中使用

Apache-2.0 OR MIT

240KB
6K SLoC

可验证的秘密共享方案

Crate Docs Apache 2.0

此crate在Rust标准库可用时提供各种密码学可验证的秘密共享方案。

  • 此实现目前正在审计中,完成时将公布结果。在此之前,请自行承担风险使用。
  • 此实现不需要Rust标准库。
  • 除非明确指出,所有操作都是常数时间。

注意:从版本2升级

接口已被重新设计以相互兼容以及序列化。

版本3定义了一组用于实现秘密共享方案的特质。虽然标准模式提供了快速简便的方法来分割和合并秘密,但这些特质允许在无-std模式下具有更多灵活性和定制性。以前的版本试图保持两种模式的一致性,但这样做导致了大量代码重复和栈溢出问题。新的特质允许单个实现用于两种模式,并允许无-std消费者使用他们确切需要的,同时最小化代码重复和栈溢出。

注意:从版本3升级

“ShareIdentifier”特质已被修改如下

  • fn to_buffer:接收一个字节数组缓冲区并填充标识符的字节表示形式
  • fn from_buffer:接收一个字节数组缓冲区并从字节表示形式创建标识符
  • to_vec:如果启用features=alloc或std,则返回字节表示形式作为Vec。

为什么进行此更改?

之前的方法不足以处理不同类型的标识符,例如u16、u32、u64等,因为as_bytes返回&[u8],在使用u16、u32等时没有安全的方法将标识符的字节表示形式转换为标识符。因此,选择是继续使用不安全的代码(不好)或实现一个包装结构体来处理转换,但需要实现与原始类型相同的方法和特性(也不太好,导致大量样板代码)。

Share特质进行了如下修改

  • value和value_mut现在与ShareIdentifier中的to_buffer和from_buffer相对应
  • value_vec与ShareIdentifier中的to_vec相对应

其他更改

在为Share特质实现固定大小为33、49和97之前。现在支持所有数组大小。

此外,支持任何大小的GenericArray。同样支持crypto-bigint Uint类型。

现在支持以标识符作为.0和份额作为.1的元组。

Gf256已添加为仅包含字节序列的秘密共享方案的字段。所有操作都是常数时间。与其他库相比,大多数实现都不是常数时间,因为:运行时间取决于秘密的值,例如使用if语句和带有break语句的循环,或使用允许攻击者监控代码或数据访问模式的查找表。虽然对性能有很大帮助,但它们不是常数时间,这对于密码学操作来说是理想的选择。此实现已与其他库进行了交叉检查,以确保兼容性,并实现了必要的特性以与该库一起使用。

编号

已添加Share编号方法。默认方法是使用从1开始的递增数字。虽然足够简单,但仍然不足以适应所有用例。以下编号方法可用

  • SequentialParticipantNumberGenerator:share标识符的索引从指定的数字开始,以指定的值递增,直到达到限制。默认是开始于1,递增1直到达到255。
  • RandomParticipantNumberGenerator:share标识符的索引是随机的。随机数生成器基于所需的索引,一个域分隔符,这些分隔符使用Shake256进行哈希。
  • ListParticipantNumberGenerator:share标识符的索引基于一个列表。提供的列表必须至少与要生成的份额数量一样长。这在份额标识符在事先已知的情况下很有用,如主动秘密共享的情况。
  • ListAndRandomParticipantNumberGenerator:上述两种方法的组合。首先使用列表,然后使用随机数字,直到列表耗尽。
  • ListAndSequentialParticipantNumberGenerator:上述两种方法的组合。首先使用列表,然后使用递增数字,直到列表耗尽。

新方法split_secret_with_participant_generator允许使用上述方法。如果您不需要新的灵活方法,仍然可以使用旧方法split_secret,它使用从1开始的递增数字的SequentialParticipantNumberGenerator。

份额和标识符

有许多请求希望能够使共享标识符不仅仅局限于整数值。现在通过实现ShareIdentifier特质,这一需求已成为可能。ShareIdentifier特质是一个简单的特质,它提供了分割和合并共享所需的方法。默认情况下,ShareIdentifier特质是为原始整数值实现的。其他值可以通过为所需类型实现该特质来使用,但需要注意字节序。默认情况下,原始类型以大端字节序列表示。正如之前所述,Share实现了[u8; N]、GenericArray、Uint、Vec以及所有无符号整数类型的ShareIdentifier。还支持诸如{原始整数类型,[u8; N]}这样的元组。消费者可以根据需要实现这两个特质。

以下元组也实现了Share

  • ({原始整数类型}, [u8; N])
  • ({原始整数类型}, GenericArray)
  • ({原始整数类型}, Vec) 当与stdalloc功能一起使用时

如果共享标识符是u8,则存在额外的实现。

  • [u8; N+1]其中N是共享大小,第一个字节是标识符
  • GenericArray其中N是共享大小,第一个字节是标识符
  • Vec其中第一个字节是标识符

多项式

Polynomial存储多项式的系数,并提供在给定点评估多项式的方法。多项式仅在分割机密时使用。

共享集

共享集是同一机密所属的共享集合。共享集提供将共享合并回原始机密或另一个组的方法。这些作为ReadableShareSetWriteableShareSet提供。在无std模式下,合并需要ShareSetCombiner

ShareSetCombiner是在机密重构操作期间使用的数据存储。

秘密共享方案

秘密共享方案作为特质实现。这些特质提供分割机密的方法,如果适用,则返回验证器集。Shamir仅分割机密。Feldman返回一个验证器集。Pedersen返回多个验证器集:一个用于自身和一个用于Feldman

FeldmanVerifierSetPedersenVerifierSet是方案返回的验证器集。它们提供验证共享的方法。

由于Pedersen在分割后返回大量信息,因此使用PedersenResult特质来封装数据。StdPedersenResult在默认情况下提供分配器时提供。

其他值得注意的事项

在标准模式下运行时,不需要实现任何特质,并且有默认函数可以完成您想要的功能,就像在之前的版本中一样。

StdVsss提供了完成分割和重构机密所需的大部分方法,但需要指定大量的泛型参数。如果您需要使用特定字段,则提供DefaultStdVsss以简化过程。DefaultStdVsss假定标识符是u8,共享是Vec<u8>

如果您在无std模式下需要自定义结构,则vsss_arr_impl宏将为您创建必要的实现。

文档

可验证的秘密共享方案用于将秘密分割成多个份额,并分发到不同的实体中,能够验证份额是否正确并且属于特定集合。这个crate包括Shamir的秘密共享方案,它不支持验证,但更多是其他方案的基础构建块。

这个crate支持Feldman和Pedersen的可验证秘密共享方案。

Feldman和Pedersen在很多方面都很相似。很难描述在什么情况下使用其中一个而不用另一个。实际上,两者都用于分布式密钥生成

Feldman揭示了验证者的公开值,而Pedersen则隐藏它。

Feldman和Pedersen在分割秘密方面与Shamir不同。将份额重新组合成原始秘密的方法在所有方法中都是相同的,并且每个方案都提供了便利。

这个crate符合no-std规范,并使用const generics来指定大小。

默认情况下,份额由字节数组表示,但可以通过实现提供的特性来更改。在指定份额大小时,使用字段大小(字节)+ 1 作为标识符。份额可以表示有限域或群,具体取决于用例。第一个字节保留为份额标识符(x坐标),其余的都是份额的实际值(y坐标)。

默认方法

分割和组合秘密的默认方法是

  • shamir::split_secret
  • feldman::split_secret
  • pedersen::split_secret
  • combine_shares
  • combine_shares_group

P-256

使用Shamir分割p256秘密

use vsss_rs::{*, shamir};
use elliptic_curve::ff::PrimeField;
use p256::{NonZeroScalar, Scalar, SecretKey};

let mut osrng = rand_core::OsRng::default();
let sk = SecretKey::random(&mut osrng);
let nzs = sk.to_nonzero_scalar();
let res = shamir::split_secret::<Scalar, u8, Vec<u8>>(2, 3, *nzs.as_ref(), &mut osrng);
assert!(res.is_ok());
let shares = res.unwrap();
let res = combine_shares(&shares);
assert!(res.is_ok());
let scalar: Scalar = res.unwrap();
let nzs_dup =  NonZeroScalar::from_repr(scalar.to_repr()).unwrap();
let sk_dup = SecretKey::from(nzs_dup);
assert_eq!(sk_dup.to_bytes(), sk.to_bytes());

或使用DefaultStdVsss结构体

 use elliptic_curve::ff::Field;

let mut osrng = rand_core::OsRng::default();
let secret = p256::Scalar::random(&mut osrng);
let res = DefaultStdVsss::<p256::ProjectivePoint>::split_secret(2, 3, secret, &mut osrng);
assert!(res.is_ok());
let shares = res.unwrap();
let res = combine_shares(&shares);
assert!(res.is_ok());
let scalar: p256::Scalar = res.unwrap();
assert_eq!(secret, scalar);

Secp256k1

使用Shamir分割k256秘密

use vsss_rs::{*, shamir};
use elliptic_curve::ff::PrimeField;
use k256::{NonZeroScalar, Scalar, ProjectivePoint, SecretKey};

let mut osrng = rand_core::OsRng::default();
let sk = SecretKey::random(&mut osrng);
let secret = *sk.to_nonzero_scalar();
let res = shamir::split_secret::<Scalar, [u8; 1], u8, Vec<u8>>(2, 3, secret, &mut osrng);
assert!(res.is_ok());
let shares = res.unwrap();
let res = combine_shares(&shares);
assert!(res.is_ok());
let scalar: Scalar = res.unwrap();
let nzs_dup = NonZeroScalar::from_repr(scalar.to_repr()).unwrap();
let sk_dup = SecretKey::from(nzs_dup);
assert_eq!(sk_dup.to_bytes(), sk.to_bytes());

或使用feldman

use vsss_rs::{*, feldman};
use bls12_381_plus::{Scalar, G1Projective};
use elliptic_curve::ff::Field;

let mut rng = rand_core::OsRng::default();
let secret = Scalar::random(&mut rng);
let res = feldman::split_secret::<G1Projective, [u8; 1], u8, Vec<u8>>(2, 3, secret, None, &mut rng);
assert!(res.is_ok());
let (shares, verifier) = res.unwrap();
for s in &shares {
    assert!(verifier.verify_share(s).is_ok());
}
let res = combine_shares(&shares);
assert!(res.is_ok());
let secret_1: Scalar = res.unwrap();
assert_eq!(secret, secret_1);

Curve25519

Curve25519不是一个素域,但这个crate使用features=["curve25519"]支持它,默认情况下启用。这个特性包装了curve25519-dalek库,以便与Shamir、Feldman和Pedersen一起使用。

这是一个使用Ed25519和x25519的示例

use curve25519_dalek::scalar::Scalar;
use rand::Rng;
use ed25519_dalek::SecretKey;
use vsss_rs::{curve25519::WrappedScalar, *};
use x25519_dalek::StaticSecret;

let mut osrng = rand::rngs::OsRng::default();
let sc = Scalar::hash_from_bytes::<sha2::Sha512>(&osrng.gen::<[u8; 32]>());
let sk1 = StaticSecret::from(sc.to_bytes());
let ske1 = SecretKey::from_bytes(&sc.to_bytes()).unwrap();
let res = shamir::split_secret::<WrappedScalar, [u8; 1], u8, Vec<u8>>(2, 3, sc.into(), &mut osrng);
assert!(res.is_ok());
let shares = res.unwrap();
let res = combine_shares(&shares);
assert!(res.is_ok());
let scalar: WrappedScalar = res.unwrap();
assert_eq!(scalar.0, sc);
let sk2 = StaticSecret::from(scalar.0.to_bytes());
let ske2 = SecretKey::from_bytes(&scalar.0.to_bytes()).unwrap();
assert_eq!(sk2.to_bytes(), sk1.to_bytes());
assert_eq!(ske1.to_bytes(), ske2.to_bytes());

使用Feldman和Pedersen VSSS时,可以使用RistrettoPointEdwardsPoint

许可证

许可证

根据您选择的以下任何一个许可证授权

供您选择。

贡献

除非您明确声明,否则任何有意提交给作品以供您包含的贡献,根据Apache-2.0许可证的定义,都应按上述方式授权,没有任何额外的条款或条件。

参考

  1. 如何共享秘密,Shamir,A. Nov,1979
  2. 实用的非交互式可验证秘密共享方案,Feldman,P. 1987
  3. 非交互和信息论安全可验证秘密共享,Pedersen,T. 1991

依赖关系

~3.5–6.5MB
~131K SLoC