use crate::bigint::BigUint;
use crate::csprng::Csprng;
use crate::field::PrimeField;
use crate::karchmer_wigderson::{self, SpanProgram};
#[derive(Clone, Debug)]
pub struct Scheme {
inner: SpanProgram,
}
impl Scheme {
#[must_use]
pub fn new(field: PrimeField, vectors: Vec<Vec<BigUint>>) -> Self {
assert!(!vectors.is_empty(), "need at least one player vector");
let m = vectors[0].len();
for v in &vectors {
assert_eq!(v.len(), m, "all vectors must have the same length m");
}
let labels: Vec<usize> = (1..=vectors.len()).collect();
Self {
inner: SpanProgram::new(field, vectors, labels),
}
}
#[must_use]
pub fn n(&self) -> usize {
self.inner.n()
}
#[must_use]
pub fn m(&self) -> usize {
self.inner.m()
}
#[must_use]
pub fn field(&self) -> &PrimeField {
self.inner.field()
}
#[must_use]
pub fn qualifies(&self, coalition: &[usize]) -> bool {
self.inner.qualifies(coalition)
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct Share {
pub player: usize,
pub value: BigUint,
}
impl core::fmt::Debug for Share {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Share(<elided>)")
}
}
#[must_use]
pub fn split<R: Csprng>(scheme: &Scheme, rng: &mut R, secret: &BigUint) -> Vec<Share> {
let inner_shares = karchmer_wigderson::split(&scheme.inner, rng, secret);
inner_shares
.into_iter()
.map(|ps| {
assert_eq!(
ps.fragments.len(),
1,
"Brickell invariant: one row per player",
);
Share {
player: ps.player,
value: ps.fragments.into_iter().next().unwrap().1,
}
})
.collect()
}
#[must_use]
pub fn reconstruct(scheme: &Scheme, shares: &[Share]) -> Option<BigUint> {
let inner: Vec<karchmer_wigderson::PlayerShare> = shares
.iter()
.map(|s| {
let row_idx = s.player.checked_sub(1)?;
Some(karchmer_wigderson::PlayerShare {
player: s.player,
fragments: vec![(row_idx, s.value.clone())],
})
})
.collect::<Option<Vec<_>>>()?;
karchmer_wigderson::reconstruct(&scheme.inner, &inner)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::csprng::ChaCha20Rng;
fn rng() -> ChaCha20Rng {
ChaCha20Rng::from_seed(&[0xB7u8; 32])
}
fn small() -> PrimeField {
PrimeField::new(BigUint::from_u64((1u64 << 61) - 1))
}
fn pick(shares: &[Share], wanted: &[usize]) -> Vec<Share> {
shares
.iter()
.filter(|s| wanted.contains(&s.player))
.cloned()
.collect()
}
#[test]
fn shamir_via_vandermonde_vectors() {
let f = small();
let k = 3;
let n = 5;
let vectors: Vec<Vec<BigUint>> = (1..=n)
.map(|j| {
let mut row = Vec::with_capacity(k);
let mut pow = BigUint::one();
let j_val = BigUint::from_u64(j as u64);
for _ in 0..k {
row.push(pow.clone());
pow = f.mul(&pow, &j_val);
}
row
})
.collect();
let scheme = Scheme::new(f, vectors);
let mut r = rng();
let secret = BigUint::from_u64(0xCAFE);
let shares = split(&scheme, &mut r, &secret);
for s in &shares {
let _ = s.value.clone();
}
for &(a, b, c) in &[(1usize, 2, 3), (1, 3, 5), (2, 4, 5)] {
assert_eq!(
reconstruct(&scheme, &pick(&shares, &[a, b, c])),
Some(secret.clone()),
"subset ({a},{b},{c})",
);
}
for &(a, b) in &[(1usize, 2), (3, 5)] {
assert!(reconstruct(&scheme, &pick(&shares, &[a, b])).is_none());
}
}
#[test]
fn non_threshold_ideal_structure() {
let f = small();
let v = vec![
vec![BigUint::one(), BigUint::zero()],
vec![BigUint::one(), BigUint::one()],
vec![BigUint::one(), BigUint::from_u64(2)],
vec![BigUint::zero(), BigUint::one()],
];
let scheme = Scheme::new(f, v);
for &q in &[
&[1usize][..],
&[2, 3],
&[2, 4],
&[3, 4],
&[1, 2],
&[1, 2, 3, 4],
] {
assert!(scheme.qualifies(q), "{q:?} should qualify");
}
for &uq in &[&[2usize][..], &[3], &[4], &[]] {
assert!(!scheme.qualifies(uq), "{uq:?} should NOT qualify");
}
let mut r = rng();
let secret = BigUint::from_u64(33);
let shares = split(&scheme, &mut r, &secret);
assert_eq!(reconstruct(&scheme, &pick(&shares, &[1])), Some(secret.clone()));
assert_eq!(reconstruct(&scheme, &pick(&shares, &[2, 3])), Some(secret.clone()));
assert!(reconstruct(&scheme, &pick(&shares, &[2])).is_none());
}
#[test]
fn duplicate_player_rejected() {
let f = small();
let v: Vec<Vec<BigUint>> = (1..=3)
.map(|j| vec![BigUint::one(), BigUint::from_u64(j as u64)])
.collect();
let scheme = Scheme::new(f, v);
let mut r = rng();
let secret = BigUint::from_u64(11);
let shares = split(&scheme, &mut r, &secret);
let dup = vec![shares[0].clone(), shares[0].clone()];
assert!(reconstruct(&scheme, &dup).is_none());
}
#[test]
fn duplicate_player_with_different_value_rejected() {
let f = small();
let v: Vec<Vec<BigUint>> = (1..=3)
.map(|j| vec![BigUint::one(), BigUint::from_u64(j as u64)])
.collect();
let scheme = Scheme::new(f.clone(), v);
let mut r = rng();
let secret = BigUint::from_u64(11);
let shares = split(&scheme, &mut r, &secret);
let mut twin = shares[0].clone();
twin.value = f.add(&twin.value, &BigUint::from_u64(1));
let bad = vec![shares[0].clone(), twin];
assert!(reconstruct(&scheme, &bad).is_none());
}
#[test]
fn out_of_range_player_rejected() {
let f = small();
let v: Vec<Vec<BigUint>> = (1..=2)
.map(|j| vec![BigUint::one(), BigUint::from_u64(j as u64)])
.collect();
let scheme = Scheme::new(f, v);
let bad = vec![
Share {
player: 0, value: BigUint::one(),
},
Share {
player: 1,
value: BigUint::one(),
},
];
assert!(reconstruct(&scheme, &bad).is_none());
}
#[test]
fn share_payload_is_one_field_element() {
let f = small();
let m = 7; let v: Vec<Vec<BigUint>> = (1..=4)
.map(|j| {
let mut row = vec![BigUint::zero(); m];
row[0] = BigUint::one();
row[1] = BigUint::from_u64(j as u64);
row
})
.collect();
let scheme = Scheme::new(f, v);
let mut r = rng();
let secret = BigUint::from_u64(7);
let shares = split(&scheme, &mut r, &secret);
assert_eq!(shares.len(), 4);
for s in &shares {
let _ = s.value.clone();
}
}
}