GithubHelp home page GithubHelp logo

vsss-rs's Introduction

Verifiable Secret Sharing Schemes

Crate Docs Apache 2.0

This crate provides various cryptography verifiable secret sharing schemes when the rust standard library is available.

  • This implementation is currently under audit and results will be published when completed. Until then use at your own risk.
  • This implementation does not require the Rust standard library.
  • All operations are constant time unless explicitly noted.

NOTE if upgrading from Version 2

The interfaces have been redesigned to be compatible with each other as well as serialization.

Version 3 defines a set of traits for implementing secret sharing schemes. While the standard mode provides quick and easy methods to split and combine secrets, the traits allow for more flexibility and customization especially in no-std mode. Previous versions tried to keep the two modes aligned but in doing so resulted in a lot of code duplication and stack overflow issues. The new traits allow for a single implementation to be used in both modes and allow no-std consumers to use exactly what they need while minimizing code duplication and stack overflows.

NOte if upgrading from Version 3

The ShareIdentifier trait has been modified as follows:

  • fn to_buffer which receives a byte buffer and fills the contents with the byte representation of the identifier
  • fn from_buffer which receives a byte buffer and creates the identifier from the byte representation
  • to_vec which returns the byte representation as a Vec if features=alloc or std is enabled.

Why this change?

The previous method was not flexible enough to handle different types of identifiers such as u16, u32, u64, etc. because as_bytes returned &[u8] and there was no safe method to convert the byte representation from the identifier when using u16, u32, etc. So the option was to either continue as is but use unsafe code with Statics (not good), or implement a wrapper struct that would handle the conversion but needed to implement the same methods and traits as the primitives would (also not good and results in a lot of boilerplate).

The Share trait has been modified as follows:

  • value and value_mut now mirror to_buffer and from_buffer in ShareIdentifier
  • value_vec mirrors to_vec in ShareIdentifier

Other changes

Before the Share trait was implemented for fixed sizes of 33, 49, and 97. Now all array sizes are supported.

In addition, GenericArray of any size is supported. crypto-bigint Uint types are supported as well.

Now tuples with the identifier as .0 and the share as .1 are supported.

Gf256 has been added as a field for secret sharing schemes with just byte sequences. All operations are constant time. Most implementations comparatively are not constant time due to: runtime is dependent on the value of the secret such as using if statements and looping with break statements, or using lookup tables that allow an attacker to monitor code or data access patterns. While great for performance, they are not constant time which is desirable for cryptographic operations. This implementation has been cross-checked for compatibility with other libraries and also implements the necessary traits to function with this library.

Numbering

Share numbering methods have been added. The default method has been to use incrementing numbers starting at 1. While simple enough, again it's not flexible enough for all use cases. The following numbering methods are available:

  • SequentialParticipantNumberGenerator: index for the share identifiers starts at a specified number and incrementing by a specified value until a limit is reached. The default is starting at 1 and incrementing by 1 until 255 is reached
  • RandomParticipantNumberGenerator: index for the share identifiers is random. The random number generator is based on the desired index, a domain separator which are hashed using Shake256.
  • ListParticipantNumberGenerator: index for the share identifiers is based on a list. The provided list must be at least as long as the number of shares to be generated. This is useful when the share identifiers are known ahead of time like in the case of proactive secret sharing.
  • ListAndRandomParticipantNumberGenerator: a combination of the above two methods. The list is used first and then random numbers are used after the list is exhausted.
  • ListAndSequentialParticipantNumberGenerator: a combination of the above two methods. The list is used first and then sequential numbers are used after the list is exhausted.

The new method split_secret_with_participant_generator enables the use of the above methods. If you don't need the new flexible methods, the old method split_secret is still available which uses SequentialParticipantNumberGenerator starting at 1 and incrementing by 1.

Shares and Identifiers

There was lots of requests to enable share identifiers to be more than just integer values. This is now possible by implementing the ShareIdentifier trait. The ShareIdentifier trait is a simple trait that provides the necessary methods for splitting and combining shares. The ShareIdentifier trait is implemented for primitive integer values by default. Other values can be used by implementing the trait for the desired type but keep in might endianness. By default, primitive types represented as big-endian byte sequences. As explained earlier, Share is implemented for [u8; N], GenericArray<u8, N>, Uint, Vec, and ShareIdentifier for all unsigned integer types. Tuples such as {primitive integer type, [u8; N]} are also supported. Both traits can be implemented however consumers need them to be.

