use crate::field::PrimeField;
use crate::primes::{gcd, mod_inverse};
use crate::bigint::BigUint;
use crate::secure::ct_eq_biguint;
#[derive(Clone, Debug)]
pub struct MignotteSequence {
moduli: Vec<BigUint>,
k: usize,
alpha: BigUint,
beta: BigUint,
pair_inv: Option<Vec<Vec<BigUint>>>,
}
pub const CRT_PRECOMP_THRESHOLD_BITS: usize = 128;
#[derive(Clone, Eq, PartialEq)]
pub struct Share {
pub index: usize,
pub residue: BigUint,
}
impl core::fmt::Debug for Share {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Share(<elided>)")
}
}
impl MignotteSequence {
#[must_use]
pub fn new(moduli: Vec<BigUint>, k: usize) -> Option<Self> {
let n = moduli.len();
if k < 2 || k > n {
return None;
}
for i in 1..n {
if moduli[i - 1] >= moduli[i] {
return None;
}
}
for i in 0..n {
for j in (i + 1)..n {
if gcd(&moduli[i], &moduli[j]) != BigUint::one() {
return None;
}
}
}
let alpha = product(&moduli[n - (k - 1)..]);
let beta = product(&moduli[..k]);
if alpha >= beta {
return None;
}
let max_bits = moduli.iter().map(BigUint::bits).max().unwrap_or(0);
let pair_inv = if max_bits >= CRT_PRECOMP_THRESHOLD_BITS {
let mut table: Vec<Vec<BigUint>> = Vec::with_capacity(n);
for i in 0..n {
let mut row = Vec::with_capacity(n);
for j in 0..n {
if i == j {
row.push(BigUint::zero());
} else {
let m_j_mod_m_i = moduli[j].modulo(&moduli[i]);
let inv = mod_inverse(&m_j_mod_m_i, &moduli[i])?;
row.push(inv);
}
}
table.push(row);
}
Some(table)
} else {
None
};
Some(Self {
moduli,
k,
alpha,
beta,
pair_inv,
})
}
#[must_use]
pub fn k(&self) -> usize {
self.k
}
#[must_use]
pub fn n(&self) -> usize {
self.moduli.len()
}
#[must_use]
pub fn moduli(&self) -> &[BigUint] {
&self.moduli
}
#[must_use]
pub fn alpha(&self) -> &BigUint {
&self.alpha
}
#[must_use]
pub fn beta(&self) -> &BigUint {
&self.beta
}
}
#[must_use]
pub fn split(seq: &MignotteSequence, secret: &BigUint) -> Vec<Share> {
assert!(
secret > &seq.alpha && secret < &seq.beta,
"secret must lie strictly inside (α, β)"
);
seq.moduli
.iter()
.enumerate()
.map(|(i, m)| Share {
index: i + 1,
residue: secret.modulo(m),
})
.collect()
}
#[must_use]
pub fn reconstruct(seq: &MignotteSequence, shares: &[Share]) -> Option<BigUint> {
let k = seq.k;
if shares.len() < k {
return None;
}
for s in shares {
if s.index == 0 || s.index > seq.n() {
return None;
}
if s.residue >= seq.moduli[s.index - 1] {
return None;
}
}
for i in 0..shares.len() {
for j in (i + 1)..shares.len() {
if shares[i].index == shares[j].index {
return None;
}
}
}
let used = &shares[..k];
let (mut x, mut prod) = (BigUint::zero(), BigUint::one());
let mut first = true;
let mut folded_indices: Vec<usize> = Vec::with_capacity(k);
for s in used {
let m_i_idx = s.index - 1;
let m = &seq.moduli[m_i_idx];
if first {
x = s.residue.clone();
prod = m.clone();
folded_indices.push(m_i_idx);
first = false;
continue;
}
let inv = if let Some(pair_inv) = &seq.pair_inv {
let mut acc = BigUint::one();
for &j in &folded_indices {
debug_assert!(
j != m_i_idx,
"fold step would self-multiply pair_inv diagonal",
);
acc = BigUint::mod_mul(&acc, &pair_inv[m_i_idx][j], m);
}
acc
} else {
mod_inverse(&prod.modulo(m), m)?
};
let x_mod_m = x.modulo(m);
let diff = if s.residue >= x_mod_m {
s.residue.sub_ref(&x_mod_m)
} else {
s.residue.add_ref(m).sub_ref(&x_mod_m)
};
let t = BigUint::mod_mul(&diff, &inv, m);
x = x.add_ref(&prod.mul_ref(&t));
prod = prod.mul_ref(m);
folded_indices.push(m_i_idx);
}
let secret = x.modulo(&prod);
if secret <= seq.alpha || secret >= seq.beta {
return None;
}
for s in &shares[k..] {
let m = &seq.moduli[s.index - 1];
let pred = secret.modulo(m);
if !ct_eq_biguint(&pred, &s.residue) {
return None;
}
}
Some(secret)
}
fn product(values: &[BigUint]) -> BigUint {
let mut acc = BigUint::one();
for v in values {
acc = acc.mul_ref(v);
}
acc
}
#[must_use]
pub fn small_example_3_of_5() -> MignotteSequence {
let moduli = [11u64, 13, 17, 19, 23]
.into_iter()
.map(BigUint::from_u64)
.collect();
MignotteSequence::new(moduli, 3).expect("hand-validated small Mignotte sequence")
}
#[allow(dead_code)]
type _UnusedField = PrimeField;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn small_example_round_trip() {
let seq = small_example_3_of_5();
let secret = BigUint::from_u64(1000);
let shares = split(&seq, &secret);
assert_eq!(shares.len(), 5);
assert_eq!(reconstruct(&seq, &shares[..3]), Some(secret.clone()));
assert_eq!(reconstruct(&seq, &shares[1..4]), Some(secret.clone()));
assert_eq!(reconstruct(&seq, &shares[2..]), Some(secret));
}
#[test]
fn extra_shares_consistent() {
let seq = small_example_3_of_5();
let secret = BigUint::from_u64(2000);
let shares = split(&seq, &secret);
assert_eq!(reconstruct(&seq, &shares), Some(secret));
}
#[test]
fn tampered_extra_share_rejected() {
let seq = small_example_3_of_5();
let secret = BigUint::from_u64(1500);
let mut shares = split(&seq, &secret);
shares[4].residue = shares[4].residue.add_ref(&BigUint::one());
assert!(reconstruct(&seq, &shares).is_none());
}
#[test]
fn below_threshold_returns_none() {
let seq = small_example_3_of_5();
let secret = BigUint::from_u64(800);
let shares = split(&seq, &secret);
assert!(reconstruct(&seq, &shares[..2]).is_none());
}
#[test]
fn duplicate_share_rejected() {
let seq = small_example_3_of_5();
let secret = BigUint::from_u64(1234);
let mut shares = split(&seq, &secret);
shares[1] = shares[0].clone();
assert!(reconstruct(&seq, &shares[..3]).is_none());
}
#[test]
#[should_panic(expected = "strictly inside (α, β)")]
fn split_rejects_secret_below_alpha() {
let seq = small_example_3_of_5();
let _ = split(&seq, &BigUint::from_u64(100));
}
#[test]
#[should_panic(expected = "strictly inside (α, β)")]
fn split_rejects_secret_at_or_above_beta() {
let seq = small_example_3_of_5();
let _ = split(&seq, &BigUint::from_u64(2431));
}
#[test]
fn rejects_non_coprime_sequence() {
let m = vec![
BigUint::from_u64(5),
BigUint::from_u64(6),
BigUint::from_u64(9),
BigUint::from_u64(11),
];
assert!(MignotteSequence::new(m, 2).is_none());
}
#[test]
fn rejects_non_increasing_sequence() {
let m = vec![
BigUint::from_u64(11),
BigUint::from_u64(7),
BigUint::from_u64(13),
BigUint::from_u64(17),
];
assert!(MignotteSequence::new(m, 2).is_none());
}
#[test]
fn rejects_when_alpha_geq_beta() {
let m = vec![
BigUint::from_u64(2),
BigUint::from_u64(3),
BigUint::from_u64(7),
];
assert!(MignotteSequence::new(m, 2).is_none());
}
#[test]
fn first_k_tamper_outside_range_rejected() {
let seq = small_example_3_of_5();
let secret = BigUint::from_u64(1234);
let mut shares = split(&seq, &secret);
for delta in 1..10u64 {
let mut bad = shares.clone();
bad[0].residue =
bad[0]
.residue
.add_ref(&BigUint::from_u64(delta))
.modulo(&seq.moduli[0]);
let got = reconstruct(&seq, &bad[..3]);
assert_ne!(got.as_ref(), Some(&secret));
}
let _ = &mut shares;
}
#[test]
fn larger_sequence_round_trip() {
let primes = [11u64, 13, 17, 19, 23, 29, 31];
let moduli: Vec<BigUint> = primes.iter().copied().map(BigUint::from_u64).collect();
let seq = MignotteSequence::new(moduli, 4).expect("valid (4,7) Mignotte");
let secret = (seq.alpha().clone()).add_ref(&BigUint::from_u64(12345));
assert!(secret < *seq.beta(), "secret must fit in space");
let shares = split(&seq, &secret);
assert_eq!(reconstruct(&seq, &shares[..4]), Some(secret.clone()));
assert_eq!(reconstruct(&seq, &shares[3..7]), Some(secret.clone()));
let picked: Vec<Share> = vec![
shares[0].clone(),
shares[2].clone(),
shares[4].clone(),
shares[6].clone(),
];
assert_eq!(reconstruct(&seq, &picked), Some(secret));
}
fn large_example_3_of_5() -> MignotteSequence {
use crate::csprng::ChaCha20Rng;
use crate::primes::{is_probable_prime, random_below};
let mut rng = ChaCha20Rng::from_seed(&[0xA1u8; 32]);
let lo = {
let mut v = BigUint::one();
v.shl_bits(130);
v
};
let span = {
let mut v = BigUint::one();
v.shl_bits(130);
v
};
let mut found: Vec<BigUint> = Vec::new();
while found.len() < 5 {
let mut candidate = random_below(&mut rng, &span).expect("span > 0");
candidate = candidate.add_ref(&lo);
if !candidate.is_odd() {
continue;
}
if is_probable_prime(&candidate) && !found.contains(&candidate) {
found.push(candidate);
}
}
found.sort();
MignotteSequence::new(found, 3).expect("constructed 130-bit Mignotte sequence")
}
#[test]
fn small_example_skips_precomp() {
let seq = small_example_3_of_5();
assert!(seq.pair_inv.is_none(), "small moduli should skip CRT precomp");
}
#[test]
fn large_example_uses_precomp() {
let seq = large_example_3_of_5();
assert!(seq.pair_inv.is_some(), "large moduli should populate CRT precomp");
let table = seq.pair_inv.as_ref().unwrap();
assert_eq!(table.len(), 5);
for row in table {
assert_eq!(row.len(), 5);
}
}
#[test]
fn large_example_round_trip_via_precomp() {
let seq = large_example_3_of_5();
let alpha = seq.alpha().clone();
let beta = seq.beta().clone();
let secret = alpha.add_ref(&BigUint::from_u64(7));
assert!(secret < beta, "secret must lie in (α, β)");
let shares = split(&seq, &secret);
for window_start in 0..=2 {
let used = &shares[window_start..window_start + 3];
assert_eq!(
reconstruct(&seq, used),
Some(secret.clone()),
"precomp branch failed for shares[{}..{}]",
window_start,
window_start + 3,
);
}
}
}