confium_tc/reshare/
session.rs1use crate::reshare::lagrange::FieldElement;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct OldCommitteeMember {
10 pub party_index: u32,
12 pub share: FieldElement,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct NewCommitteeMember {
19 pub party_index: u32,
21 pub identity_public_key: Vec<u8>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ReshareParams {
28 pub algorithm: String,
30 pub old_committee: Vec<OldCommitteeMember>,
32 pub old_threshold: u32,
34 pub new_committee: Vec<NewCommitteeMember>,
36 pub new_threshold: u32,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum ReshareState {
44 Pending,
46 ContributionsComputed,
48 Distributed,
50 Verified,
52 Aborted,
54}
55
56pub struct ReshareSession {
58 params: ReshareParams,
59 state: ReshareState,
60 #[allow(dead_code)]
64 new_shares: Vec<(u32, FieldElement)>,
65 created_at: DateTime<Utc>,
66}
67
68#[derive(Debug, thiserror::Error)]
70pub enum ReshareError {
71 #[error("insufficient old shares: have {have}, need {need}")]
73 InsufficientOldShares {
74 have: usize,
76 need: u32,
78 },
79 #[error("invalid state: current {current:?}")]
81 InvalidState {
82 current: ReshareState,
84 },
85 #[error("committee mismatch")]
87 CommitteeMismatch,
88}
89
90impl ReshareSession {
91 pub fn new(params: ReshareParams) -> Self {
93 Self {
94 params,
95 state: ReshareState::Pending,
96 new_shares: Vec::new(),
97 created_at: Utc::now(),
98 }
99 }
100
101 pub fn state(&self) -> ReshareState {
103 self.state
104 }
105
106 pub fn mark_contributions_computed(&mut self) -> Result<(), ReshareError> {
108 if self.state != ReshareState::Pending {
109 return Err(ReshareError::InvalidState {
110 current: self.state,
111 });
112 }
113 if (self.params.old_committee.len() as u32) < self.params.old_threshold {
114 return Err(ReshareError::InsufficientOldShares {
115 have: self.params.old_committee.len(),
116 need: self.params.old_threshold,
117 });
118 }
119 self.state = ReshareState::ContributionsComputed;
120 Ok(())
121 }
122
123 pub fn mark_distributed(&mut self) -> Result<(), ReshareError> {
125 if self.state != ReshareState::ContributionsComputed {
126 return Err(ReshareError::InvalidState {
127 current: self.state,
128 });
129 }
130 self.state = ReshareState::Distributed;
131 Ok(())
132 }
133
134 pub fn mark_verified(&mut self) -> Result<(), ReshareError> {
136 if self.state != ReshareState::Distributed {
137 return Err(ReshareError::InvalidState {
138 current: self.state,
139 });
140 }
141 self.state = ReshareState::Verified;
142 Ok(())
143 }
144
145 pub fn created_at(&self) -> DateTime<Utc> {
147 self.created_at
148 }
149
150 pub fn params(&self) -> &ReshareParams {
152 &self.params
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn sample_params() -> ReshareParams {
161 ReshareParams {
162 algorithm: "FROST-ed25519".into(),
163 old_committee: vec![
164 OldCommitteeMember {
165 party_index: 0,
166 share: FieldElement::new(vec![1u8; 32]),
167 },
168 OldCommitteeMember {
169 party_index: 1,
170 share: FieldElement::new(vec![2u8; 32]),
171 },
172 OldCommitteeMember {
173 party_index: 2,
174 share: FieldElement::new(vec![3u8; 32]),
175 },
176 ],
177 old_threshold: 2,
178 new_committee: vec![
179 NewCommitteeMember {
180 party_index: 0,
181 identity_public_key: vec![0u8; 32],
182 },
183 NewCommitteeMember {
184 party_index: 1,
185 identity_public_key: vec![1u8; 32],
186 },
187 ],
188 new_threshold: 2,
189 }
190 }
191
192 #[test]
193 fn full_lifecycle() {
194 let mut session = ReshareSession::new(sample_params());
195 assert_eq!(session.state(), ReshareState::Pending);
196 session.mark_contributions_computed().unwrap();
197 assert_eq!(session.state(), ReshareState::ContributionsComputed);
198 session.mark_distributed().unwrap();
199 assert_eq!(session.state(), ReshareState::Distributed);
200 session.mark_verified().unwrap();
201 assert_eq!(session.state(), ReshareState::Verified);
202 }
203
204 #[test]
205 fn insufficient_old_shares_fails() {
206 let mut params = sample_params();
207 params.old_threshold = 5;
208 let mut session = ReshareSession::new(params);
209 let result = session.mark_contributions_computed();
210 assert!(matches!(
211 result,
212 Err(ReshareError::InsufficientOldShares { .. })
213 ));
214 }
215
216 #[test]
217 fn wrong_state_fails() {
218 let mut session = ReshareSession::new(sample_params());
219 let result = session.mark_distributed();
221 assert!(matches!(result, Err(ReshareError::InvalidState { .. })));
222 }
223}