The following tuples also implement Share:

  • ({primitive integer type}, [u8; N])
  • ({primitive integer type}, GenericArray<u8, N>)
  • ({primitive integer type}, Vec) when used with the std or alloc feature

If the share identifier is u8 then the additional implementations exist.

  • [u8; N+1] where N is the share size and the first byte is the identifier
  • GenericArray<u8, N+1> where N is the share size and the first byte is the identifier
  • Vec where the first byte is the identifier

Polynomials

Polynomial holds the coefficients of the polynomial and provides methods to evaluate the polynomial at a given point. Polymomials are only used when splitting secrets.

Share Sets

A share set is a collection of shares that belong to the same secret. The share set provides methods to combine into the original secret or another group. These are offered as ReadableShareSet and WriteableShareSet. In no-std mode, combines require a ShareSetCombiner.

ShareSetCombiner is the data store used during a secret reconstruct operation.

Secret Sharing Schemes

Secret sharing schemes are implemented as traits. The traits provide methods to split secrets and if applicable return the verifier set. Shamir only splits secrets. Feldman returns a verifier set. Pedersen returns multiple verifier sets: one for itself and one for Feldman.

FeldmanVerifierSet and PedersenVerifierSet are the verifier sets returned by the schemes. They provide methods to validate the shares.

Since Pedersen returns a large amount of information after a split the PedersenResult trait is used to encapsulate the data. StdPedersenResult is provided when an allocator is available by default.

Other noteworthy items

When operating in standard mode, no traits should be necessary to be implemented and there are default functions to accomplish what you want just like in previous versions.

StdVsss provides the majority of methods needed to accomplish splitting and reconstructing secrets but requires specifying lots of generic parameters. If you need to use a specific field, DefaultStdVsss is provided to make the process easier. DefaultStdVsss assumes the identifier is u8 and the share is a Vec<u8>.

If you need custom structs in no-std mode the vsss_arr_impl macro will create the necessary implementations for you.

Verifiable Secret Sharing Schemes are using to split secrets into multiple shares and distribute them among different entities, with the ability to verify if the shares are correct and belong to a specific set. This crate includes Shamir's secret sharing scheme which does not support verification but is more of a building block for the other schemes.

This crate supports Feldman and Pedersen verifiable secret sharing schemes.

Feldman and Pedersen are similar in many ways. It's hard to describe when to use one over the other. Indeed, both are used in distributed key generation.

Feldman reveals the public value of the verifier whereas Pedersen's hides it.

Feldman and Pedersen are different from Shamir when splitting the secret. Combining shares back into the original secret is identical across all methods and is available for each scheme for convenience.

This crate is no-std compliant and uses const generics to specify sizes.

Shares are represented as byte arrays by default but can be changed by implementing the provided traits. When specifying share sizes, use the field size in bytes + 1 for the identifier. Shares can represent finite fields or groups depending on the use case. The first byte is reserved for the share identifier (x-coordinate) and everything else is the actual value of the share (y-coordinate).

Default methods

The default methods for splitting and combining secrets are:

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

P-256

To split a p256 secret using Shamir

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());

Or using the DefaultStdVsss struct

 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

To split a k256 secret using Shamir

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());

or to use 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 is not a prime field but this crate does support it using features=["curve25519"] which is enabled by default. This feature wraps curve25519-dalek libraries so they can be used with Shamir, Feldman, and Pedersen.

Here's an example of using Ed25519 and 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());

Either RistrettoPoint or EdwardsPoint may be used when using Feldman and Pedersen VSSS.

License

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.

References

  1. How to share a secret, Shamir, A. Nov, 1979
  2. A Practical Scheme for Non-interactive Verifiable Secret Sharing, Feldman, P. 1987
  3. Non-Interactive and Information-Theoretic Secure Verifiable Secret Sharing, Pedersen, T. 1991

vsss-rs's People

Contributors

mikelodder7 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

vsss-rs's Issues

Incompatible Dependencies Have Missing Traits

