use crate::field::PrimeField;
use crate::poly::{horner, lagrange_eval};
use crate::bigint::BigUint;
use crate::csprng::Csprng;
use crate::secure::{ct_eq_biguint, Zeroizing};
#[derive(Clone, Eq, PartialEq)]
pub struct VssShare {
pub player: usize,
pub g: Vec<BigUint>,
pub h: Vec<BigUint>,
}
impl core::fmt::Debug for VssShare {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("VssShare(<elided>)")
}
}
impl VssShare {
#[must_use]
pub fn eval_g(&self, field: &PrimeField, j: &BigUint) -> BigUint {
horner(field, &self.g, j)
}
#[must_use]
pub fn eval_h(&self, field: &PrimeField, j: &BigUint) -> BigUint {
horner(field, &self.h, j)
}
}
#[must_use]
pub fn deal<R: Csprng>(
field: &PrimeField,
rng: &mut R,
secret: &BigUint,
k: usize,
n: usize,
) -> Vec<VssShare> {
assert!(k >= 2, "k must be at least 2");
assert!(n >= k, "n must be at least k");
assert!(
BigUint::from_u64(n as u64) < *field.modulus(),
"prime modulus must exceed n",
);
let mut coeffs = Zeroizing::new(
(0..k)
.map(|_| (0..k).map(|_| field.random(rng)).collect::<Vec<BigUint>>())
.collect::<Vec<Vec<BigUint>>>(),
);
coeffs[0][0] = field.reduce(secret);
(1..=n)
.map(|i| {
let i_val = BigUint::from_u64(i as u64);
let mut g = vec![BigUint::zero(); k];
for b in 0..k {
let col: Vec<BigUint> = (0..k).map(|a| coeffs[a][b].clone()).collect();
g[b] = horner(field, &col, &i_val);
}
let mut h = vec![BigUint::zero(); k];
for a in 0..k {
h[a] = horner(field, &coeffs[a], &i_val);
}
VssShare { player: i, g, h }
})
.collect()
}
#[must_use]
pub fn cross_check(field: &PrimeField, share_i: &VssShare, share_j: &VssShare) -> bool {
if share_i.g.is_empty()
|| share_i.h.is_empty()
|| share_j.g.is_empty()
|| share_j.h.is_empty()
|| share_i.player == 0
|| share_j.player == 0
{
return false;
}
let i_val = BigUint::from_u64(share_i.player as u64);
let j_val = BigUint::from_u64(share_j.player as u64);
let lhs = share_i.eval_g(field, &j_val);
let rhs = share_j.eval_h(field, &i_val);
ct_eq_biguint(&lhs, &rhs)
}
#[must_use]
pub fn is_honest_majority(k: usize, n: usize) -> bool {
k >= 2 && 2 * (k - 1) < n
}
#[must_use]
pub fn deal_validated<R: Csprng>(
field: &PrimeField,
rng: &mut R,
secret: &BigUint,
k: usize,
n: usize,
) -> Vec<VssShare> {
assert!(
is_honest_majority(k, n),
"Rabin–Ben-Or VSS requires honest majority: 2(k − 1) < n",
);
deal(field, rng, secret, k, n)
}
#[must_use]
pub fn verify_consistent(field: &PrimeField, shares: &[VssShare]) -> bool {
for i in 0..shares.len() {
for j in 0..shares.len() {
if i == j {
continue;
}
if !cross_check(field, &shares[i], &shares[j]) {
return false;
}
}
}
true
}
#[must_use]
pub fn reconstruct(field: &PrimeField, shares: &[VssShare], k: usize) -> Option<BigUint> {
if k < 2 || shares.len() < k {
return None;
}
for s in shares {
if s.player == 0 || s.g.len() != k || s.h.len() != k {
return None;
}
}
for i in 0..shares.len() {
for j in (i + 1)..shares.len() {
if shares[i].player == shares[j].player {
return None;
}
}
}
if !verify_consistent(field, shares) {
return None;
}
let pts: Vec<(BigUint, BigUint)> = shares
.iter()
.take(k)
.map(|s| {
let x = BigUint::from_u64(s.player as u64);
let y = s.g[0].clone(); (x, y)
})
.collect();
lagrange_eval(field, &pts, &BigUint::zero())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::csprng::ChaCha20Rng;
fn rng() -> ChaCha20Rng {
ChaCha20Rng::from_seed(&[0x55u8; 32])
}
fn small() -> PrimeField {
PrimeField::new(BigUint::from_u64((1u64 << 61) - 1))
}
#[test]
fn round_trip() {
let f = small();
let mut r = rng();
let secret = BigUint::from_u64(0xC0FFEE);
let shares = deal(&f, &mut r, &secret, 3, 5);
assert_eq!(shares.len(), 5);
assert!(verify_consistent(&f, &shares));
assert_eq!(reconstruct(&f, &shares[..3], 3), Some(secret.clone()));
assert_eq!(reconstruct(&f, &shares[1..4], 3), Some(secret.clone()));
assert_eq!(reconstruct(&f, &shares[2..], 3), Some(secret));
}
#[test]
fn cross_check_detects_tamper() {
let f = small();
let mut r = rng();
let secret = BigUint::from_u64(42);
let mut shares = deal(&f, &mut r, &secret, 3, 5);
shares[0].g[1] = f.add(&shares[0].g[1], &BigUint::from_u64(1));
assert!(!cross_check(&f, &shares[0], &shares[1]));
assert!(reconstruct(&f, &shares[..3], 3).is_none());
}
#[test]
fn cross_check_detects_h_tamper() {
let f = small();
let mut r = rng();
let secret = BigUint::from_u64(7);
let mut shares = deal(&f, &mut r, &secret, 3, 5);
shares[2].h[2] = f.add(&shares[2].h[2], &BigUint::from_u64(1));
assert!(!verify_consistent(&f, &shares));
}
#[test]
fn consistency_holds_for_honest_dealer() {
let f = small();
let mut r = rng();
let secret = BigUint::from_u64(0xBEEF);
let shares = deal(&f, &mut r, &secret, 4, 7);
for i in 0..shares.len() {
for j in 0..shares.len() {
if i == j {
continue;
}
assert!(
cross_check(&f, &shares[i], &shares[j]),
"cross-check failed between players {} and {}",
shares[i].player,
shares[j].player
);
}
}
}
#[test]
fn below_threshold_returns_none() {
let f = small();
let mut r = rng();
let secret = BigUint::from_u64(13);
let shares = deal(&f, &mut r, &secret, 4, 6);
assert!(reconstruct(&f, &shares[..3], 4).is_none());
}
#[test]
fn duplicate_player_rejected() {
let f = small();
let mut r = rng();
let secret = BigUint::from_u64(11);
let shares = deal(&f, &mut r, &secret, 3, 5);
let dup = vec![shares[0].clone(), shares[0].clone(), shares[1].clone()];
assert!(reconstruct(&f, &dup, 3).is_none());
}
#[test]
fn malformed_share_returns_none() {
let f = small();
let mut r = rng();
let secret = BigUint::from_u64(3);
let mut shares = deal(&f, &mut r, &secret, 3, 4);
shares[0].g.push(BigUint::one()); assert!(reconstruct(&f, &shares[..3], 3).is_none());
}
#[test]
fn extra_shares_validated() {
let f = small();
let mut r = rng();
let secret = BigUint::from_u64(0xDEAD);
let shares = deal(&f, &mut r, &secret, 3, 6);
assert_eq!(reconstruct(&f, &shares, 3), Some(secret.clone()));
let mut bad = shares.clone();
bad[5].g[0] = f.add(&bad[5].g[0], &BigUint::from_u64(1));
assert!(reconstruct(&f, &bad, 3).is_none());
}
#[test]
fn honest_majority_helper_matches_paper_bound() {
assert!(is_honest_majority(2, 4)); assert!(is_honest_majority(2, 3)); assert!(is_honest_majority(3, 6)); assert!(!is_honest_majority(3, 4)); assert!(!is_honest_majority(4, 5)); assert!(!is_honest_majority(5, 5)); assert!(!is_honest_majority(1, 5)); }
#[test]
fn changing_secret_changes_g_i_zero() {
let f = small();
let mut r1 = rng();
let mut r2 = rng();
let s1 = BigUint::from_u64(100);
let s2 = BigUint::from_u64(200);
let a = deal(&f, &mut r1, &s1, 3, 5);
let b = deal(&f, &mut r2, &s2, 3, 5);
let any_diff = a.iter().zip(b.iter()).any(|(x, y)| x.g[0] != y.g[0]);
assert!(any_diff);
}
}