use crate::reshare::lagrange::FieldElement;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OldCommitteeMember {
pub party_index: u32,
pub share: FieldElement,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewCommitteeMember {
pub party_index: u32,
pub identity_public_key: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReshareParams {
pub algorithm: String,
pub old_committee: Vec<OldCommitteeMember>,
pub old_threshold: u32,
pub new_committee: Vec<NewCommitteeMember>,
pub new_threshold: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReshareState {
Pending,
ContributionsComputed,
Distributed,
Verified,
Aborted,
}
pub struct ReshareSession {
params: ReshareParams,
state: ReshareState,
#[allow(dead_code)]
new_shares: Vec<(u32, FieldElement)>,
created_at: DateTime<Utc>,
}
#[derive(Debug, thiserror::Error)]
pub enum ReshareError {
#[error("insufficient old shares: have {have}, need {need}")]
InsufficientOldShares {
have: usize,
need: u32,
},
#[error("invalid state: current {current:?}")]
InvalidState {
current: ReshareState,
},
#[error("committee mismatch")]
CommitteeMismatch,
}
impl ReshareSession {
pub fn new(params: ReshareParams) -> Self {
Self {
params,
state: ReshareState::Pending,
new_shares: Vec::new(),
created_at: Utc::now(),
}
}
pub fn state(&self) -> ReshareState {
self.state
}
pub fn mark_contributions_computed(&mut self) -> Result<(), ReshareError> {
if self.state != ReshareState::Pending {
return Err(ReshareError::InvalidState {
current: self.state,
});
}
if (self.params.old_committee.len() as u32) < self.params.old_threshold {
return Err(ReshareError::InsufficientOldShares {
have: self.params.old_committee.len(),
need: self.params.old_threshold,
});
}
self.state = ReshareState::ContributionsComputed;
Ok(())
}
pub fn mark_distributed(&mut self) -> Result<(), ReshareError> {
if self.state != ReshareState::ContributionsComputed {
return Err(ReshareError::InvalidState {
current: self.state,
});
}
self.state = ReshareState::Distributed;
Ok(())
}
pub fn mark_verified(&mut self) -> Result<(), ReshareError> {
if self.state != ReshareState::Distributed {
return Err(ReshareError::InvalidState {
current: self.state,
});
}
self.state = ReshareState::Verified;
Ok(())
}
pub fn created_at(&self) -> DateTime<Utc> {
self.created_at
}
pub fn params(&self) -> &ReshareParams {
&self.params
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_params() -> ReshareParams {
ReshareParams {
algorithm: "FROST-ed25519".into(),
old_committee: vec![
OldCommitteeMember {
party_index: 0,
share: FieldElement::new(vec![1u8; 32]),
},
OldCommitteeMember {
party_index: 1,
share: FieldElement::new(vec![2u8; 32]),
},
OldCommitteeMember {
party_index: 2,
share: FieldElement::new(vec![3u8; 32]),
},
],
old_threshold: 2,
new_committee: vec![
NewCommitteeMember {
party_index: 0,
identity_public_key: vec![0u8; 32],
},
NewCommitteeMember {
party_index: 1,
identity_public_key: vec![1u8; 32],
},
],
new_threshold: 2,
}
}
#[test]
fn full_lifecycle() {
let mut session = ReshareSession::new(sample_params());
assert_eq!(session.state(), ReshareState::Pending);
session.mark_contributions_computed().unwrap();
assert_eq!(session.state(), ReshareState::ContributionsComputed);
session.mark_distributed().unwrap();
assert_eq!(session.state(), ReshareState::Distributed);
session.mark_verified().unwrap();
assert_eq!(session.state(), ReshareState::Verified);
}
#[test]
fn insufficient_old_shares_fails() {
let mut params = sample_params();
params.old_threshold = 5;
let mut session = ReshareSession::new(params);
let result = session.mark_contributions_computed();
assert!(matches!(
result,
Err(ReshareError::InsufficientOldShares { .. })
));
}
#[test]
fn wrong_state_fails() {
let mut session = ReshareSession::new(sample_params());
let result = session.mark_distributed();
assert!(matches!(result, Err(ReshareError::InvalidState { .. })));
}
}