I wanted to play around with the crate to implement DKG but I noticed that new versions dependencies seem to be incompatible. It seems that the sha dependency does not play nice with curve25519_dalek anymore. When I try to run the example BLS12-381 code the compiler complains about missing trait bounds.

339 |         Self(EdwardsPoint::hash_from_bytes::<sha2::Sha512>(&seed))
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait 'curve25519_dalek::digest::FixedOutput' is not implemented for 'Sha512'

and

339 |         Self(EdwardsPoint::hash_from_bytes::<sha2::Sha512>(&seed))
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `HashMarker` is not implemented for `Sha512`

I have not played around with it too much but I suspect pinning the dependencies to exact versions would work but of course would mean outdated dependencies. Is there a better solution to solve the issue? Are there any plans to add the missing traits?

Thanks a lot for the work in this crate I really appreciate it.

Support for >255 shamir parties

Hi, I noticed this bit in shamir.rs:

        // Generate the shares of (x, y) coordinates
        // x coordinates are incremental from [1, N+1). 0 is reserved for the secret
        let mut shares = Vec::with_capacity(self.n);
        let mut x = F::one();
        for i in 0..self.n {
            let y = polynomial.evaluate(x, self.t);
            let mut t = Vec::with_capacity(1 + y.to_repr().as_ref().len());
            t.push((i + 1) as u8);
            t.extend_from_slice(y.to_repr().as_ref());

            shares.push(Share(t));

            x += F::one();
        }
        (shares, polynomial)

And this comment in share.rs:

/// The first byte is the X-coordinate or identifier
/// The remaining bytes are the Y-coordinate

I'm not sure if it's documented explicitly, but it seems to point to only supporting secret sharing with up to 255 parties. I was wondering how much might need to be changed to support a greater number of parties (potentially on the order of a few thousand). I'm assuming just changing the structure of the Share type and changing a few other constants, right?

Subtle dependency not truly optional

In src/utils.rs, subtle is unconditionally used, but in Cargo.toml, subtle is marked as optional. This causes builds to fail. I would try to fix it myself, but all the tests fail as well, which cannot be a good sign.

Latest stable release doesn't compile on Rust 1.61

Starting a new project and adding

vsss-rs = "1.4.0"

to Cargo.toml will not compile with the latest version of Rust (or with Rust 1.59, which was the only other stable release I happen to have installed):

error[E0277]: the trait bound `Sha512: curve25519_dalek::digest::FixedOutput` is not satisfied
   --> /home/john/.cargo/registry/src/github.com-1ecc6299db9ec823/vsss-rs-1.4.0/src/curve25519.rs:339:14
    |
339 |         Self(EdwardsPoint::hash_from_bytes::<sha2::Sha512>(&seed))
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `curve25519_dalek::digest::FixedOutput` is not implemented for `Sha512`
    |
    = note: required because of the requirements on the impl of `curve25519_dalek::digest::Digest` for `Sha512`
note: required by a bound in `EdwardsPoint::hash_from_bytes`
   --> /home/john/.cargo/registry/src/github.com-1ecc6299db9ec823/curve25519-dalek-4.0.0-pre.2/src/edwards.rs:533:12
    |
533 |         D: Digest<OutputSize = U64> + Default,
    |            ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `EdwardsPoint::hash_from_bytes`

error[E0277]: the trait bound `Sha512: HashMarker` is not satisfied
   --> /home/john/.cargo/registry/src/github.com-1ecc6299db9ec823/vsss-rs-1.4.0/src/curve25519.rs:339:14
    |
339 |         Self(EdwardsPoint::hash_from_bytes::<sha2::Sha512>(&seed))
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `HashMarker` is not implemented for `Sha512`
    |
    = note: required because of the requirements on the impl of `curve25519_dalek::digest::Digest` for `Sha512`
note: required by a bound in `EdwardsPoint::hash_from_bytes`
   --> /home/john/.cargo/registry/src/github.com-1ecc6299db9ec823/curve25519-dalek-4.0.0-pre.2/src/edwards.rs:533:12
    |
533 |         D: Digest<OutputSize = U64> + Default,
    |            ^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `EdwardsPoint::hash_from_bytes`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `vsss-rs` due to 2 previous errors

Proper way to construct a WrappedScalar

The secp256k1::WrappedScalar has the following constructors:

From<k256::arithmetic::scalar::Scalar>>
From<u64>>

