confium_coordinator/coordinator/
abort.rs1use crate::coordinator::coordinator::ThresholdSigner;
13use crate::coordinator::session::SignerId;
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone)]
18pub struct LabeledShare {
19 pub signer_id: SignerId,
21 pub bytes: Vec<u8>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct BlameReport {
28 pub session_id: String,
30 pub bad_signers: Vec<SignerId>,
32 pub all_individual_valid: bool,
35 pub error: String,
37}
38
39pub fn identify_bad_signer(
45 session_id: &str,
46 shares: &[LabeledShare],
47 threshold: u32,
48 message: &[u8],
49 scheme: &str,
50 signer: &dyn ThresholdSigner,
51) -> BlameReport {
52 if shares.is_empty() {
53 return BlameReport {
54 session_id: session_id.into(),
55 bad_signers: vec![],
56 all_individual_valid: false,
57 error: "no shares provided".into(),
58 };
59 }
60
61 let all_bytes: Vec<Vec<u8>> = shares.iter().map(|s| s.bytes.clone()).collect();
63 match signer.sign(scheme, &all_bytes, threshold, message) {
64 Ok(_) => BlameReport {
65 session_id: session_id.into(),
66 bad_signers: vec![],
67 all_individual_valid: true,
68 error: "aggregation succeeded — no bad signer".into(),
69 },
70 Err(e) => {
71 let error = format!("{e}");
72 let mut bad_signers = Vec::new();
73
74 for (i, _) in shares.iter().enumerate() {
76 let subset: Vec<Vec<u8>> = shares
77 .iter()
78 .enumerate()
79 .filter(|(j, _)| *j != i)
80 .map(|(_, s)| s.bytes.clone())
81 .collect();
82
83 if (threshold as usize) > subset.len() {
87 continue;
88 }
89
90 match signer.sign(scheme, &subset, threshold, message) {
91 Ok(_) => {
92 bad_signers.push(shares[i].signer_id.clone());
94 }
95 Err(_) => {
96 }
98 }
99 }
100
101 BlameReport {
102 session_id: session_id.into(),
103 bad_signers,
104 all_individual_valid: false,
105 error,
106 }
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::coordinator::coordinator::MockSigner;
115
116 fn make_share(id: &str, byte: u8) -> LabeledShare {
117 LabeledShare {
118 signer_id: id.into(),
119 bytes: vec![byte; 64],
120 }
121 }
122
123 #[test]
124 fn no_shares_returns_empty_report() {
125 let signer = MockSigner;
126 let report = identify_bad_signer("s1", &[], 2, b"msg", "CMP20", &signer);
127 assert!(report.bad_signers.is_empty());
128 assert!(report.error.contains("no shares"));
129 }
130
131 #[test]
132 fn all_valid_shares_no_blame() {
133 let signer = MockSigner;
134 let shares = vec![
135 make_share("alice", 0xAA),
136 make_share("bob", 0xBB),
137 make_share("carol", 0xCC),
138 ];
139 let report = identify_bad_signer("s1", &shares, 2, b"msg", "CMP20", &signer);
140 assert!(report.bad_signers.is_empty());
141 assert!(report.error.contains("succeeded"));
142 }
143
144 #[test]
145 fn empty_shares_list_handled() {
146 let signer = MockSigner;
147 let report = identify_bad_signer("s1", &[], 0, b"msg", "CMP20", &signer);
148 assert!(report.bad_signers.is_empty());
149 }
150
151 #[test]
152 fn blame_report_serializes() {
153 let report = BlameReport {
154 session_id: "s1".into(),
155 bad_signers: vec!["alice".into()],
156 all_individual_valid: false,
157 error: "aggregation failed".into(),
158 };
159 let json = serde_json::to_string(&report).unwrap();
160 assert!(json.contains("alice"));
161 assert!(json.contains("s1"));
162 }
163
164 #[test]
165 fn blame_report_deserializes() {
166 let json = r#"{"session_id":"s1","bad_signers":["bob"],"all_individual_valid":false,"error":"test"}"#;
167 let report: BlameReport = serde_json::from_str(json).unwrap();
168 assert_eq!(report.session_id, "s1");
169 assert_eq!(report.bad_signers, vec!["bob"]);
170 }
171
172 #[test]
173 fn labeled_share_carries_identity() {
174 let share = make_share("alice", 0x42);
175 assert_eq!(share.signer_id, "alice");
176 assert_eq!(share.bytes.len(), 64);
177 }
178
179 #[test]
180 fn threshold_above_subset_size_skipped() {
181 let signer = MockSigner;
182 let shares = vec![make_share("alice", 0xAA), make_share("bob", 0xBB)];
183 let report = identify_bad_signer("s1", &shares, 2, b"msg", "CMP20", &signer);
184 assert!(report.bad_signers.is_empty());
188 }
189}