use crate::serializer::{SignatureEntry, VersionEntry};
use crate::signature_chain::verify_attestation;
use crate::types::AuthorId;
use crate::{AionError, Result};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MultiSigPolicy {
pub threshold: u32,
pub total_signers: u32,
pub authorized_signers: Vec<AuthorId>,
}
impl MultiSigPolicy {
pub fn new(threshold: u32, authorized_signers: Vec<AuthorId>) -> Result<Self> {
if threshold == 0 {
return Err(AionError::InvalidFormat {
reason: "Threshold must be at least 1".to_string(),
});
}
let total = authorized_signers.len() as u32;
if threshold > total {
return Err(AionError::InvalidFormat {
reason: format!(
"Threshold ({threshold}) cannot exceed number of signers ({total})"
),
});
}
Ok(Self {
threshold,
total_signers: total,
authorized_signers,
})
}
#[must_use]
pub fn single_signer(signer: AuthorId) -> Self {
Self {
threshold: 1,
total_signers: 1,
authorized_signers: vec![signer],
}
}
pub fn m_of_n(m: u32, signers: Vec<AuthorId>) -> Result<Self> {
Self::new(m, signers)
}
#[must_use]
pub fn is_authorized(&self, author: AuthorId) -> bool {
self.authorized_signers.contains(&author)
}
#[must_use]
pub fn description(&self) -> String {
format!("{}-of-{}", self.threshold, self.total_signers)
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct MultiSigVerification {
pub threshold_met: bool,
pub valid_count: u32,
pub required: u32,
pub valid_signers: Vec<AuthorId>,
pub invalid_signers: Vec<AuthorId>,
pub missing_signers: Vec<AuthorId>,
}
impl MultiSigVerification {
#[must_use]
pub fn is_valid(&self) -> bool {
self.threshold_met && self.invalid_signers.is_empty()
}
}
pub fn verify_multisig(
version: &VersionEntry,
signatures: &[SignatureEntry],
policy: &MultiSigPolicy,
registry: &crate::key_registry::KeyRegistry,
) -> Result<MultiSigVerification> {
let mut valid_signers = Vec::new();
let mut invalid_signers = Vec::new();
let mut seen: std::collections::HashSet<AuthorId> = std::collections::HashSet::new();
for sig in signatures {
let author = AuthorId::new(sig.author_id);
if !policy.is_authorized(author) {
continue;
}
if !seen.insert(author) {
continue;
}
match verify_attestation(version, sig, registry) {
Ok(()) => valid_signers.push(author),
Err(_) => invalid_signers.push(author),
}
}
let missing_signers: Vec<_> = policy
.authorized_signers
.iter()
.filter(|a| !seen.contains(a))
.copied()
.collect();
let valid_count = valid_signers.len() as u32;
let threshold_met = valid_count >= policy.threshold;
if threshold_met && invalid_signers.is_empty() {
tracing::info!(
event = "multisig_threshold_met",
version = version.version_number,
valid = valid_count,
required = policy.threshold,
);
} else {
tracing::warn!(
event = "multisig_threshold_short",
version = version.version_number,
valid = valid_count,
required = policy.threshold,
invalid = invalid_signers.len() as u32,
missing = missing_signers.len() as u32,
reason = if invalid_signers.is_empty() {
"insufficient_signers"
} else {
"byzantine_signer"
},
);
}
Ok(MultiSigVerification {
threshold_met,
valid_count,
required: policy.threshold,
valid_signers,
invalid_signers,
missing_signers,
})
}
#[derive(Debug, Clone)]
pub struct SignatureAggregator {
signatures: Vec<SignatureEntry>,
}
impl SignatureAggregator {
#[must_use]
pub const fn new() -> Self {
Self {
signatures: Vec::new(),
}
}
pub fn add_signature(&mut self, signature: SignatureEntry) {
self.signatures.push(signature);
}
#[must_use]
pub fn count(&self) -> usize {
self.signatures.len()
}
#[must_use]
pub fn signatures(&self) -> &[SignatureEntry] {
&self.signatures
}
#[must_use]
pub fn into_signatures(self) -> Vec<SignatureEntry> {
self.signatures
}
}
impl Default for SignatureAggregator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(deprecated)] mod tests {
use super::*;
#[test]
fn test_policy_creation() {
let signers = vec![AuthorId::new(1), AuthorId::new(2), AuthorId::new(3)];
let policy = MultiSigPolicy::new(2, signers).unwrap_or_else(|_| std::process::abort());
assert_eq!(policy.threshold, 2);
assert_eq!(policy.total_signers, 3);
assert_eq!(policy.description(), "2-of-3");
}
#[test]
fn test_policy_invalid_threshold() {
let signers = vec![AuthorId::new(1), AuthorId::new(2)];
let result = MultiSigPolicy::new(3, signers.clone());
assert!(result.is_err());
let result = MultiSigPolicy::new(0, signers);
assert!(result.is_err());
}
#[test]
fn test_single_signer_policy() {
let policy = MultiSigPolicy::single_signer(AuthorId::new(42));
assert_eq!(policy.threshold, 1);
assert_eq!(policy.total_signers, 1);
assert!(policy.is_authorized(AuthorId::new(42)));
assert!(!policy.is_authorized(AuthorId::new(99)));
}
#[test]
fn test_signature_aggregator() {
let mut agg = SignatureAggregator::new();
assert_eq!(agg.count(), 0);
let sig = SignatureEntry {
author_id: 100,
public_key: [0u8; 32],
signature: [0u8; 64],
reserved: [0u8; 8],
};
agg.add_signature(sig);
assert_eq!(agg.count(), 1);
}
mod properties {
use super::*;
use crate::crypto::SigningKey;
use crate::key_registry::KeyRegistry;
use crate::serializer::VersionEntry;
use crate::signature_chain::sign_attestation;
use crate::types::VersionNumber;
use hegel::generators as gs;
fn pin_all(signers: &[(AuthorId, SigningKey)]) -> KeyRegistry {
let mut reg = KeyRegistry::new();
for (author, key) in signers {
let master = SigningKey::generate();
reg.register_author(*author, master.verifying_key(), key.verifying_key(), 0)
.unwrap_or_else(|_| std::process::abort());
}
reg
}
fn make_version(author: AuthorId) -> VersionEntry {
VersionEntry::new(
VersionNumber::GENESIS,
[0u8; 32],
[0xAA; 32],
author,
1_700_000_000_000_000_000,
0,
0,
)
}
fn distinct_signers(n: u32, exclude: AuthorId) -> Vec<(AuthorId, SigningKey)> {
let mut out = Vec::with_capacity(n as usize);
let mut next_id: u64 = 10_000;
while (out.len() as u32) < n {
if next_id != exclude.as_u64() {
out.push((AuthorId::new(next_id), SigningKey::generate()));
}
next_id = next_id.saturating_add(1);
}
out
}
#[hegel::test]
fn prop_multisig_k_distinct_signers_accepts(tc: hegel::TestCase) {
let n = tc.draw(gs::integers::<u32>().min_value(1).max_value(8));
let threshold = tc.draw(gs::integers::<u32>().min_value(1).max_value(n));
let version_author =
AuthorId::new(tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX)));
let version = make_version(version_author);
let signers = distinct_signers(n, version_author);
let authorized: Vec<AuthorId> = signers.iter().map(|(a, _)| *a).collect();
let policy = MultiSigPolicy::new(threshold, authorized)
.unwrap_or_else(|_| std::process::abort());
let attestations: Vec<SignatureEntry> = signers
.iter()
.take(threshold as usize)
.map(|(who, key)| sign_attestation(&version, *who, key))
.collect();
let reg = pin_all(&signers);
let result = verify_multisig(&version, &attestations, &policy, ®)
.unwrap_or_else(|_| std::process::abort());
assert!(result.threshold_met);
assert_eq!(result.valid_count, threshold);
}
#[hegel::test]
fn prop_multisig_kminus1_distinct_rejects(tc: hegel::TestCase) {
let n = tc.draw(gs::integers::<u32>().min_value(2).max_value(8));
let threshold = tc.draw(gs::integers::<u32>().min_value(2).max_value(n));
let version_author =
AuthorId::new(tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX)));
let version = make_version(version_author);
let signers = distinct_signers(n, version_author);
let authorized: Vec<AuthorId> = signers.iter().map(|(a, _)| *a).collect();
let policy = MultiSigPolicy::new(threshold, authorized)
.unwrap_or_else(|_| std::process::abort());
let short = threshold.saturating_sub(1) as usize;
let attestations: Vec<SignatureEntry> = signers
.iter()
.take(short)
.map(|(who, key)| sign_attestation(&version, *who, key))
.collect();
let reg = pin_all(&signers);
let result = verify_multisig(&version, &attestations, &policy, ®)
.unwrap_or_else(|_| std::process::abort());
assert!(!result.threshold_met);
}
#[hegel::test]
fn prop_multisig_duplicate_attestations_count_once(tc: hegel::TestCase) {
let n = tc.draw(gs::integers::<u32>().min_value(2).max_value(8));
let threshold = tc.draw(gs::integers::<u32>().min_value(2).max_value(n));
let dups = tc.draw(gs::integers::<u32>().min_value(2).max_value(8));
let version_author =
AuthorId::new(tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX)));
let version = make_version(version_author);
let signers = distinct_signers(n, version_author);
let authorized: Vec<AuthorId> = signers.iter().map(|(a, _)| *a).collect();
let policy = MultiSigPolicy::new(threshold, authorized)
.unwrap_or_else(|_| std::process::abort());
let first = signers.first().unwrap_or_else(|| std::process::abort());
let att = sign_attestation(&version, first.0, &first.1);
let attestations: Vec<SignatureEntry> = (0..dups).map(|_| att).collect();
let reg = pin_all(&signers);
let result = verify_multisig(&version, &attestations, &policy, ®)
.unwrap_or_else(|_| std::process::abort());
assert_eq!(result.valid_count, 1);
assert!(!result.threshold_met);
}
#[hegel::test]
fn prop_unauthorized_signers_do_not_count(tc: hegel::TestCase) {
let author_id = tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX / 2));
let author = AuthorId::new(author_id);
let version = make_version(author);
let impostor = AuthorId::new(author_id.wrapping_add(1).max(2));
let key = SigningKey::generate();
let sig = sign_attestation(&version, impostor, &key);
let policy =
MultiSigPolicy::new(1, vec![author]).unwrap_or_else(|_| std::process::abort());
let author_key = SigningKey::generate();
let reg = pin_all(&[(author, author_key)]);
let result = verify_multisig(&version, &[sig], &policy, ®)
.unwrap_or_else(|_| std::process::abort());
assert_eq!(result.valid_count, 0);
assert!(!result.threshold_met);
}
#[hegel::test]
fn prop_forged_author_id_rejects(tc: hegel::TestCase) {
let version_author =
AuthorId::new(tc.draw(gs::integers::<u64>().min_value(1).max_value(u64::MAX / 2)));
let version = make_version(version_author);
let real_signer = AuthorId::new(version_author.as_u64().saturating_add(1));
let fake_signer = AuthorId::new(real_signer.as_u64().saturating_add(1));
let key = SigningKey::generate();
let mut sig = sign_attestation(&version, real_signer, &key);
sig.author_id = fake_signer.as_u64();
let policy =
MultiSigPolicy::new(1, vec![fake_signer]).unwrap_or_else(|_| std::process::abort());
let fake_key = SigningKey::generate();
let reg = pin_all(&[(fake_signer, fake_key)]);
let result = verify_multisig(&version, &[sig], &policy, ®)
.unwrap_or_else(|_| std::process::abort());
assert_eq!(result.valid_count, 0);
assert!(!result.threshold_met);
}
}
}