The problems with these constructors are:

  • The first constructor requires a Scalar type, which must match the exact same k256 version that vsss_rs uses (currently 0.11, but 0.12 is out). Else, we have this problem:

    error[E0277]: the trait bound `vsss_rs::secp256k1::WrappedScalar: From<Scalar>` is not satisfied
    ...
      = help: the following other types implement trait `From<T>`:
                <vsss_rs::secp256k1::WrappedScalar as From<k256::arithmetic::scalar::Scalar>>
                <vsss_rs::secp256k1::WrappedScalar as From<u64>>
      = note: required for `Scalar` to implement `Into<vsss_rs::secp256k1::WrappedScalar>`
    
  • The second constructor will not work with 256-bit keys, if I understand correctly.

There also some other constructors (e.g., from_be_bytes_reduced), but it's not clear how one can use them (not a fault of this repo, the elliptic_curves is a bit lacking in this regard).

Why is the above a problem? Consider someone running the examples in this repo:

use k256::{NonZeroScalar, SecretKey};
[...]

let sk = SecretKey::random(&mut osrng);
let secret = WrappedScalar(*sk.to_nonzero_scalar());

Rust will complain that the Scalar type that vsss_rs expects (0.11) is different from the provided k256::Scalar type (0.12). The only way I've found to circumvent this limitation is to use an older version of k256 (0.11), specifically for importing a k256::SecretKey, which will produce a proper k256::Scalar type (0.11).

This is of course a bit esoteric, and prone to breakage. A better suggestion would be to re-export k256::SecretKey from this module (as k256 already does for the elliptic_curves::SecretKey), and let library consumers use that.

Do you agree, do you perhaps have a better suggestion?

FeldmanVerifier::verify() panics if given a malformed Share

Hi! I was testing vsss-rs with arbitrary Shares, and expected this to pass (i.e., passing an empty share should not verify):

    let sk = SecretKey::random(&mut osrng);
    let nzs = sk.to_secret_scalar();
    let (_shares, verifier): (_, FeldmanVerifier<Scalar, ProjectivePoint>) = Feldman { t: 3, n: 5 }
        .split_secret(*nzs, None, &mut osrng)
        .unwrap();

    assert!(!verifier.verify(&Share(vec![])));

but it panics with an out of bounds slice access:

thread 'main' panicked at 'range start index 1 out of range for slice of length 0', /home/john/.cargo/registry/src/github.com-1ecc6299db9ec823/vsss-rs-2.0.0-pre2/src/standard/share.rs:60:10
stack backtrace:
   0: rust_begin_unwind
             at /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/std/src/panicking.rs:498:5
   1: core::panicking::panic_fmt
             at /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/panicking.rs:116:14
   2: core::slice::index::slice_start_index_len_fail
             at /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/slice/index.rs:35:5
   3: <core::ops::range::RangeFrom<usize> as core::slice::index::SliceIndex<[T]>>::index
             at /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/slice/index.rs:329:13
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/slice/index.rs:15:9
   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
             at /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/alloc/src/vec/mod.rs:2520:9
   6: vsss_rs::standard::share::Share::value
             at /home/john/.cargo/registry/src/github.com-1ecc6299db9ec823/vsss-rs-2.0.0-pre2/src/standard/share.rs:60:10
   7: vsss_rs::standard::verifier::feldman::FeldmanVerifier<F,G>::verify
             at /home/john/.cargo/registry/src/github.com-1ecc6299db9ec823/vsss-rs-2.0.0-pre2/src/standard/verifier/feldman.rs:86:37
   8: vsss_rs_panic::main
             at ./src/main.rs:15:14
   9: core::ops::function::FnOnce::call_once
             at /rustc/9d1b2106e23b1abd32fce1f17267604a5102f57a/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

It looks like Share's implementation assumes the vec it wraps is at least 1 byte long in all three of these methods. I could add a check that the vec isn't empty before creating the share, but it doesn't seem ideal that one can create a Share (using either the pub vec like I did above, or via the TryFrom<&[u8]> that always succeeds) that will then panic if used.

Package does not build with ff 0.10

ff 0.10 depends on funty version 1.2, which has been yanked recently. So, the package does not build. Tried updating versions of all packages, but does not compile.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.