Skip to content

Commit bd0ef25

Browse files
authored
Merge pull request #392 from EspressoSystems/ax/vess
feat: VESS implementation
2 parents cc8ddd8 + 56ebdae commit bd0ef25

File tree

13 files changed

+702
-131
lines changed

13 files changed

+702
-131
lines changed

Cargo.toml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ members = [
1010
"tests",
1111
"timeboost",
1212
"timeboost-builder",
13-
"timeboost-sequencer",
1413
"timeboost-crypto",
14+
"timeboost-proto",
15+
"timeboost-sequencer",
1516
"timeboost-types",
1617
"timeboost-utils",
17-
"timeboost-proto",
1818
"yapper",
1919
]
2020
resolver = "2"
@@ -24,11 +24,6 @@ version = "0.1.0"
2424
edition = "2024"
2525
rust-version = "1.85.0"
2626

27-
[profile.test]
28-
codegen-units = 16
29-
incremental = false
30-
opt-level = 3
31-
3227
[workspace.dependencies]
3328
aes-gcm = { version = "0.10.3" }
3429
alloy-chains = "0.2.0"
@@ -41,12 +36,12 @@ alloy-signer-local = "1.0.5"
4136
anyhow = "1.0.89"
4237
arbitrary = "1.4.1"
4338
arbtest = "0.3.2"
39+
ark-bls12-381 = "0.5"
4440
ark-bn254 = "0.5"
4541
ark-ec = "0.5"
4642
ark-ff = "0.5"
4743
ark-poly = "0.5"
4844
ark-secp256k1 = "0.5"
49-
ark-bls12-381 = "0.5"
5045
ark-serialize = { version = "0.5", features = ["derive"] }
5146
ark-std = { version = "0.5", default-features = false }
5247
arrayvec = "0.7.6"
@@ -81,21 +76,21 @@ prost = "0.13.5"
8176
quickcheck = "1.0.3"
8277
rand = "0.9"
8378
rayon = "1.10"
84-
secp256k1 = { version = "0.31.0", features = ["global-context", "hashes", "rand", "serde"] }
8579
reqwest = { version = "0.12" }
80+
secp256k1 = { version = "0.31.0", features = ["global-context", "hashes", "rand", "serde"] }
8681
serde = { version = "1", features = ["derive", "rc"] }
8782
serde_bytes = "0.11.15"
8883
serde_json = { version = "1.0" }
8984
serde_with = "3.12.0"
9085
sha2 = { version = "0.10", default-features = false }
9186
sha3 = "0.10.8"
87+
smallvec = "1.15.1"
9288
snow = "0.9.6"
9389
spongefish = { git = "https://github.com/arkworks-rs/spongefish.git", rev = "e9f7031", features = [
9490
"arkworks-algebra",
9591
] }
9692
thiserror = "2.0"
9793
tide-disco = "0.9.3"
98-
smallvec = "1.15.1"
9994
tokio = { version = "1", default-features = false, features = ["full"] }
10095
tokio-stream = "0.1.17"
10196
tokio-util = "0.7.15"
@@ -107,3 +102,8 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] }
107102
turmoil = "0.6.4"
108103
vbs = "0.1"
109104
zeroize = { version = "1.8", features = ["zeroize_derive"] }
105+
106+
[profile.test]
107+
codegen-units = 16
108+
incremental = false
109+
opt-level = 3

tests/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ timeboost-utils = { path = "../timeboost-utils", features = ["test"] }
2828
tokio = { workspace = true }
2929
tokio-stream = { workspace = true }
3030
tokio-util = { workspace = true }
31-
turmoil = { workspace = true }
3231
tracing = { workspace = true }
32+
turmoil = { workspace = true }

timeboost-builder/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ rust-version.workspace = true
77

