use crate::{PublicKey, SignatureBytes};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ThresholdError {
#[error("Not enough signatures: need {threshold}, got {actual}")]
InsufficientSignatures { threshold: usize, actual: usize },
#[error("Invalid signature at index {0}")]
InvalidSignature(usize),
#[error("Duplicate public key")]
DuplicatePublicKey,
#[error("Invalid threshold: {0}")]
InvalidThreshold(String),
#[error("Signing error: {0}")]
SigningError(#[from] crate::SigningError),
}
#[derive(Debug, Clone)]
pub struct MultiSig {
pub signers: Vec<PublicKey>,
pub signatures: Vec<SignatureBytes>,
}
impl MultiSig {
pub fn new(signers: Vec<PublicKey>, signatures: Vec<SignatureBytes>) -> Self {
Self {
signers,
signatures,
}
}
pub fn verify(&self, message: &[u8]) -> Result<(), ThresholdError> {
use crate::signing::verify;
if self.signers.len() != self.signatures.len() {
return Err(ThresholdError::InsufficientSignatures {
threshold: self.signers.len(),
actual: self.signatures.len(),
});
}
for (i, (pubkey, sig)) in self.signers.iter().zip(self.signatures.iter()).enumerate() {
verify(pubkey, message, sig).map_err(|_| ThresholdError::InvalidSignature(i))?;
}
Ok(())
}
pub fn signer_count(&self) -> usize {
self.signers.len()
}
}
#[derive(Debug, Clone)]
pub struct ThresholdSig {
pub possible_signers: Vec<PublicKey>,
pub threshold: usize,
pub signatures: Vec<(PublicKey, SignatureBytes)>,
}
impl ThresholdSig {
pub fn new(possible_signers: Vec<PublicKey>, threshold: usize) -> Result<Self, ThresholdError> {
if threshold == 0 || threshold > possible_signers.len() {
return Err(ThresholdError::InvalidThreshold(format!(
"threshold must be 1 <= M <= {}, got {}",
possible_signers.len(),
threshold
)));
}
let mut sorted = possible_signers.clone();
sorted.sort();
for i in 1..sorted.len() {
if sorted[i] == sorted[i - 1] {
return Err(ThresholdError::DuplicatePublicKey);
}
}
Ok(Self {
possible_signers,
threshold,
signatures: Vec::new(),
})
}
pub fn add_signature(
&mut self,
signer: PublicKey,
signature: SignatureBytes,
) -> Result<(), ThresholdError> {
if !self.possible_signers.contains(&signer) {
return Err(ThresholdError::InvalidSignature(0));
}
if self.signatures.iter().any(|(pk, _)| pk == &signer) {
return Err(ThresholdError::DuplicatePublicKey);
}
self.signatures.push((signer, signature));
Ok(())
}
pub fn verify(&self, message: &[u8]) -> Result<(), ThresholdError> {
use crate::signing::verify;
if self.signatures.len() < self.threshold {
return Err(ThresholdError::InsufficientSignatures {
threshold: self.threshold,
actual: self.signatures.len(),
});
}
for (i, (pubkey, sig)) in self.signatures.iter().enumerate() {
if !self.possible_signers.contains(pubkey) {
return Err(ThresholdError::InvalidSignature(i));
}
verify(pubkey, message, sig).map_err(|_| ThresholdError::InvalidSignature(i))?;
}
Ok(())
}
pub fn is_complete(&self) -> bool {
self.signatures.len() >= self.threshold
}
pub fn signature_count(&self) -> usize {
self.signatures.len()
}
}
pub struct MultiSigBuilder {
signers: Vec<PublicKey>,
signatures: Vec<Option<SignatureBytes>>,
}
impl MultiSigBuilder {
pub fn new(signers: Vec<PublicKey>) -> Self {
let count = signers.len();
Self {
signers,
signatures: vec![None; count],
}
}
pub fn add_signature(
&mut self,
signer: &PublicKey,
signature: SignatureBytes,
) -> Result<(), ThresholdError> {
let index = self
.signers
.iter()
.position(|pk| pk == signer)
.ok_or(ThresholdError::InvalidSignature(0))?;
self.signatures[index] = Some(signature);
Ok(())
}
pub fn is_complete(&self) -> bool {
self.signatures.iter().all(|s| s.is_some())
}
pub fn build(self) -> Result<MultiSig, ThresholdError> {
let signatures: Option<Vec<SignatureBytes>> = self.signatures.into_iter().collect();
match signatures {
Some(sigs) => Ok(MultiSig::new(self.signers, sigs)),
None => Err(ThresholdError::InsufficientSignatures {
threshold: self.signers.len(),
actual: 0,
}),
}
}
}
pub struct ThresholdCoordinator {
threshold_sig: ThresholdSig,
message: Vec<u8>,
}
impl ThresholdCoordinator {
pub fn new(
possible_signers: Vec<PublicKey>,
threshold: usize,
message: Vec<u8>,
) -> Result<Self, ThresholdError> {
let threshold_sig = ThresholdSig::new(possible_signers, threshold)?;
Ok(Self {
threshold_sig,
message,
})
}
pub fn add_signature(
&mut self,
signer: PublicKey,
signature: SignatureBytes,
) -> Result<(), ThresholdError> {
use crate::signing::verify;
verify(&signer, &self.message, &signature)?;
self.threshold_sig.add_signature(signer, signature)
}
pub fn is_complete(&self) -> bool {
self.threshold_sig.is_complete()
}
pub fn finalize(self) -> Result<ThresholdSig, ThresholdError> {
if !self.threshold_sig.is_complete() {
return Err(ThresholdError::InsufficientSignatures {
threshold: self.threshold_sig.threshold,
actual: self.threshold_sig.signature_count(),
});
}
Ok(self.threshold_sig)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::signing::KeyPair;
#[test]
fn test_multi_sig() {
let message = b"Multi-sig test message";
let kp1 = KeyPair::generate();
let kp2 = KeyPair::generate();
let kp3 = KeyPair::generate();
let signers = vec![kp1.public_key(), kp2.public_key(), kp3.public_key()];
let signatures = vec![kp1.sign(message), kp2.sign(message), kp3.sign(message)];
let multi_sig = MultiSig::new(signers, signatures);
assert!(multi_sig.verify(message).is_ok());
}
#[test]
fn test_multi_sig_invalid() {
let message = b"Test message";
let wrong_message = b"Wrong message";
let kp1 = KeyPair::generate();
let kp2 = KeyPair::generate();
let signers = vec![kp1.public_key(), kp2.public_key()];
let signatures = vec![kp1.sign(message), kp2.sign(wrong_message)];
let multi_sig = MultiSig::new(signers, signatures);
assert!(multi_sig.verify(message).is_err());
}
#[test]
fn test_threshold_sig_2_of_3() {
let message = b"Threshold sig test";
let kp1 = KeyPair::generate();
let kp2 = KeyPair::generate();
let kp3 = KeyPair::generate();
let possible_signers = vec![kp1.public_key(), kp2.public_key(), kp3.public_key()];
let mut threshold_sig = ThresholdSig::new(possible_signers, 2).unwrap();
threshold_sig
.add_signature(kp1.public_key(), kp1.sign(message))
.unwrap();
assert!(!threshold_sig.is_complete());
threshold_sig
.add_signature(kp2.public_key(), kp2.sign(message))
.unwrap();
assert!(threshold_sig.is_complete());
assert!(threshold_sig.verify(message).is_ok());
}
#[test]
fn test_threshold_insufficient_signatures() {
let message = b"Test";
let kp1 = KeyPair::generate();
let kp2 = KeyPair::generate();
let kp3 = KeyPair::generate();
let possible_signers = vec![kp1.public_key(), kp2.public_key(), kp3.public_key()];
let mut threshold_sig = ThresholdSig::new(possible_signers, 2).unwrap();
threshold_sig
.add_signature(kp1.public_key(), kp1.sign(message))
.unwrap();
assert!(threshold_sig.verify(message).is_err());
}
#[test]
fn test_multi_sig_builder() {
let message = b"Builder test";
let kp1 = KeyPair::generate();
let kp2 = KeyPair::generate();
let signers = vec![kp1.public_key(), kp2.public_key()];
let mut builder = MultiSigBuilder::new(signers);
assert!(!builder.is_complete());
builder
.add_signature(&kp1.public_key(), kp1.sign(message))
.unwrap();
assert!(!builder.is_complete());
builder
.add_signature(&kp2.public_key(), kp2.sign(message))
.unwrap();
assert!(builder.is_complete());
let multi_sig = builder.build().unwrap();
assert!(multi_sig.verify(message).is_ok());
}
#[test]
fn test_threshold_coordinator() {
let message = b"Coordinator test";
let kp1 = KeyPair::generate();
let kp2 = KeyPair::generate();
let kp3 = KeyPair::generate();
let signers = vec![kp1.public_key(), kp2.public_key(), kp3.public_key()];
let mut coordinator = ThresholdCoordinator::new(signers, 2, message.to_vec()).unwrap();
coordinator
.add_signature(kp1.public_key(), kp1.sign(message))
.unwrap();
assert!(!coordinator.is_complete());
coordinator
.add_signature(kp2.public_key(), kp2.sign(message))
.unwrap();
assert!(coordinator.is_complete());
let threshold_sig = coordinator.finalize().unwrap();
assert!(threshold_sig.verify(message).is_ok());
}
#[test]
fn test_invalid_threshold() {
let kp1 = KeyPair::generate();
let signers = vec![kp1.public_key()];
assert!(ThresholdSig::new(signers.clone(), 0).is_err());
assert!(ThresholdSig::new(signers, 2).is_err());
}
}