use crate::field::PrimeField;
use crate::bigint::BigUint;
use crate::csprng::Csprng;
#[derive(Clone, Debug)]
pub struct AccessStructure {
n: usize,
forbidden: Vec<Vec<usize>>,
}
impl AccessStructure {
#[must_use]
pub fn new(n: usize, mut forbidden: Vec<Vec<usize>>) -> Option<Self> {
if n == 0 || forbidden.is_empty() {
return None;
}
for f in &mut forbidden {
f.sort_unstable();
f.dedup();
if f.iter().any(|&j| j == 0 || j > n) {
return None;
}
if f.len() == n {
return None;
}
}
for i in 0..forbidden.len() {
for j in 0..forbidden.len() {
if i == j {
continue;
}
if is_subset(&forbidden[i], &forbidden[j]) {
return None;
}
}
}
Some(Self { n, forbidden })
}
#[must_use]
pub fn n(&self) -> usize {
self.n
}
#[must_use]
pub fn t(&self) -> usize {
self.forbidden.len()
}
#[must_use]
pub fn qualifies(&self, coalition: &[usize]) -> bool {
let mut sorted = coalition.to_vec();
sorted.sort_unstable();
sorted.dedup();
self.forbidden.iter().all(|f| !is_subset(&sorted, f))
}
}
fn is_subset(small: &[usize], large: &[usize]) -> bool {
let (mut i, mut j) = (0usize, 0usize);
while i < small.len() {
while j < large.len() && large[j] < small[i] {
j += 1;
}
if j == large.len() || large[j] != small[i] {
return false;
}
i += 1;
}
true
}
#[derive(Clone, Eq, PartialEq)]
pub struct PlayerShare {
pub player: usize,
pub parts: Vec<(usize, BigUint)>,
}
impl core::fmt::Debug for PlayerShare {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("PlayerShare(<elided>)")
}
}
#[must_use]
pub fn split<R: Csprng>(
field: &PrimeField,
rng: &mut R,
secret: &BigUint,
structure: &AccessStructure,
) -> Vec<PlayerShare> {
let t = structure.t();
let mut rs: Vec<BigUint> = Vec::with_capacity(t);
let mut sum = BigUint::zero();
for _ in 0..(t - 1) {
let v = field.random(rng);
sum = field.add(&sum, &v);
rs.push(v);
}
rs.push(field.sub(&field.reduce(secret), &sum));
(1..=structure.n())
.map(|player| {
let parts: Vec<(usize, BigUint)> = (0..t)
.filter(|&i| !structure.forbidden[i].contains(&player))
.map(|i| (i, rs[i].clone()))
.collect();
PlayerShare { player, parts }
})
.collect()
}
#[must_use]
pub fn reconstruct(
field: &PrimeField,
structure: &AccessStructure,
shares: &[PlayerShare],
) -> Option<BigUint> {
if shares.is_empty() {
return None;
}
let t = structure.t();
for i in 0..shares.len() {
for j in (i + 1)..shares.len() {
if shares[i].player == shares[j].player {
return None;
}
}
}
for s in shares {
if s.player == 0 || s.player > structure.n() {
return None;
}
for (i, _) in &s.parts {
if *i >= t {
return None;
}
if structure.forbidden[*i].contains(&s.player) {
return None;
}
}
}
let mut collected: Vec<Option<BigUint>> = vec![None; t];
for s in shares {
for (i, r) in &s.parts {
match &collected[*i] {
Some(prev) if prev != r => return None,
_ => collected[*i] = Some(r.clone()),
}
}
}
let mut sum = BigUint::zero();
for slot in &collected {
let v = slot.as_ref()?;
sum = field.add(&sum, v);
}
Some(sum)
}
#[must_use]
pub fn threshold_access_structure(n: usize, k: usize) -> AccessStructure {
assert!(k >= 2 && k <= n, "need 2 ≤ k ≤ n");
let mut combos: Vec<Vec<usize>> = Vec::new();
let mut cur = Vec::with_capacity(k - 1);
fn rec(start: usize, n: usize, want: usize, cur: &mut Vec<usize>, out: &mut Vec<Vec<usize>>) {
if cur.len() == want {
out.push(cur.clone());
return;
}
for j in start..=n {
cur.push(j);
rec(j + 1, n, want, cur, out);
cur.pop();
}
}
rec(1, n, k - 1, &mut cur, &mut combos);
AccessStructure::new(n, combos).expect("threshold structure is well-formed")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::csprng::ChaCha20Rng;
fn rng() -> ChaCha20Rng {
ChaCha20Rng::from_seed(&[0xA1u8; 32])
}
fn small() -> PrimeField {
PrimeField::new(BigUint::from_u64(65_537))
}
#[test]
fn two_of_three_threshold_via_isn() {
let f = small();
let mut r = rng();
let s = threshold_access_structure(3, 2);
let secret = BigUint::from_u64(42);
let shares = split(&f, &mut r, &secret, &s);
for sh in &shares {
assert_eq!(sh.parts.len(), 2);
}
for &(i, j) in &[(0usize, 1usize), (0, 2), (1, 2)] {
let pair = vec![shares[i].clone(), shares[j].clone()];
assert_eq!(reconstruct(&f, &s, &pair), Some(secret.clone()));
}
for sh in &shares {
let solo = vec![sh.clone()];
assert!(reconstruct(&f, &s, &solo).is_none());
}
}
#[test]
fn explicit_non_threshold_structure() {
let f = small();
let mut r = rng();
let structure = AccessStructure::new(
4,
vec![vec![1, 4], vec![2, 3], vec![2, 4]],
)
.unwrap();
assert!(structure.qualifies(&[1, 2]));
assert!(structure.qualifies(&[3, 4]));
assert!(structure.qualifies(&[1, 3]));
assert!(!structure.qualifies(&[1, 4]));
assert!(!structure.qualifies(&[2, 3]));
assert!(!structure.qualifies(&[2, 4]));
let secret = BigUint::from_u64(0xC0DE);
let shares = split(&f, &mut r, &secret, &structure);
for &(i, j) in &[(0usize, 1usize), (2, 3), (0, 2)] {
let pair = vec![shares[i].clone(), shares[j].clone()];
assert_eq!(reconstruct(&f, &structure, &pair), Some(secret.clone()));
}
for &(i, j) in &[(0usize, 3usize), (1, 2), (1, 3)] {
let pair = vec![shares[i].clone(), shares[j].clone()];
assert!(reconstruct(&f, &structure, &pair).is_none());
}
}
#[test]
fn rejects_subset_in_maximality_check() {
let res = AccessStructure::new(4, vec![vec![1, 2], vec![1, 2, 3]]);
assert!(res.is_none());
}
#[test]
fn rejects_full_party_set() {
let res = AccessStructure::new(3, vec![vec![1, 2, 3]]);
assert!(res.is_none());
}
#[test]
fn rejects_out_of_range_player() {
let res = AccessStructure::new(3, vec![vec![0, 1]]);
assert!(res.is_none());
let res = AccessStructure::new(3, vec![vec![1, 4]]);
assert!(res.is_none());
}
#[test]
fn tampered_subshare_with_overlap_is_rejected() {
let f = small();
let mut r = rng();
let s = threshold_access_structure(3, 2);
let secret = BigUint::from_u64(99);
let mut shares = split(&f, &mut r, &secret, &s);
shares[0].parts[0].1 = f.add(&shares[0].parts[0].1, &BigUint::from_u64(1));
let coalition = vec![shares[0].clone(), shares[1].clone(), shares[2].clone()];
assert!(reconstruct(&f, &s, &coalition).is_none());
}
#[test]
fn single_source_tamper_in_minimal_coalition_undetectable() {
let f = small();
let mut r = rng();
let s = threshold_access_structure(3, 2);
let secret = BigUint::from_u64(99);
let mut shares = split(&f, &mut r, &secret, &s);
shares[0].parts[0].1 = f.add(&shares[0].parts[0].1, &BigUint::from_u64(1));
let pair = vec![shares[0].clone(), shares[1].clone()];
let got = reconstruct(&f, &s, &pair).expect("reconstruction succeeds without overlap");
assert_ne!(got, secret);
}
#[test]
fn duplicate_player_rejected() {
let f = small();
let mut r = rng();
let s = threshold_access_structure(3, 2);
let secret = BigUint::from_u64(7);
let shares = split(&f, &mut r, &secret, &s);
let dup = vec![shares[0].clone(), shares[0].clone()];
assert!(reconstruct(&f, &s, &dup).is_none());
}
#[test]
fn fabricated_part_for_player_in_forbidden_set_rejected() {
let f = small();
let mut r = rng();
let s = threshold_access_structure(3, 2);
let secret = BigUint::from_u64(1);
let shares = split(&f, &mut r, &secret, &s);
let mut bad = shares[0].clone();
let i_bad = (0..s.t())
.find(|&i| s.forbidden[i].contains(&1))
.expect("player 1 is in some F_i");
bad.parts.push((i_bad, BigUint::from_u64(0)));
let pair = vec![bad, shares[1].clone()];
assert!(reconstruct(&f, &s, &pair).is_none());
}
}