88
[dependencies]
99
bincode = { workspace = true }
10-
committable = { workspace = true }
1110
bon = { workspace = true }
1211
bytes = { workspace = true }
1312
cliquenet = { path = "../cliquenet" }
13+
committable = { workspace = true }
1414
metrics = { path = "../metrics" }
1515
multisig = { path = "../multisig" }
1616
serde = { workspace = true }
17+
smallvec = { workspace = true }
1718
thiserror = { workspace = true }
1819
timeboost-proto = { path = "../timeboost-proto" }
1920
timeboost-types = { path = "../timeboost-types" }
20-
smallvec = { workspace = true }
2121
tokio = { workspace = true }
2222
tracing = { workspace = true }

timeboost-crypto/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ rust-version.workspace = true
99
aes-gcm = { workspace = true }
1010
alloy-rlp = { workspace = true }
1111
anyhow = { workspace = true }
12-
ark-ec = { workspace = true }
1312
ark-bls12-381 = { workspace = true }
13+
ark-ec = { workspace = true }
1414
ark-ff = { workspace = true }
1515
ark-poly = { workspace = true }
1616
ark-serialize = { workspace = true }
@@ -22,6 +22,10 @@ committable = { workspace = true }
2222
digest = { workspace = true }
2323
generic-array = { workspace = true }
2424
multisig = { path = "../multisig" }
25+
num-integer = "0.1"
26+
# sadly, ark_std depends on [email protected], while [email protected] depends on [email protected]
27+
rand_chacha = "0.3.1"
28+
rayon = { workspace = true }
2529
serde = { workspace = true }
2630
serde_with = { workspace = true }
2731
sha2 = { workspace = true }
@@ -30,8 +34,8 @@ thiserror = { workspace = true }
3034
zeroize = { workspace = true }
3135

3236
[dev-dependencies]
33-
ark-secp256k1 = { workspace = true }
3437
ark-bn254 = { workspace = true }
38+
ark-secp256k1 = { workspace = true }
3539
criterion = { workspace = true }
3640

3741
[[bench]]

timeboost-crypto/src/feldman.rs

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use ark_ec::CurveGroup;
44
use ark_poly::{DenseUVPolynomial, Polynomial, univariate::DensePolynomial};
5+
use ark_serialize::serialize_to_vec;
56
use ark_std::marker::PhantomData;
67
use ark_std::rand::Rng;
78
use std::{iter::successors, num::NonZeroU32};
@@ -29,6 +30,44 @@ impl FeldmanVssPublicParam {
2930
}
3031
}
3132

