1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct DecryptionShare {
12 pub party_idx: u32,
13 pub share: Vec<u8>,
15}
16
17#[derive(Debug)]
19pub struct DecryptionSession {
20 pub session_id: String,
21 pub threshold: u32,
22 pub party_count: u32,
23 pub ciphertext: Vec<u8>,
24 pub shares: HashMap<u32, DecryptionShare>,
25}
26
27impl DecryptionSession {
28 pub fn new(session_id: &str, threshold: u32, party_count: u32, ciphertext: &[u8]) -> Self {
29 Self {
30 session_id: session_id.into(),
31 threshold,
32 party_count,
33 ciphertext: ciphertext.to_vec(),
34 shares: HashMap::new(),
35 }
36 }
37
38 pub fn submit_share(&mut self, share: DecryptionShare) -> Result<(), String> {
40 if share.party_idx == 0 || share.party_idx > self.party_count {
41 return Err(format!("invalid party_idx: {}", share.party_idx));
42 }
43 if self.shares.contains_key(&share.party_idx) {
44 return Err(format!("party {} already submitted", share.party_idx));
45 }
46 self.shares.insert(share.party_idx, share);
47 Ok(())
48 }
49
50 pub fn is_ready(&self) -> bool {
52 self.shares.len() >= self.threshold as usize
53 }
54
55 pub fn share_count(&self) -> usize {
57 self.shares.len()
58 }
59
60 pub fn ordered_shares(&self) -> Vec<&DecryptionShare> {
62 let mut shares: Vec<&DecryptionShare> = self.shares.values().collect();
63 shares.sort_by_key(|s| s.party_idx);
64 shares
65 }
66
67 pub fn missing_parties(&self) -> Vec<u32> {
69 (1..=self.party_count)
70 .filter(|i| !self.shares.contains_key(i))
71 .collect()
72 }
73}
74
75#[derive(Debug, thiserror::Error)]
77pub enum DecryptionError {
78 #[error("insufficient shares: {have}/{need}")]
79 InsufficientShares { have: usize, need: u32 },
80 #[error("combination failed: {0}")]
81 CombinationFailed(String),
82}
83
84pub fn combine_shares(session: &DecryptionSession) -> Result<Vec<u8>, DecryptionError> {
89 if !session.is_ready() {
90 return Err(DecryptionError::InsufficientShares {
91 have: session.share_count(),
92 need: session.threshold,
93 });
94 }
95
96 let shares = session.ordered_shares();
97 let max_len = shares.iter().map(|s| s.share.len()).max().unwrap_or(0);
98 let mut result = vec![0u8; max_len];
99 for share in &shares {
100 for (i, &b) in share.share.iter().enumerate() {
101 result[i] ^= b;
102 }
103 }
104 Ok(result)
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 fn make_share(party_idx: u32, byte: u8) -> DecryptionShare {
112 DecryptionShare {
113 party_idx,
114 share: vec![byte; 32],
115 }
116 }
117
118 #[test]
119 fn new_session_empty() {
120 let session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
121 assert_eq!(session.share_count(), 0);
122 assert!(!session.is_ready());
123 }
124
125 #[test]
126 fn submit_share_increments_count() {
127 let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
128 session.submit_share(make_share(1, 0xAA)).unwrap();
129 assert_eq!(session.share_count(), 1);
130 }
131
132 #[test]
133 fn ready_at_threshold() {
134 let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
135 session.submit_share(make_share(1, 0xAA)).unwrap();
136 assert!(!session.is_ready());
137 session.submit_share(make_share(2, 0xBB)).unwrap();
138 assert!(session.is_ready());
139 }
140
141 #[test]
142 fn duplicate_submission_rejected() {
143 let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
144 session.submit_share(make_share(1, 0xAA)).unwrap();
145 assert!(session.submit_share(make_share(1, 0xBB)).is_err());
146 }
147
148 #[test]
149 fn invalid_party_idx_rejected() {
150 let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
151 assert!(session.submit_share(make_share(0, 0xAA)).is_err());
152 assert!(session.submit_share(make_share(4, 0xAA)).is_err());
153 }
154
155 #[test]
156 fn combine_requires_threshold() {
157 let session = DecryptionSession::new("s1", 3, 5, &[0; 32]);
158 assert!(combine_shares(&session).is_err());
159 }
160
161 #[test]
162 fn combine_xors_shares() {
163 let mut session = DecryptionSession::new("s1", 2, 3, &[0; 32]);
164 session.submit_share(make_share(1, 0xFF)).unwrap();
165 session.submit_share(make_share(2, 0x0F)).unwrap();
166 let result = combine_shares(&session).unwrap();
167 assert_eq!(result, vec![0xF0; 32]);
169 }
170
171 #[test]
172 fn ordered_shares_sorted() {
173 let mut session = DecryptionSession::new("s1", 3, 5, &[0; 32]);
174 session.submit_share(make_share(3, 0x33)).unwrap();
175 session.submit_share(make_share(1, 0x11)).unwrap();
176 session.submit_share(make_share(2, 0x22)).unwrap();
177 let ordered = session.ordered_shares();
178 assert_eq!(ordered[0].party_idx, 1);
179 assert_eq!(ordered[1].party_idx, 2);
180 assert_eq!(ordered[2].party_idx, 3);
181 }
182
183 #[test]
184 fn missing_parties_lists_gaps() {
185 let mut session = DecryptionSession::new("s1", 3, 5, &[0; 32]);
186 session.submit_share(make_share(1, 0xAA)).unwrap();
187 session.submit_share(make_share(3, 0xCC)).unwrap();
188 let missing = session.missing_parties();
189 assert_eq!(missing, vec![2, 4, 5]);
190 }
191}