use crate::ecies;
use crate::types::ShareIndex;
use fastcrypto::error::{FastCryptoError, FastCryptoResult};
use fastcrypto::groups::GroupElement;
use fastcrypto::hash::{Blake2b256, Digest, HashFunction};
use serde::{Deserialize, Serialize};
use tracing::debug;
pub type PartyId = u16;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Node<G: GroupElement> {
pub id: PartyId,
pub pk: ecies::PublicKey<G>,
pub weight: u16, }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Nodes<G: GroupElement> {
nodes: Vec<Node<G>>, total_weight: u16, accumulated_weights: Vec<u16>, nodes_with_nonzero_weight: Vec<u16>, }
impl<G: GroupElement + Serialize> Nodes<G> {
const MAX_NODES: usize = 1000;
pub fn new(nodes: Vec<Node<G>>) -> FastCryptoResult<Self> {
let mut nodes = nodes;
nodes.sort_by_key(|n| n.id);
if (0..nodes.len()).any(|i| (nodes[i].id as usize) != i) {
return Err(FastCryptoError::InvalidInput);
}
if nodes.is_empty() || nodes.len() > Self::MAX_NODES {
return Err(FastCryptoError::InvalidInput);
}
let total_weight = nodes.iter().map(|n| n.weight as u32).sum::<u32>();
if total_weight > u16::MAX as u32 || total_weight == 0 {
return Err(FastCryptoError::InvalidInput);
}
let total_weight = total_weight as u16;
let accumulated_weights = Self::get_accumulated_weights(&nodes);
let nodes_with_nonzero_weight = Self::filter_nonzero_weights(&nodes);
Ok(Self {
nodes,
total_weight,
accumulated_weights,
nodes_with_nonzero_weight,
})
}
fn filter_nonzero_weights(nodes: &[Node<G>]) -> Vec<u16> {
nodes
.iter()
.enumerate()
.filter_map(|(i, n)| if n.weight > 0 { Some(i as u16) } else { None })
.collect::<Vec<_>>()
}
fn get_accumulated_weights(nodes: &[Node<G>]) -> Vec<u16> {
nodes
.iter()
.filter_map(|n| if n.weight > 0 { Some(n.weight) } else { None })
.scan(0, |accumulated_weight, weight| {
*accumulated_weight += weight;
Some(*accumulated_weight)
})
.collect::<Vec<_>>()
}
pub fn total_weight(&self) -> u16 {
self.total_weight
}
pub fn num_nodes(&self) -> usize {
self.nodes.len()
}
pub fn share_ids_iter(&self) -> impl Iterator<Item = ShareIndex> {
(1..=self.total_weight).map(|i| ShareIndex::new(i).expect("nonzero"))
}
pub fn share_id_to_node(&self, share_id: &ShareIndex) -> FastCryptoResult<&Node<G>> {
let nonzero_node_id = self
.accumulated_weights
.binary_search(&share_id.get())
.unwrap_or_else(|i| i);
match self.nodes_with_nonzero_weight.get(nonzero_node_id) {
Some(node_id) => self.node_id_to_node(*node_id),
None => Err(FastCryptoError::InvalidInput),
}
}
pub fn node_id_to_node(&self, party_id: PartyId) -> FastCryptoResult<&Node<G>> {
self.nodes
.get(party_id as usize)
.ok_or(FastCryptoError::InvalidInput)
}
pub fn share_ids_of(&self, id: PartyId) -> FastCryptoResult<Vec<ShareIndex>> {
self.node_id_to_node(id)?;
Ok(self
.share_ids_iter()
.filter(|share_id| self.share_id_to_node(share_id).expect("valid share id").id == id)
.collect::<Vec<_>>())
}
pub fn iter(&self) -> impl Iterator<Item = &Node<G>> {
self.nodes.iter()
}
pub fn hash(&self) -> Digest<32> {
let mut hash = Blake2b256::default();
hash.update(bcs::to_bytes(&self.nodes).expect("should serialize"));
hash.finalize()
}
pub fn new_reduced(
nodes_vec: Vec<Node<G>>,
t: u16,
allowed_delta: u16,
total_weight_lower_bound: u16,
) -> FastCryptoResult<(Self, u16)> {
let n = Self::new(nodes_vec)?; assert!(total_weight_lower_bound <= n.total_weight && total_weight_lower_bound > 0);
let mut max_d = 1;
for d in 2..=40 {
let new_total_weight = n.nodes.iter().map(|n| n.weight / d).sum::<u16>();
if new_total_weight < total_weight_lower_bound {
break;
}
let delta = n.nodes.iter().map(|n| n.weight % d).sum::<u16>();
if delta <= allowed_delta {
max_d = d;
}
}
debug!(
"Nodes::reduce reducing from {} with max_d {}, allowed_delta {}, total_weight_lower_bound {}",
n.total_weight, max_d, allowed_delta, total_weight_lower_bound
);
let nodes = n
.nodes
.iter()
.map(|n| Node {
id: n.id,
pk: n.pk.clone(),
weight: n.weight / max_d,
})
.collect::<Vec<_>>();
let accumulated_weights = Self::get_accumulated_weights(&nodes);
let nodes_with_nonzero_weight = Self::filter_nonzero_weights(&nodes);
let total_weight = nodes.iter().map(|n| n.weight).sum::<u16>();
let new_t = t / max_d + (t % max_d != 0) as u16;
Ok((
Self {
nodes,
total_weight,
accumulated_weights,
nodes_with_nonzero_weight,
},
new_t,
))
}
}