33+
impl<C: CurveGroup> FeldmanVss<C> {
34+
/// sample a random polynomial for VSS `secret`, returns the poly and its feldman commitment
35+
pub(crate) fn rand_poly_and_commit<R: Rng>(
36+
pp: &FeldmanVssPublicParam,
37+
secret: C::ScalarField,
38+
rng: &mut R,
39+
) -> (DensePolynomial<C::ScalarField>, Vec<C::Affine>) {
40+
// sample random polynomial of degree t-1 (s.t. any t evaluations can interpolate this poly)
41+
// f(X) = Sum a_i * X^i
42+
let mut poly = DensePolynomial::<C::ScalarField>::rand(pp.t.get() as usize - 1, rng);
43+
// f(0) = a_0 set to the secret, this index access will never panic since t>0
44+
poly.coeffs[0] = secret;
45+
46+
// prepare commitment, u = (g^a_0, g^a_1, ..., g^a_t-1)
47+
let commitment = C::generator().batch_mul(&poly.coeffs);
48+
49+
(poly, commitment)
50+
}
51+
52+
/// given a secret-embedded polynomial, compute the Shamir secret shares
53+
/// node i \in {0,.. ,n-1} get f(i+1)
54+
pub(crate) fn compute_shares(
55+
pp: &FeldmanVssPublicParam,
56+
poly: &DensePolynomial<C::ScalarField>,
57+
) -> impl Iterator<Item = C::ScalarField> {
58+
(0..pp.n.get()).map(|node_idx| poly.evaluate(&(node_idx + 1).into()))
59+
}
60+
61+
/// same as [`Self::compute_shares()`], but output an iterator of bytes
62+
pub(crate) fn compute_serialized_shares(
63+
pp: &FeldmanVssPublicParam,
64+
poly: &DensePolynomial<C::ScalarField>,
65+
) -> impl Iterator<Item = Vec<u8>> {
66+
Self::compute_shares(pp, poly)
67+
.map(|s| serialize_to_vec![s].expect("ark_serialize valid shares never panic"))
68+
}
69+
}
70+
3271
impl<C: CurveGroup> VerifiableSecretSharing for FeldmanVss<C> {
3372
type PublicParam = FeldmanVssPublicParam;
3473
type Secret = C::ScalarField;
@@ -40,29 +79,17 @@ impl<C: CurveGroup> VerifiableSecretSharing for FeldmanVss<C> {
4079
rng: &mut R,
4180
secret: Self::Secret,
4281
) -> (Vec<Self::SecretShare>, Self::Commitment) {
43-
// sample random polynomial of degree t-1 (s.t. any t evaluations can interpolate this poly)
44-
// f(X) = Sum a_i * X^i
45-
let mut poly = DensePolynomial::<Self::Secret>::rand(pp.t.get() as usize - 1, rng);
46-
// f(0) = a_0 set to the secret, this index access will never panic since t>0
47-
poly.coeffs[0] = secret;
48-
49-
// prepare shares, node i \in {0,.. ,n-1} get f(i+1)
50-
let shares: Vec<Self::SecretShare> = (0..pp.n.get())
51-
.map(|node_idx| poly.evaluate(&(node_idx + 1).into()))
52-
.collect();
53-
54-
// prepare commitment, u = (g^a_0, g^a_1, ..., g^a_t-1)
55-
let commitment = C::generator().batch_mul(&poly.coeffs);
56-
57-
(shares, commitment)
82+
let (poly, comm) = Self::rand_poly_and_commit(pp, secret, rng);
83+
let shares = Self::compute_shares(pp, &poly).collect();
84+
(shares, comm)
5885
}
5986

6087
fn verify(
6188
pp: &Self::PublicParam,
6289
node_idx: usize,
6390
share: &Self::SecretShare,
6491
commitment: &Self::Commitment,
65-
) -> Result<bool, VssError> {
92+
) -> Result<(), VssError> {
6693
let n = pp.n.get() as usize;
6794
let t = pp.t.get() as usize;
6895

@@ -86,7 +113,11 @@ impl<C: CurveGroup> VerifiableSecretSharing for FeldmanVss<C> {
86113
VssError::InternalError("commitments and powers mismatched length".to_string())
87114
})?;
88115

89-
Ok(C::generator().mul(share) == eval_in_exp)
116+
if C::generator().mul(share) == eval_in_exp {
117+
Ok(())
118+
} else {
119+
Err(VssError::FailedVerification)
120+
}
90121
}
91122

