1use std::{collections::HashMap, iter::repeat};
6
7use borsh::{BorshDeserialize, BorshSerialize};
8use thiserror::Error;
9
10use crate::types::{Ciphertext, RandomId, SecretDigest, SecretIdent, SecretKey};
11
12#[derive(Error, Debug, PartialEq, Eq)]
13pub enum Error {
14 #[error("invalid random status: {0:?}")]
15 InvalidRandomStatus(RandomStatus),
16
17 #[error("invalid operator, expected: {0}, actual: {1}")]
18 InvalidOperator(String, String),
19
20 #[error("duplicated mask")]
21 DuplicatedMask,
22
23 #[error("duplicated lock")]
24 DuplicatedLock,
25
26 #[error("can't mask")]
27 CantMask,
28
29 #[error("invalid ciphertexts")]
30 InvalidCiphertexts,
31
32 #[error("update expired")]
33 UpdateExpired,
34
35 #[error("invalid index")]
36 InvalidIndex,
37
38 #[error("ciphertext already assigned")]
39 CiphertextAlreadyAssigned,
40
41 #[error("invalid mask provider")]
42 InvalidMaskProvider,
43
44 #[error("invalid lock provider")]
45 InvalidLockProvider,
46
47 #[error("duplicated secret")]
48 DuplicatedSecret,
49
50 #[error("invalid secret")]
51 InvalidSecret,
52
53 #[error("randomness is not ready")]
54 RandomnessNotReady,
55
56 #[error("secrets are not ready")]
57 SecretsNotReady,
58
59 #[error("No enough servers")]
60 NoEnoughServer,
61
62 #[error("Invalid reveal operation")]
63 InvalidRevealOperation,
64
65 #[error("Unreachable: {0}")]
66 Unreachable(String),
67}
68
69impl From<Error> for crate::error::Error {
70 fn from(e: Error) -> Self {
71 Self::RandomizationError(e.to_string())
72 }
73}
74
75pub type Result<T> = std::result::Result<T, Error>;
76
77#[derive(Debug, BorshSerialize, BorshDeserialize, PartialEq, Eq)]
78pub enum RandomMode {
79 Shuffler,
80 Drawer,
81}
82
83#[derive(Debug, BorshDeserialize, BorshSerialize, PartialEq, Eq)]
84pub enum RandomSpec {
85 ShuffledList {
86 options: Vec<String>,
87 },
88 Lottery {
89 options_and_weights: HashMap<String, u16>,
90 },
91}
92
93impl RandomSpec {
94 pub fn as_options(self) -> Vec<String> {
95 match self {
96 RandomSpec::ShuffledList { options } => options,
97 RandomSpec::Lottery {
98 options_and_weights,
99 } => options_and_weights
100 .into_iter()
101 .flat_map(|(o, w)| repeat(o).take(w as _))
102 .collect(),
103 }
104 }
105
106 pub fn deck_of_cards() -> Self {
110 RandomSpec::ShuffledList {
111 options: vec![
112 "ha".into(),
113 "h2".into(),
114 "h3".into(),
115 "h4".into(),
116 "h5".into(),
117 "h6".into(),
118 "h7".into(),
119 "h8".into(),
120 "h9".into(),
121 "ht".into(),
122 "hj".into(),
123 "hq".into(),
124 "hk".into(),
125 "sa".into(),
126 "s2".into(),
127 "s3".into(),
128 "s4".into(),
129 "s5".into(),
130 "s6".into(),
131 "s7".into(),
132 "s8".into(),
133 "s9".into(),
134 "st".into(),
135 "sj".into(),
136 "sq".into(),
137 "sk".into(),
138 "da".into(),
139 "d2".into(),
140 "d3".into(),
141 "d4".into(),
142 "d5".into(),
143 "d6".into(),
144 "d7".into(),
145 "d8".into(),
146 "d9".into(),
147 "dt".into(),
148 "dj".into(),
149 "dq".into(),
150 "dk".into(),
151 "ca".into(),
152 "c2".into(),
153 "c3".into(),
154 "c4".into(),
155 "c5".into(),
156 "c6".into(),
157 "c7".into(),
158 "c8".into(),
159 "c9".into(),
160 "ct".into(),
161 "cj".into(),
162 "cq".into(),
163 "ck".into(),
164 ],
165 }
166 }
167
168 pub fn shuffled_list(options: Vec<String>) -> Self {
169 RandomSpec::ShuffledList { options }
170 }
171
172 pub fn lottery(options_and_weights: HashMap<String, u16>) -> Self {
173 RandomSpec::Lottery {
174 options_and_weights,
175 }
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
180pub enum MaskStatus {
181 Required,
182 Applied,
183 Removed,
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
187pub struct Mask {
188 pub status: MaskStatus,
189 pub owner: String,
190}
191
192impl Mask {
193 pub fn new<S: Into<String>>(owner: S) -> Self {
194 Self {
195 status: MaskStatus::Required,
196 owner: owner.into(),
197 }
198 }
199
200 pub fn is_required(&self) -> bool {
201 self.status == MaskStatus::Required
202 }
203
204 pub fn is_applied(&self) -> bool {
205 self.status == MaskStatus::Applied
206 }
207
208 pub fn is_removed(&self) -> bool {
209 self.status == MaskStatus::Removed
210 }
211
212 pub fn belongs_to<S: AsRef<str>>(&self, addr: S) -> bool {
213 self.owner.eq(addr.as_ref())
214 }
215}
216
217#[derive(Clone, Default, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
218pub struct Lock {
219 pub digest: SecretDigest,
220 pub owner: String,
221}
222
223impl Lock {
224 pub fn new<S: Into<String>>(owner: S, digest: SecretDigest) -> Self {
225 Self {
226 digest,
227 owner: owner.into(),
228 }
229 }
230}
231
232#[derive(Debug, Default, PartialEq, Eq, BorshDeserialize, BorshSerialize, Clone)]
233pub enum CipherOwner {
234 #[default]
235 Unclaimed,
236 Assigned(String),
238 MultiAssigned(Vec<String>),
240 Revealed,
241}
242
243#[derive(Debug, Default, PartialEq, Eq, BorshDeserialize, BorshSerialize, Clone)]
246pub struct LockedCiphertext {
247 pub locks: Vec<Lock>,
248 pub owner: CipherOwner,
249 pub ciphertext: Ciphertext,
250}
251
252impl LockedCiphertext {
253 pub fn new(text: Ciphertext) -> Self {
254 Self {
255 locks: vec![],
256 owner: CipherOwner::Unclaimed,
257 ciphertext: text,
258 }
259 }
260
261 pub fn ciphertext(&self) -> &Ciphertext {
262 &self.ciphertext
263 }
264}
265
266#[derive(Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, Clone)]
267pub struct Share {
268 from_addr: String,
269 to_addr: Option<String>,
271 index: usize,
272 secret: Option<SecretKey>,
274}
275
276#[derive(Default, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, Clone)]
277pub enum RandomStatus {
278 #[default]
279 Ready,
280 Locking(String), Masking(String), WaitingSecrets, Shared, }
285
286#[derive(Default, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, Clone)]
288pub struct RandomState {
289 pub id: RandomId,
290 pub size: usize,
291 pub owners: Vec<String>,
292 pub options: Vec<String>,
293 pub status: RandomStatus,
294 pub masks: Vec<Mask>,
295 pub ciphertexts: Vec<LockedCiphertext>,
296 pub secret_shares: Vec<Share>,
297 pub revealed: HashMap<usize, String>,
298}
299
300impl RandomState {
301 pub fn is_fully_masked(&self) -> bool {
302 self.masks.iter().all(|m| !m.is_required())
303 }
304
305 pub fn is_fully_locked(&self) -> bool {
306 self.masks.iter().all(|m| m.is_removed())
307 }
308
309 pub fn is_shared(&self) -> bool {
310 self.status == RandomStatus::Shared
311 }
312
313 pub fn is_ready(&self) -> bool {
314 self.status == RandomStatus::Ready
315 }
316
317 pub fn get_ciphertext(&self, index: usize) -> Option<&LockedCiphertext> {
318 self.ciphertexts.get(index)
319 }
320
321 pub fn get_ciphertext_unchecked(&self, index: usize) -> &LockedCiphertext {
322 &self.ciphertexts[index]
323 }
324
325 fn get_ciphertext_mut(&mut self, index: usize) -> Option<&mut LockedCiphertext> {
326 self.ciphertexts.get_mut(index)
327 }
328
329 pub fn try_new(id: RandomId, spec: RandomSpec, owners: &[String]) -> Result<Self> {
330 let options = spec.as_options();
331 let size = options.len();
332
333 let ciphertexts = options
334 .iter()
335 .map(|o| {
336 let ciphertext = o.as_bytes().to_owned();
337 LockedCiphertext::new(ciphertext)
338 })
339 .collect();
340
341 let masks = owners.iter().map(Mask::new).collect();
342
343 let status = if let Some(owner) = owners.first() {
344 RandomStatus::Masking(owner.clone())
345 } else {
346 return Err(Error::NoEnoughServer);
347 };
348
349 Ok(Self {
350 id,
351 size,
352 masks,
353 owners: owners.to_owned(),
354 options: options.clone(),
355 status,
356 ciphertexts,
357 revealed: HashMap::new(),
358 secret_shares: Vec::new(),
359 })
360 }
361
362 pub fn mask<S: AsRef<str>>(&mut self, addr: S, mut ciphertexts: Vec<Ciphertext>) -> Result<()> {
363 match self.status {
364 RandomStatus::Masking(ref a) => {
365 let addr = addr.as_ref();
366 if a.ne(addr) {
367 return Err(Error::InvalidOperator(a.into(), addr.into()));
368 }
369 if let Some(mask) = self.masks.iter_mut().find(|m| m.owner.eq(addr)) {
370 if !mask.is_required() {
371 return Err(Error::DuplicatedMask);
372 } else {
373 if ciphertexts.len() != self.ciphertexts.len() {
374 return Err(Error::InvalidCiphertexts);
375 }
376 for c in self.ciphertexts.iter_mut() {
377 c.ciphertext = ciphertexts.remove(0);
378 }
379 mask.status = MaskStatus::Applied;
380 self.update_status();
381 }
382 } else {
383 return Err(Error::Unreachable("Mask is None, this is an internal error".into()));
385 }
386 Ok(())
387 }
388 _ => Err(Error::InvalidRandomStatus(self.status.clone())),
389 }
390 }
391
392 pub fn lock<S>(
393 &mut self,
394 addr: S,
395 mut ciphertexts_and_digests: Vec<(Ciphertext, SecretDigest)>,
396 ) -> Result<()>
397 where
398 S: Into<String> + AsRef<str> + Clone,
399 {
400 match self.status {
401 RandomStatus::Locking(ref a) => {
402 let addr = addr.as_ref();
403 if a.ne(addr) {
404 return Err(Error::InvalidOperator(a.into(), addr.into()));
405 }
406 if let Some(mask) = self.masks.iter_mut().find(|m| m.owner.eq(addr)) {
407 if mask.status.eq(&MaskStatus::Removed) {
408 return Err(Error::DuplicatedLock);
409 }
410 if ciphertexts_and_digests.len() != self.ciphertexts.len() {
411 return Err(Error::InvalidCiphertexts);
412 }
413 mask.status = MaskStatus::Removed;
414 for c in self.ciphertexts.iter_mut() {
415 let (new_text, digest) = ciphertexts_and_digests.remove(0);
416 c.ciphertext = new_text;
417 c.locks.push(Lock::new(addr.to_owned(), digest));
418 }
419 self.update_status();
420 } else {
421 return Err(Error::Unreachable("Mask is None, this is an internal error".into()));
422 }
423 Ok(())
424 }
425 _ => Err(Error::InvalidRandomStatus(self.status.clone())),
426 }
427 }
428
429 pub fn assign<S>(&mut self, addr: S, indexes: Vec<usize>) -> Result<()>
430 where
431 S: ToOwned<Owned = String>,
432 {
433 if !matches!(
434 self.status,
435 RandomStatus::Shared | RandomStatus::Ready | RandomStatus::WaitingSecrets
436 ) {
437 return Err(Error::InvalidRandomStatus(self.status.clone()));
438 }
439
440 if indexes
441 .iter()
442 .filter_map(|i| self.get_ciphertext(*i))
443 .any(|c| matches!(c.owner, CipherOwner::Assigned(_) | CipherOwner::Revealed))
444 {
445 return Err(Error::CiphertextAlreadyAssigned);
446 }
447
448 for i in indexes.into_iter() {
449 if let Some(c) = self.get_ciphertext_mut(i) {
450 c.owner = CipherOwner::Assigned(addr.to_owned());
451 }
452 let secrets = &mut self.secret_shares;
453 for o in self.owners.iter() {
454 secrets.push(Share {
455 from_addr: o.to_owned(),
456 to_addr: Some(addr.to_owned()),
457 index: i,
458 secret: None,
459 });
460 }
461 }
462
463 self.status = RandomStatus::WaitingSecrets;
464
465 Ok(())
466 }
467
468 fn add_secret_share(&mut self, share: Share) {
469 if self
470 .secret_shares
471 .iter()
472 .find(|ss| {
473 ss.from_addr.eq(&share.from_addr)
474 && ss.to_addr.eq(&share.to_addr)
475 && ss.index == share.index
476 })
477 .is_none()
478 {
479 self.secret_shares.push(share);
480 }
481 }
482 pub fn reveal(&mut self, indexes: Vec<usize>) -> Result<()> {
483 if !matches!(
484 self.status,
485 RandomStatus::Shared | RandomStatus::Ready | RandomStatus::WaitingSecrets
486 ) {
487 return Err(Error::InvalidRandomStatus(self.status.clone()));
488 }
489
490 for i in indexes.into_iter() {
491 if let Some(c) = self.get_ciphertext_mut(i) {
492 if c.owner != CipherOwner::Revealed {
493 c.owner = CipherOwner::Revealed;
494 let owners: Vec<String> = self.owners.iter().map(|o| o.to_owned()).collect();
495 for o in owners.into_iter() {
496 self.add_secret_share(Share {
497 from_addr: o,
498 to_addr: None,
499 index: i,
500 secret: None,
501 })
502 }
503 }
504 }
505 }
506
507 self.status = RandomStatus::WaitingSecrets;
508 Ok(())
509 }
510
511 pub fn list_required_secrets_by_from_addr(&self, from_addr: &str) -> Vec<SecretIdent> {
512 self.secret_shares
513 .iter()
514 .filter(|ss| ss.secret.is_none() && ss.from_addr.eq(from_addr))
515 .map(|ss| SecretIdent {
516 from_addr: ss.from_addr.clone(),
517 to_addr: ss.to_addr.clone(),
518 random_id: self.id,
519 index: ss.index as usize,
520 })
521 .collect()
522 }
523
524 pub fn list_revealed_secrets(&self) -> Result<HashMap<usize, Vec<Ciphertext>>> {
525 if self.status != RandomStatus::Ready {
526 return Err(Error::SecretsNotReady);
527 }
528 let ret = self
529 .secret_shares
530 .iter()
531 .filter(|ss| ss.to_addr.is_none())
532 .fold(HashMap::new(), |mut acc, ss| {
533 acc.entry(ss.index as usize)
534 .and_modify(|v: &mut Vec<SecretKey>| {
535 v.push(ss.secret.as_ref().unwrap().clone())
536 })
537 .or_insert_with(|| vec![ss.secret.as_ref().unwrap().clone()]);
538 acc
539 });
540 Ok(ret)
541 }
542
543 pub fn list_assigned_ciphertexts(&self, addr: &str) -> HashMap<usize, Ciphertext> {
546 self.ciphertexts
547 .iter()
548 .enumerate()
549 .filter_map(|(i, c)| {
550 if matches!(&c.owner, CipherOwner::Assigned(a) if a.eq(addr)) {
551 Some((i as usize, c.ciphertext.clone()))
552 } else {
553 None
554 }
555 })
556 .collect()
557 }
558
559 pub fn list_revealed_ciphertexts(&self) -> HashMap<usize, Ciphertext> {
560 self.ciphertexts
561 .iter()
562 .enumerate()
563 .filter_map(|(i, c)| {
564 if c.owner == CipherOwner::Revealed {
565 Some((i as usize, c.ciphertext.clone()))
566 } else {
567 None
568 }
569 })
570 .collect()
571 }
572
573 pub fn list_shared_secrets(&self, to_addr: &str) -> Result<HashMap<usize, Vec<SecretKey>>> {
577 if self.status.ne(&RandomStatus::Ready) {
578 return Err(Error::SecretsNotReady);
579 }
580
581 Ok(self
582 .secret_shares
583 .iter()
584 .filter(|ss| ss.to_addr.is_some() && ss.to_addr.as_ref().unwrap().eq(to_addr))
585 .fold(HashMap::new(), |mut acc, ss| {
586 acc.entry(ss.index)
587 .and_modify(|v: &mut Vec<SecretKey>| {
588 v.push(ss.secret.as_ref().unwrap().clone())
589 })
590 .or_insert_with(|| vec![ss.secret.as_ref().unwrap().clone()]);
591 acc
592 }))
593 }
594
595 pub fn add_revealed(&mut self, revealed: HashMap<usize, String>) -> Result<()> {
596 for (index, value) in revealed.into_iter() {
597 if index >= self.size {
598 return Err(Error::InvalidIndex);
599 }
600 self.revealed.entry(index).or_insert(value);
601 }
602 Ok(())
603 }
604
605 pub fn get_revealed(&self) -> &HashMap<usize, String> {
606 &self.revealed
607 }
608
609 pub fn add_secret(
610 &mut self,
611 from_addr: String,
612 to_addr: Option<String>,
613 index: usize,
614 secret: SecretKey,
615 ) -> Result<()> {
616 if let Some(secret_share) = self
617 .secret_shares
618 .iter_mut()
619 .find(|ss| ss.from_addr.eq(&from_addr) && ss.to_addr.eq(&to_addr) && ss.index == index)
620 {
621 match secret_share.secret {
622 None => {
623 if let Some(_ciphertext) = self.ciphertexts.get(secret_share.index) {
624 secret_share.secret = Some(secret);
631 } else {
632 return Err(Error::InvalidSecret);
633 }
634 }
635 Some(_) => return Err(Error::DuplicatedSecret),
636 }
637 }
638 self.update_status();
639 Ok(())
640 }
641
642 pub fn list_operating_addrs(&self) -> Vec<String> {
644 match &self.status {
645 RandomStatus::Shared => Vec::new(),
646 RandomStatus::Ready => Vec::new(),
647 RandomStatus::Locking(addr) => vec![addr.clone()],
648 RandomStatus::Masking(addr) => vec![addr.clone()],
649 RandomStatus::WaitingSecrets => self
650 .secret_shares
651 .iter()
652 .filter(|s| s.secret.is_none())
653 .map(|s| s.from_addr.to_owned())
654 .collect(),
655 }
656 }
657
658 pub fn update_status(&mut self) {
660 if matches!(self.status, RandomStatus::Locking(_))
661 && self.masks.iter().all(|m| m.is_removed())
662 {
663 self.status = RandomStatus::Ready;
665 } else if let Some(mask) = self.masks.iter().find(|m| m.is_required()) {
666 self.status = RandomStatus::Masking(mask.owner.clone());
667 } else if let Some(mask) = self.masks.iter().find(|m| m.is_applied()) {
668 self.status = RandomStatus::Locking(mask.owner.clone());
669 } else if self.secret_shares.iter().any(|s| s.secret.is_none()) {
670 self.status = RandomStatus::WaitingSecrets;
671 } else {
672 self.status = RandomStatus::Shared;
673 }
674 }
675}
676
677#[cfg(test)]
678mod tests {
679
680 use super::*;
681
682 #[test]
683 fn test_list_required_secrets() -> Result<()> {
684 let random = RandomSpec::shuffled_list(vec!["a".into(), "b".into(), "c".into()]);
685 let mut state = RandomState::try_new(0, random, &["alice".into(), "bob".into()])?;
686 state.mask("alice", vec![vec![1], vec![2], vec![3]])?;
687 state.mask("bob", vec![vec![1], vec![2], vec![3]])?;
688 state.lock(
689 "alice",
690 vec![(vec![1], vec![1]), (vec![2], vec![2]), (vec![3], vec![3])],
691 )?;
692 state.lock(
693 "bob",
694 vec![(vec![1], vec![1]), (vec![2], vec![2]), (vec![3], vec![3])],
695 )?;
696 state.reveal(vec![0])?;
697 assert_eq!(1, state.list_required_secrets_by_from_addr("alice").len());
698 assert_eq!(1, state.list_required_secrets_by_from_addr("bob").len());
699 state.add_secret("alice".into(), None, 0, vec![1, 2, 3])?;
700 state.reveal(vec![0])?;
702 assert_eq!(0, state.list_required_secrets_by_from_addr("alice").len());
703 assert_eq!(1, state.list_required_secrets_by_from_addr("bob").len());
704 state.add_secret("bob".into(), None, 0, vec![1, 2, 3])?;
705 assert_eq!(0, state.list_required_secrets_by_from_addr("alice").len());
706 assert_eq!(0, state.list_required_secrets_by_from_addr("bob").len());
707 assert_eq!(RandomStatus::Shared, state.status);
708 Ok(())
709 }
710
711 #[test]
712 fn test_new_random_spec() -> Result<()> {
713 let random = RandomSpec::shuffled_list(vec!["a".into(), "b".into(), "c".into()]);
714 let state =
715 RandomState::try_new(0, random, &["alice".into(), "bob".into(), "charlie".into()])?;
716 assert_eq!(3, state.masks.len());
717 Ok(())
718 }
719
720 #[test]
721 fn test_mask_serialize() {
722 let mask = Mask::new("hello");
723 let encoded = mask.try_to_vec().unwrap();
724 let decoded = Mask::try_from_slice(&encoded).unwrap();
725 assert_eq!(mask, decoded);
726 }
727
728 #[test]
729 fn test_mask() -> Result<()> {
730 let random = RandomSpec::shuffled_list(vec!["a".into(), "b".into(), "c".into()]);
731 let mut state = RandomState::try_new(0, random, &["alice".into(), "bob".into()])?;
732 assert_eq!(RandomStatus::Masking("alice".into()), state.status);
733 state
734 .mask("alice", vec![vec![1], vec![2], vec![3]])
735 .expect("failed to mask");
736
737 assert_eq!(RandomStatus::Masking("bob".into()), state.status);
738 assert_eq!(false, state.is_fully_masked());
739 state
740 .mask("bob", vec![vec![1], vec![2], vec![3]])
741 .expect("failed to mask");
742 assert_eq!(RandomStatus::Locking("alice".into()), state.status);
743 assert_eq!(true, state.is_fully_masked());
744 Ok(())
745 }
746
747 #[test]
748 fn test_add_secret_share() -> Result<()> {
749 let random = RandomSpec::shuffled_list(vec!["a".into(), "b".into(), "c".into()]);
750 let mut state = RandomState::try_new(0, random, &["alice".into(), "bob".into()])?;
751 let share1 = Share {
752 from_addr: "alice".into(),
753 to_addr: None,
754 index: 0,
755 secret: None,
756 };
757 let share2 = Share {
758 from_addr: "alice".into(),
759 to_addr: None,
760 index: 0,
761 secret: Some(vec![1]),
762 };
763 state.add_secret_share(share1);
764 state.add_secret_share(share2);
765 assert_eq!(state.secret_shares.len(), 1);
766 Ok(())
767 }
768
769 #[test]
770 fn test_lock() -> Result<()> {
771 let random = RandomSpec::shuffled_list(vec!["a".into(), "b".into(), "c".into()]);
772 let mut state = RandomState::try_new(0, random, &["alice".into(), "bob".into()])?;
773 state
774 .mask("alice", vec![vec![1], vec![2], vec![3]])
775 .expect("failed to mask");
776 state
777 .lock(
778 "alice",
779 vec![(vec![1], vec![1]), (vec![2], vec![2]), (vec![3], vec![3])],
780 )
781 .expect_err("should failed to lock");
782 state
783 .mask("bob", vec![vec![1], vec![2], vec![3]])
784 .expect("failed to mask");
785 assert_eq!(RandomStatus::Locking("alice".into()), state.status);
786 state
787 .lock(
788 "alice",
789 vec![(vec![1], vec![1]), (vec![2], vec![2]), (vec![3], vec![3])],
790 )
791 .expect("failed to lock");
792 assert_eq!(RandomStatus::Locking("bob".into()), state.status);
793 assert_eq!(false, state.is_fully_locked());
794 state
795 .lock(
796 "bob",
797 vec![(vec![1], vec![1]), (vec![2], vec![2]), (vec![3], vec![3])],
798 )
799 .expect("failed to lock");
800 assert_eq!(RandomStatus::Ready, state.status);
801 assert_eq!(true, state.is_fully_locked());
802 Ok(())
803 }
804}