use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecryptionShare {
pub party_idx: u32,
pub share: Vec<u8>,
}
#[derive(Debug)]
pub struct DecryptionSession {
pub session_id: String,
pub threshold: u32,
pub party_count: u32,
pub ciphertext: Vec<u8>,
pub shares: HashMap<u32, DecryptionShare>,
}
impl DecryptionSession {
pub fn new(session_id: &str, threshold: u32, party_count: u32, ciphertext: &[u8]) -> Self {
Self {
session_id: session_id.into(),
threshold,
party_count,
ciphertext: ciphertext.to_vec(),
shares: HashMap::new(),
}
}
pub fn submit_share(&mut self, share: DecryptionShare) -> Result<(), String> {
if share.party_idx == 0 || share.party_idx > self.party_count {
return Err(format!("invalid party_idx: {}", share.party_idx));
}
if self.shares.contains_key(&share.party_idx) {
return Err(format!("party {} already submitted", share.party_idx));
}
self.shares.insert(share.party_idx, share);
Ok(())
}
pub fn is_ready(&self) -> bool {
self.shares.len() >= self.threshold as usize
}
pub fn share_count(&self) -> usize {
self.shares.len()
}
pub fn ordered_shares(&self) -> Vec<&DecryptionShare> {
let mut shares: Vec<&DecryptionShare> = self.shares.values().collect();
shares.sort_by_key(|s| s.party_idx);
shares
}
pub fn missing_parties(&self) -> Vec<u32> {
(1..=self.party_count)
.filter(|i| !self.shares.contains_key(i))
.collect()
}
}
#[derive(Debug, thiserror::Error)]
pub enum DecryptionError {
#[error("insufficient shares: {have}/{need}")]
InsufficientShares { have: usize, need: u32 },
#[error("combination failed: {0}")]
CombinationFailed(String),
}
pub fn combine_shares(session: &DecryptionSession) -> Result<Vec<u8>, DecryptionError> {
if !session.is_ready() {
return Err(DecryptionError::InsufficientShares {
have: session.share_count(),
need: session.threshold,
});
}
let shares = session.ordered_shares();
let max_len = shares.iter().map(|s| s.share.len()).max().unwrap_or(0);
let mut result = vec![0u8; max_len];
for share in &shares {
for (i, &b) in share.share.iter().enumerate() {
result[i] ^= b;
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_share(party_idx: u32, byte: u8) -> DecryptionShare {
DecryptionShare {
party_idx,
share: vec![byte; 32],
}
}
#[test]
fn new_session_empty() {
let session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
assert_eq!(session.share_count(), 0);
assert!(!session.is_ready());
}
#[test]
fn submit_share_increments_count() {
let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
session.submit_share(make_share(1, 0xAA)).unwrap();
assert_eq!(session.share_count(), 1);
}
#[test]
fn ready_at_threshold() {
let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
session.submit_share(make_share(1, 0xAA)).unwrap();
assert!(!session.is_ready());
session.submit_share(make_share(2, 0xBB)).unwrap();
assert!(session.is_ready());
}
#[test]
fn duplicate_submission_rejected() {
let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
session.submit_share(make_share(1, 0xAA)).unwrap();
assert!(session.submit_share(make_share(1, 0xBB)).is_err());
}
#[test]
fn invalid_party_idx_rejected() {
let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
assert!(session.submit_share(make_share(0, 0xAA)).is_err());
assert!(session.submit_share(make_share(4, 0xAA)).is_err());
}
#[test]
fn combine_requires_threshold() {
let session = DecryptionSession::new("s1", 3, 5, &[0; 32]);
assert!(combine_shares(&session).is_err());
}
#[test]
fn combine_xors_shares() {
let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
session.submit_share(make_share(1, 0xFF)).unwrap();
session.submit_share(make_share(2, 0x0F)).unwrap();
let result = combine_shares(&session).unwrap();
assert_eq!(result, vec![0xF0; 32]);
}
#[test]
fn ordered_shares_sorted() {
let mut session = DecryptionSession::new("s1", 3, 5, &[0; 32]);
session.submit_share(make_share(3, 0x33)).unwrap();
session.submit_share(make_share(1, 0x11)).unwrap();
session.submit_share(make_share(2, 0x22)).unwrap();
let ordered = session.ordered_shares();
assert_eq!(ordered[0].party_idx, 1);
assert_eq!(ordered[1].party_idx, 2);
assert_eq!(ordered[2].party_idx, 3);
}
#[test]
fn missing_parties_lists_gaps() {
let mut session = DecryptionSession::new("s1", 3, 5, &[0; 32]);
session.submit_share(make_share(1, 0xAA)).unwrap();
session.submit_share(make_share(3, 0xCC)).unwrap();
let missing = session.missing_parties();
assert_eq!(missing, vec![2, 4, 5]);
}
}