92123
fn reconstruct(
@@ -140,29 +171,27 @@ mod tests {
140171
let (shares, commitment) = FeldmanVss::<C>::share(&pp, rng, secret);
141172
for (node_idx, s) in shares.iter().enumerate() {
142173
// happy path
143-
assert!(FeldmanVss::<C>::verify(&pp, node_idx, s, &commitment).unwrap());
174+
assert!(FeldmanVss::<C>::verify(&pp, node_idx, s, &commitment).is_ok());
144175

145176
// sad path
146177
// wrong node_idx should fail
147-
assert!(
148-
!FeldmanVss::<C>::verify(&pp, node_idx + 1, s, &commitment).unwrap_or(false)
149-
);
178+
assert!(FeldmanVss::<C>::verify(&pp, node_idx + 1, s, &commitment).is_err());
150179

151180
// wrong secret share should fail
152181
assert!(
153-
!FeldmanVss::<C>::verify(
182+
FeldmanVss::<C>::verify(
154183
&pp,
155184
node_idx,
156185
&C::ScalarField::rand(rng),
157186
&commitment,
158187
)
159-
.unwrap()
188+
.is_err()
160189
);
161190

162191
// wrong commitment should fail
163192
let mut bad_comm = commitment.clone();
164193
bad_comm[1] = C::Affine::default();
165-
assert!(!FeldmanVss::<C>::verify(&pp, node_idx, s, &bad_comm).unwrap());
194+
assert!(FeldmanVss::<C>::verify(&pp, node_idx, s, &bad_comm).is_err());
166195

167196
// incomplete/dropped commitment should fail
168197
bad_comm.pop();

timeboost-crypto/src/mre.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,18 @@ impl<C: CurveGroup> LabeledDecryptionKey<C> {
121121
}
122122
}
123123

124+
use crate::try_from_bytes;
125+
124126
/// Ciphertext for multiple recipients in MRE scheme
127+
#[serde_as]
125128
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129+
#[serde(bound = "H: Digest")]
126130
pub struct MultiRecvCiphertext<C: CurveGroup, H: Digest = sha2::Sha256> {
127131
// the shared ephemeral public key (v:=g^beta in the paper)
128-
epk: C::Affine,
132+
#[serde_as(as = "crate::SerdeAs")]
133+
pub(crate) epk: C::Affine,
129134
// individual ciphertexts (e_i in the paper)
130-
cts: Vec<Output<H>>,
135+
pub(crate) cts: Vec<Output<H>>,
131136
}
132137

133138
impl<C: CurveGroup, H: Digest> MultiRecvCiphertext<C, H> {
@@ -138,6 +143,15 @@ impl<C: CurveGroup, H: Digest> MultiRecvCiphertext<C, H> {
138143
ct: ct.clone(),
139144
})
140145
}
146+
147+
pub fn to_bytes(&self) -> Vec<u8> {
148+
bincode::serde::encode_to_vec(self, bincode::config::standard())
149+
.expect("serializing mre ciphertext")
150+
}
151+
152+
pub fn try_from_bytes<const N: usize>(value: &[u8]) -> Result<Self, SerializationError> {
153+
try_from_bytes::<Self, N>(value)
154+
}
141155
}
142156

143157
/// (Part of) [`MultiRecvCiphertext`] for a specific recipient.

timeboost-crypto/src/traits/dkg.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub trait VerifiableSecretSharing {
2626
fn share<R: Rng>(
2727
pp: &Self::PublicParam,
2828
rng: &mut R,
29+
// TODO(alex): consider accept Zeroizing<Self::Secret> instead
2930
secret: Self::Secret,
3031
) -> (Vec<Self::SecretShare>, Self::Commitment);
3132

@@ -35,13 +36,13 @@ pub trait VerifiableSecretSharing {
3536
/// - `share`: the secret share to verify
3637
/// - `commitment`: the global commitment (if any)
3738
///
38-
/// Returns Ok(true) if valid, Ok(false) if invalid, or an appropriate `VssError` otherwise.
39+
/// Returns Ok(()) if valid, or an appropriate `VssError` otherwise.
3940
fn verify(
4041
pp: &Self::PublicParam,
4142
node_idx: usize,
4243
share: &Self::SecretShare,
4344
commitment: &Self::Commitment,
44-
) -> Result<bool, VssError>;
45+
) -> Result<(), VssError>;
4546

4647
/// Reconstructs the original secret from a set of (index, share) pairs.
4748
///
@@ -65,6 +66,8 @@ pub enum VssError {
6566
InvalidShare(usize, String),
6667
#[error("invalid VSS commitment")]
6768
InvalidCommitment,
69+
#[error("failed verification: share does not match commitment")]
70+
FailedVerification,
6871
#[error("failed to reconstruct: {0}")]
6972
FailedReconstruction(String),
7073
#[error("internal err: {0}")]

0 commit comments

Comments
 (0)