1use crate::serializer::{SignatureEntry, VersionEntry};
14use crate::signature_chain::verify_attestation;
15use crate::types::AuthorId;
16use crate::{AionError, Result};
17
18#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
20pub struct MultiSigPolicy {
21 pub threshold: u32,
23 pub total_signers: u32,
25 pub authorized_signers: Vec<AuthorId>,
27}
28
29impl MultiSigPolicy {
30 pub fn new(threshold: u32, authorized_signers: Vec<AuthorId>) -> Result<Self> {
58 if threshold == 0 {
59 return Err(AionError::InvalidFormat {
60 reason: "Threshold must be at least 1".to_string(),
61 });
62 }
63
64 let total = authorized_signers.len() as u32;
65 if threshold > total {
66 return Err(AionError::InvalidFormat {
67 reason: format!(
68 "Threshold ({threshold}) cannot exceed number of signers ({total})"
69 ),
70 });
71 }
72
73 Ok(Self {
74 threshold,
75 total_signers: total,
76 authorized_signers,
77 })
78 }
79
80 #[must_use]
82 pub fn single_signer(signer: AuthorId) -> Self {
83 Self {
84 threshold: 1,
85 total_signers: 1,
86 authorized_signers: vec![signer],
87 }
88 }
89
90 pub fn m_of_n(m: u32, signers: Vec<AuthorId>) -> Result<Self> {
92 Self::new(m, signers)
93 }
94
95 #[must_use]
97 pub fn is_authorized(&self, author: AuthorId) -> bool {
98 self.authorized_signers.contains(&author)
99 }
100
101 #[must_use]
103 pub fn description(&self) -> String {
104 format!("{}-of-{}", self.threshold, self.total_signers)
105 }
106}
107
108#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
110pub struct MultiSigVerification {
111 pub threshold_met: bool,
113 pub valid_count: u32,
115 pub required: u32,
117 pub valid_signers: Vec<AuthorId>,
119 pub invalid_signers: Vec<AuthorId>,
121 pub missing_signers: Vec<AuthorId>,
123}
124
125impl MultiSigVerification {
126 #[must_use]
128 pub fn is_valid(&self) -> bool {
129 self.threshold_met && self.invalid_signers.is_empty()
130 }
131}
132
133pub fn verify_multisig(
149 version: &VersionEntry,
150 signatures: &[SignatureEntry],
151 policy: &MultiSigPolicy,
152 registry: &crate::key_registry::KeyRegistry,
153) -> Result<MultiSigVerification> {
154 let mut valid_signers = Vec::new();
155 let mut invalid_signers = Vec::new();
156 let mut seen: std::collections::HashSet<AuthorId> = std::collections::HashSet::new();
157
158 for sig in signatures {
159 let author = AuthorId::new(sig.author_id);
160 if !policy.is_authorized(author) {
161 continue;
162 }
163 if !seen.insert(author) {
164 continue;
165 }
166 match verify_attestation(version, sig, registry) {
167 Ok(()) => valid_signers.push(author),
168 Err(_) => invalid_signers.push(author),
169 }
170 }
171
172 let missing_signers: Vec<_> = policy
173 .authorized_signers
174 .iter()
175 .filter(|a| !seen.contains(a))
176 .copied()
177 .collect();
178
179 let valid_count = valid_signers.len() as u32;
180 let threshold_met = valid_count >= policy.threshold;
181
182 if threshold_met && invalid_signers.is_empty() {
183 tracing::info!(
184 event = "multisig_threshold_met",
185 version = version.version_number,
186 valid = valid_count,
187 required = policy.threshold,
188 );
189 } else {
190 tracing::warn!(
191 event = "multisig_threshold_short",
192 version = version.version_number,
193 valid = valid_count,
194 required = policy.threshold,
195 invalid = invalid_signers.len() as u32,
196 missing = missing_signers.len() as u32,
197 reason = if invalid_signers.is_empty() {
198 "insufficient_signers"
199 } else {
200 "byzantine_signer"
201 },
202 );
203 }
204
205 Ok(MultiSigVerification {
206 threshold_met,
207 valid_count,
208 required: policy.threshold,
209 valid_signers,
210 invalid_signers,
211 missing_signers,
212 })
213}
214
215#[derive(Debug, Clone)]
220pub struct SignatureAggregator {
221 signatures: Vec<SignatureEntry>,
222}
223
224impl SignatureAggregator {
225 #[must_use]
227 pub const fn new() -> Self {
228 Self {
229 signatures: Vec::new(),
230 }
231 }
232
233 pub fn add_signature(&mut self, signature: SignatureEntry) {
235 self.signatures.push(signature);
236 }
237
238 #[must_use]
240 pub fn count(&self) -> usize {
241 self.signatures.len()
242 }
243
244 #[must_use]
246 pub fn signatures(&self) -> &[SignatureEntry] {
247 &self.signatures
248 }
249
250 #[must_use]
252 pub fn into_signatures(self) -> Vec<SignatureEntry> {
253 self.signatures
254 }
255}
256
257impl Default for SignatureAggregator {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263#[cfg(test)]
264#[allow(deprecated)] mod tests {
266 use super::*;
267
268 #[test]
269 fn test_policy_creation() {
270 let signers = vec![AuthorId::new(1), AuthorId::new(2), AuthorId::new(3)];
271 let policy = MultiSigPolicy::new(2, signers).unwrap_or_else(|_| std::process::abort());
272
273 assert_eq!(policy.threshold, 2);
274 assert_eq!(policy.total_signers, 3);
275 assert_eq!(policy.description(), "2-of-3");
276 }
277
278 #[test]
279 fn test_policy_invalid_threshold() {
280 let signers = vec![AuthorId::new(1), AuthorId::new(2)];
281
282 let result = MultiSigPolicy::new(3, signers.clone());
284 assert!(result.is_err());
285
286 let result = MultiSigPolicy::new(0, signers);
288 assert!(result.is_err());
289 }
290
291 #[test]
292 fn test_single_signer_policy() {
293 let policy = MultiSigPolicy::single_signer(AuthorId::new(42));
294
295 assert_eq!(policy.threshold, 1);
296 assert_eq!(policy.total_signers, 1);
297 assert!(policy.is_authorized(AuthorId::new(42)));
298 assert!(!policy.is_authorized(AuthorId::new(99)));
299 }
300
301 #[test]
302 fn test_signature_aggregator() {
303 let mut agg = SignatureAggregator::new();
304 assert_eq!(agg.count(), 0);
305
306 let sig = SignatureEntry {
307 author_id: 100,
308 public_key: [0u8; 32],
309 signature: [0u8; 64],
310 reserved: [0u8; 8],
311 };
312
313 agg.add_signature(sig);
314 assert_eq!(agg.count(), 1);
315 }
316
317 mod properties {
318 use super::*;
319 use crate::crypto::SigningKey;
320 use crate::key_registry::KeyRegistry;
321 use crate::serializer::VersionEntry;
322 use crate::signature_chain::sign_attestation;
323 use crate::types::VersionNumber;
324 use hegel::generators as gs;
325
326 fn pin_all(signers: &[(AuthorId, SigningKey)]) -> KeyRegistry {
330 let mut reg = KeyRegistry::new();
331 for (author, key) in signers {
332 let master = SigningKey::generate();
333 reg.register_author(*author, master.verifying_key(), key.verifying_key(), 0)
334 .unwrap_or_else(|_| std::process::abort());
335 }
336 reg
337 }
338
339 fn make_version(author: AuthorId) -> VersionEntry {
340 VersionEntry::new(
341 VersionNumber::GENESIS,
342 [0u8; 32],
343 [0xAA; 32],
344 author,
345 1_700_000_000_000_000_000,
346 0,
347 0,
348 )
349 }
350
351 fn distinct_signers(n: u32, exclude: AuthorId) -> Vec<(AuthorId, SigningKey)> {
355 let mut out = Vec::with_capacity(n as usize);
356 let mut next_id: u64 = 10_000;
357 while (out.len() as u32) < n {
358 if next_id != exclude.as_u64() {
359 out.push((AuthorId::new(next_id), SigningKey::generate()));
360 }
361 next_id = next_id.saturating_add(1);
362 }
363 out
364 }
365
366 #[hegel::test]
367 fn prop_multisig_k_distinct_signers_accepts(tc: hegel::TestCase) {
368 let n = tc.draw(gs::integers::<u32>().min_value(1).max_value(8));
369 let threshold = tc.draw(gs::integers::<u32>().min_value(1).max_value(n));
370 let version_author =
371 AuthorId::new(tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX)));
372 let version = make_version(version_author);
373 let signers = distinct_signers(n, version_author);
374 let authorized: Vec<AuthorId> = signers.iter().map(|(a, _)| *a).collect();
375 let policy = MultiSigPolicy::new(threshold, authorized)
376 .unwrap_or_else(|_| std::process::abort());
377 let attestations: Vec<SignatureEntry> = signers
378 .iter()
379 .take(threshold as usize)
380 .map(|(who, key)| sign_attestation(&version, *who, key))
381 .collect();
382 let reg = pin_all(&signers);
383 let result = verify_multisig(&version, &attestations, &policy, ®)
384 .unwrap_or_else(|_| std::process::abort());
385 assert!(result.threshold_met);
386 assert_eq!(result.valid_count, threshold);
387 }
388
389 #[hegel::test]
390 fn prop_multisig_kminus1_distinct_rejects(tc: hegel::TestCase) {
391 let n = tc.draw(gs::integers::<u32>().min_value(2).max_value(8));
392 let threshold = tc.draw(gs::integers::<u32>().min_value(2).max_value(n));
393 let version_author =
394 AuthorId::new(tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX)));
395 let version = make_version(version_author);
396 let signers = distinct_signers(n, version_author);
397 let authorized: Vec<AuthorId> = signers.iter().map(|(a, _)| *a).collect();
398 let policy = MultiSigPolicy::new(threshold, authorized)
399 .unwrap_or_else(|_| std::process::abort());
400 let short = threshold.saturating_sub(1) as usize;
401 let attestations: Vec<SignatureEntry> = signers
402 .iter()
403 .take(short)
404 .map(|(who, key)| sign_attestation(&version, *who, key))
405 .collect();
406 let reg = pin_all(&signers);
407 let result = verify_multisig(&version, &attestations, &policy, ®)
408 .unwrap_or_else(|_| std::process::abort());
409 assert!(!result.threshold_met);
410 }
411
412 #[hegel::test]
413 fn prop_multisig_duplicate_attestations_count_once(tc: hegel::TestCase) {
414 let n = tc.draw(gs::integers::<u32>().min_value(2).max_value(8));
415 let threshold = tc.draw(gs::integers::<u32>().min_value(2).max_value(n));
416 let dups = tc.draw(gs::integers::<u32>().min_value(2).max_value(8));
417 let version_author =
418 AuthorId::new(tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX)));
419 let version = make_version(version_author);
420 let signers = distinct_signers(n, version_author);
421 let authorized: Vec<AuthorId> = signers.iter().map(|(a, _)| *a).collect();
422 let policy = MultiSigPolicy::new(threshold, authorized)
423 .unwrap_or_else(|_| std::process::abort());
424 let first = signers.first().unwrap_or_else(|| std::process::abort());
426 let att = sign_attestation(&version, first.0, &first.1);
427 let attestations: Vec<SignatureEntry> = (0..dups).map(|_| att).collect();
428 let reg = pin_all(&signers);
429 let result = verify_multisig(&version, &attestations, &policy, ®)
430 .unwrap_or_else(|_| std::process::abort());
431 assert_eq!(result.valid_count, 1);
432 assert!(!result.threshold_met);
433 }
434
435 #[hegel::test]
436 fn prop_unauthorized_signers_do_not_count(tc: hegel::TestCase) {
437 let author_id = tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX / 2));
438 let author = AuthorId::new(author_id);
439 let version = make_version(author);
440 let impostor = AuthorId::new(author_id.wrapping_add(1).max(2));
441 let key = SigningKey::generate();
442 let sig = sign_attestation(&version, impostor, &key);
443 let policy =
444 MultiSigPolicy::new(1, vec![author]).unwrap_or_else(|_| std::process::abort());
445 let author_key = SigningKey::generate();
448 let reg = pin_all(&[(author, author_key)]);
449 let result = verify_multisig(&version, &[sig], &policy, ®)
450 .unwrap_or_else(|_| std::process::abort());
451 assert_eq!(result.valid_count, 0);
452 assert!(!result.threshold_met);
453 }
454
455 #[hegel::test]
456 fn prop_forged_author_id_rejects(tc: hegel::TestCase) {
457 let version_author =
458 AuthorId::new(tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX / 2)));
459 let version = make_version(version_author);
460 let real_signer = AuthorId::new(version_author.as_u64().saturating_add(1));
461 let fake_signer = AuthorId::new(real_signer.as_u64().saturating_add(1));
462 let key = SigningKey::generate();
463 let mut sig = sign_attestation(&version, real_signer, &key);
464 sig.author_id = fake_signer.as_u64();
465 let policy =
466 MultiSigPolicy::new(1, vec![fake_signer]).unwrap_or_else(|_| std::process::abort());
467 let fake_key = SigningKey::generate();
470 let reg = pin_all(&[(fake_signer, fake_key)]);
471 let result = verify_multisig(&version, &[sig], &policy, ®)
472 .unwrap_or_else(|_| std::process::abort());
473 assert_eq!(result.valid_count, 0);
474 assert!(!result.threshold_met);
475 }
476 }
477}