1use snafu::ensure;
15
16use crate::Result;
17use crate::error;
18use crate::message::Message;
19use crate::party::PartyList;
20use crate::registry::{self, RoundResult, SessionImpl, TcSchemeKind};
21use crate::share::Share;
22
23#[derive(Debug, Clone)]
29pub struct SessionParams {
30 pub scheme: String,
32 pub parties: PartyList,
34 pub threshold: u32,
36 pub this_party_idx: usize,
38 pub local_share: Option<Share>,
41 pub message: Option<Vec<u8>>,
44}
45
46pub struct Session {
51 scheme_name: String,
52 scheme_kind: TcSchemeKind,
53 threshold: u32,
54 this_party_idx: usize,
55 party_count: usize,
56 round: u8,
57 complete: bool,
58 impl_: Box<dyn SessionImpl>,
59}
60
61impl std::fmt::Debug for Session {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.debug_struct("Session")
64 .field("scheme_name", &self.scheme_name)
65 .field("scheme_kind", &self.scheme_kind)
66 .field("threshold", &self.threshold)
67 .field("this_party_idx", &self.this_party_idx)
68 .field("party_count", &self.party_count)
69 .field("round", &self.round)
70 .field("complete", &self.complete)
71 .finish_non_exhaustive()
72 }
73}
74
75impl Session {
76 pub fn create(params: &SessionParams) -> Result<Self> {
80 params.parties.validate(params.threshold)?;
81 ensure!(
82 params.this_party_idx < params.parties.len(),
83 error::ThisPartyIdxOutOfRangeSnafu {
84 idx: params.this_party_idx,
85 party_count: params.parties.len(),
86 }
87 );
88 if let Some(share) = ¶ms.local_share {
89 share.assert_scheme(¶ms.scheme)?;
90 }
91
92 let scheme = registry::find(¶ms.scheme).ok_or_else(|| {
93 error::SchemeNotFoundSnafu {
94 name: params.scheme.clone(),
95 }
96 .build()
97 })?;
98 let impl_ = scheme.create_session(params)?;
99 Ok(Session {
100 scheme_name: scheme.name().to_string(),
101 scheme_kind: scheme.kind(),
102 threshold: params.threshold,
103 this_party_idx: params.this_party_idx,
104 party_count: params.parties.len(),
105 round: 0,
106 complete: false,
107 impl_,
108 })
109 }
110
111 pub fn scheme_name(&self) -> &str {
112 &self.scheme_name
113 }
114
115 pub fn scheme_kind(&self) -> TcSchemeKind {
116 self.scheme_kind
117 }
118
119 pub fn threshold(&self) -> u32 {
120 self.threshold
121 }
122
123 pub fn this_party_idx(&self) -> usize {
124 self.this_party_idx
125 }
126
127 pub fn party_count(&self) -> usize {
128 self.party_count
129 }
130
131 pub fn round(&self) -> u8 {
134 self.round
135 }
136
137 pub fn is_complete(&self) -> bool {
138 self.complete
139 }
140
141 pub fn round_step(&mut self, incoming: &[Message]) -> Result<RoundResult> {
148 ensure!(!self.complete, error::SessionAlreadyCompleteSnafu {});
149 self.round = self
150 .round
151 .checked_add(1)
152 .ok_or_else(|| error::RoundOverflowSnafu { round: self.round }.build())?;
153 let res = self.impl_.round(incoming)?;
154 if res.complete {
155 self.complete = true;
156 }
157 Ok(res)
158 }
159
160 pub fn result(&self) -> Result<Vec<u8>> {
163 ensure!(self.complete, error::SessionNotCompleteSnafu {});
164 self.impl_.result()
165 }
166
167 pub fn dkg_public_key(&self) -> Result<Vec<u8>> {
178 ensure!(self.complete, error::SessionNotCompleteSnafu {});
179 ensure!(
180 self.scheme_kind == TcSchemeKind::Dkg,
181 error::NotADkgSessionSnafu {
182 kind: self.scheme_kind,
183 }
184 );
185 self.impl_.result()
186 }
187
188 pub fn destroy(&mut self) {
191 self.impl_.destroy();
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use crate::party::{Party, PartyList};
199
200 struct TwoRoundScheme;
204
205 impl crate::registry::TcScheme for TwoRoundScheme {
206 fn name(&self) -> &'static str {
207 "test-two-round"
208 }
209 fn kind(&self) -> TcSchemeKind {
210 TcSchemeKind::Signature
211 }
212 fn create_session(&self, params: &SessionParams) -> Result<Box<dyn SessionImpl>> {
213 let our_id = params.parties.get(params.this_party_idx)?.id.clone();
214 Ok(Box::new(TwoRoundSession {
215 our_id,
216 message: params.message.clone().unwrap_or_default(),
217 round_done: 0,
218 }))
219 }
220 }
221
222 struct TwoRoundSession {
223 our_id: String,
224 message: Vec<u8>,
225 round_done: u8,
226 }
227
228 impl SessionImpl for TwoRoundSession {
229 fn round(&mut self, _incoming: &[Message]) -> Result<RoundResult> {
230 self.round_done += 1;
231 if self.round_done == 1 {
232 let msg = Message::broadcast(&self.our_id, 1, self.message.clone());
233 Ok(RoundResult::new(vec![msg], false))
234 } else {
235 Ok(RoundResult::done())
236 }
237 }
238 fn result(&self) -> Result<Vec<u8>> {
239 Ok(self.message.clone())
240 }
241 fn destroy(&mut self) {
242 self.message.fill(0);
243 }
244 }
245
246 inventory::submit! {
248 crate::registry::RegisteredScheme {
249 scheme: &TwoRoundScheme as &dyn crate::registry::TcScheme
250 }
251 }
252
253 fn params(scheme: &str, idx: usize, threshold: u32) -> SessionParams {
254 SessionParams {
255 scheme: scheme.to_string(),
256 parties: PartyList::from_parties(vec![
257 Party::inproc("a"),
258 Party::inproc("b"),
259 Party::inproc("c"),
260 ]),
261 threshold,
262 this_party_idx: idx,
263 local_share: None,
264 message: Some(b"hello".to_vec()),
265 }
266 }
267
268 #[test]
269 fn session_create_resolves_registered_scheme() {
270 let params = params("test-two-round", 0, 2);
271 let session = Session::create(¶ms).expect("session created");
272 assert_eq!(session.scheme_name(), "test-two-round");
273 assert_eq!(session.scheme_kind(), TcSchemeKind::Signature);
274 assert_eq!(session.threshold(), 2);
275 assert_eq!(session.this_party_idx(), 0);
276 assert_eq!(session.party_count(), 3);
277 assert_eq!(session.round(), 0);
278 assert!(!session.is_complete());
279 }
280
281 #[test]
282 fn session_create_unknown_scheme_errors() {
283 let mut params = params("test-two-round", 0, 2);
284 params.scheme = "no-such-scheme".to_string();
285 let err = Session::create(¶ms).unwrap_err();
286 assert!(matches!(err, error::Error::SchemeNotFound { .. }));
287 }
288
289 #[test]
290 fn session_create_rejects_bad_party_index() {
291 let params = params("test-two-round", 99, 2);
292 let err = Session::create(¶ms).unwrap_err();
293 assert!(matches!(
294 err,
295 error::Error::ThisPartyIdxOutOfRange {
296 idx: 99,
297 party_count: 3,
298 ..
299 }
300 ));
301 }
302
303 #[test]
304 fn session_create_rejects_threshold_above_party_count() {
305 let params = params("test-two-round", 0, 99);
306 let err = Session::create(¶ms).unwrap_err();
307 assert!(matches!(err, error::Error::ThresholdTooLarge { .. }));
308 }
309
310 #[test]
311 fn session_create_rejects_share_scheme_mismatch() {
312 let mut params = params("test-two-round", 0, 2);
313 params.local_share = Some(Share::new("wrong-scheme", vec![1]));
314 let err = Session::create(¶ms).unwrap_err();
315 assert!(matches!(err, error::Error::ShareSchemeMismatch { .. }));
316 }
317
318 #[test]
319 fn session_round_progresses_then_completes() {
320 let params = params("test-two-round", 0, 2);
321 let mut session = Session::create(¶ms).expect("session");
322
323 let r1 = session.round_step(&[]).expect("round 1");
325 assert!(!r1.complete);
326 assert_eq!(r1.outgoing.len(), 1);
327 assert!(r1.outgoing[0].is_broadcast());
328 assert_eq!(session.round(), 1);
329
330 let r2 = session.round_step(&[]).expect("round 2");
332 assert!(r2.complete);
333 assert!(session.is_complete());
334
335 let result = session.result().expect("result");
336 assert_eq!(result, b"hello");
337 }
338
339 #[test]
340 fn session_round_after_complete_errors() {
341 let params = params("test-two-round", 0, 2);
342 let mut session = Session::create(¶ms).expect("session");
343 session.round_step(&[]).expect("round 1");
344 session.round_step(&[]).expect("round 2 completes");
345 let err = session.round_step(&[]).unwrap_err();
346 assert!(matches!(err, error::Error::SessionAlreadyComplete { .. }));
347 }
348
349 #[test]
350 fn session_result_before_complete_errors() {
351 let params = params("test-two-round", 0, 2);
352 let session = Session::create(¶ms).expect("session");
353 let err = session.result().unwrap_err();
354 assert!(matches!(err, error::Error::SessionNotComplete { .. }));
355 }
356
357 #[test]
358 fn session_dkg_public_key_rejects_non_dkg() {
359 let params = params("test-two-round", 0, 2);
360 let mut session = Session::create(¶ms).expect("session");
361 session.round_step(&[]).expect("round 1");
362 session.round_step(&[]).expect("round 2 completes");
363 let err = session.dkg_public_key().unwrap_err();
364 assert!(matches!(err, error::Error::NotADkgSession { .. }));
365 }
366}