use std::{fmt, str::FromStr};
use crate::{error::Error, prefix::AttachedSignaturePrefix};
use fraction::{Fraction, One, Zero};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_hex::{Compact, SerHex};
#[derive(Debug, Clone, PartialEq)]
pub struct ThresholdFraction {
fraction: Fraction,
}
impl ThresholdFraction {
pub fn new(n: u64, d: u64) -> Self {
Self {
fraction: Fraction::new(n, d),
}
}
}
impl fmt::Display for ThresholdFraction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.fraction)
}
}
impl FromStr for ThresholdFraction {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let f: Vec<_> = s.split('/').collect();
if f.len() > 2 {
Err(Error::SemanticError("Improper threshold fraction".into()))
} else if f.len() == 1 {
let a = f[0].parse::<u64>()?;
Ok(ThresholdFraction {
fraction: Fraction::new(a, 1u64),
})
} else {
let a = f[0].parse::<u64>()?;
let b = f[1].parse::<u64>()?;
Ok(ThresholdFraction {
fraction: Fraction::new(a, b),
})
}
}
}
impl<'de> Deserialize<'de> for ThresholdFraction {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(de::Error::custom)
}
}
impl Serialize for ThresholdFraction {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{}", self.fraction))
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum SignatureThreshold {
#[serde(with = "SerHex::<Compact>")]
Simple(u64),
Weighted(WeightedThreshold),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum WeightedThreshold {
Single(ThresholdClause),
Multi(MultiClauses),
}
impl WeightedThreshold {
pub fn enough_signatures(&self, sigs: &[AttachedSignaturePrefix]) -> Result<bool, Error> {
match self {
WeightedThreshold::Single(clause) => clause.enough_signatures(0, sigs),
WeightedThreshold::Multi(clauses) => clauses.enough_signatures(sigs),
}
}
pub fn extract_threshold(&self) -> String {
match self {
WeightedThreshold::Single(clause) => clause.extract_threshold(),
WeightedThreshold::Multi(clauses) => clauses.extract_threshold(),
}
}
}
impl SignatureThreshold {
pub fn simple(t: u64) -> Self {
Self::Simple(t)
}
pub fn single_weighted(fracs: Vec<(u64, u64)>) -> Self {
Self::Weighted(WeightedThreshold::Single(ThresholdClause::new_from_tuples(
fracs,
)))
}
pub fn multi_weighted(fracs: Vec<Vec<(u64, u64)>>) -> Self {
Self::Weighted(WeightedThreshold::Multi(MultiClauses::new_from_tuples(
fracs,
)))
}
pub fn enough_signatures(&self, sigs: &[AttachedSignaturePrefix]) -> Result<bool, Error> {
match self {
SignatureThreshold::Simple(ref t) => Ok((sigs.len() as u64) >= *t),
SignatureThreshold::Weighted(ref thresh) => thresh.enough_signatures(sigs),
}
}
}
impl Default for SignatureThreshold {
fn default() -> Self {
Self::Simple(1)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ThresholdClause(Vec<ThresholdFraction>);
impl ThresholdClause {
pub fn new(fracs: &[ThresholdFraction]) -> Self {
Self(fracs.to_owned())
}
pub fn new_from_tuples(tuples: Vec<(u64, u64)>) -> Self {
let clause = tuples
.into_iter()
.map(|(n, d)| ThresholdFraction::new(n, d))
.collect();
Self(clause)
}
pub fn enough_signatures(
&self,
start_index: u16,
sigs: &[AttachedSignaturePrefix],
) -> Result<bool, Error> {
Ok(sigs.iter().fold(Zero::zero(), |acc: Fraction, sig| {
acc + self.0[(sig.index - start_index) as usize].fraction
}) >= One::one())
}
pub fn extract_threshold(&self) -> String {
self.0
.iter()
.map(|fr| fr.to_string())
.collect::<Vec<_>>()
.join(",")
}
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub struct MultiClauses(Vec<ThresholdClause>);
impl MultiClauses {
pub fn new(fracs: Vec<Vec<ThresholdFraction>>) -> Self {
let clauses = fracs
.iter()
.map(|clause| ThresholdClause::new(clause))
.collect();
Self(clauses)
}
pub fn new_from_tuples(fracs: Vec<Vec<(u64, u64)>>) -> Self {
let wt = fracs
.into_iter()
.map(ThresholdClause::new_from_tuples)
.collect();
MultiClauses(wt)
}
pub fn enough_signatures(&self, sigs: &[AttachedSignaturePrefix]) -> Result<bool, Error> {
Ok(self
.0
.iter()
.fold(Ok((0, true)), |acc, clause| -> Result<_, Error> {
let (start, enough) = acc?;
let sigs: Vec<AttachedSignaturePrefix> = sigs
.to_owned()
.into_iter()
.filter(|sig| sig.index >= start && sig.index < start + clause.0.len() as u16)
.collect();
Ok((
start + clause.0.len() as u16,
enough && clause.enough_signatures(start, &sigs)?,
))
})?
.1)
}
pub fn extract_threshold(&self) -> String {
self.0
.iter()
.map(|clause| clause.extract_threshold())
.collect::<Vec<_>>()
.join("&")
}
}
#[test]
fn test_enough_sigs() -> Result<(), Error> {
use crate::derivation::self_signing::SelfSigning;
let wt = MultiClauses::new_from_tuples(vec![vec![(1, 1)], vec![(1, 2), (1, 2), (1, 2)]]);
let dump_signatures: Vec<_> = vec![0, 1, 2, 3]
.iter()
.map(|x| AttachedSignaturePrefix::new(SelfSigning::Ed25519Sha512, vec![], x.to_owned()))
.collect();
assert!(wt.enough_signatures(&dump_signatures)?);
let enough = vec![
dump_signatures[0].clone(),
dump_signatures[1].clone(),
dump_signatures[3].clone(),
];
assert!(wt.enough_signatures(&enough)?);
let not_enough = vec![dump_signatures[0].clone()];
assert!(!wt.enough_signatures(¬_enough)?);
Ok(())
}
#[test]
pub fn test_weighted_treshold_serialization() -> Result<(), Error> {
let multi_threshold = r#"[["1"],["1/2","1/2","1/2"]]"#.to_string();
let wt: WeightedThreshold = serde_json::from_str(&multi_threshold)?;
assert!(matches!(wt, WeightedThreshold::Multi(_)));
assert_eq!(serde_json::to_string(&wt).unwrap(), multi_threshold);
let single_threshold = r#"["1/2","1/2","1/2"]"#.to_string();
let wt: WeightedThreshold = serde_json::from_str(&single_threshold)?;
assert!(matches!(wt, WeightedThreshold::Single(_)));
assert_eq!(serde_json::to_string(&wt).unwrap(), single_threshold);
Ok(())
}