race_api/
decision.rs

1//! Decision handling
2//!
3//! Player can submit an immutable decision, and hide it from seeing by others
4//! Later the decision can be revealed by share the secrets.
5
6use borsh::{BorshDeserialize, BorshSerialize};
7
8use crate::{
9    error::{Error, Result},
10    types::{Ciphertext, DecisionId, SecretDigest, SecretKey},
11};
12
13#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
14pub enum DecisionStatus {
15    Asked,
16    Answered,
17    Releasing,
18    Released,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
22pub struct Answer {
23    pub digest: SecretDigest,
24    pub ciphertext: Ciphertext,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
28pub struct DecisionState {
29    pub id: DecisionId,
30    owner: String,
31    status: DecisionStatus,
32    answer: Option<Answer>,
33    secret: Option<SecretKey>,
34    value: Option<String>,
35}
36
37impl DecisionState {
38    pub fn new(id: DecisionId, owner: String) -> Self {
39        Self {
40            id,
41            owner,
42            status: DecisionStatus::Asked,
43            answer: None,
44            secret: None,
45            value: None,
46        }
47    }
48
49    pub fn answer(
50        &mut self,
51        owner: &str,
52        ciphertext: Ciphertext,
53        digest: SecretDigest,
54    ) -> Result<()> {
55        if self.owner.ne(owner) {
56            return Err(Error::InvalidDecisionOwner);
57        }
58        if self.status.ne(&DecisionStatus::Asked) {
59            return Err(Error::InvalidDecisionStatus);
60        }
61        self.answer = Some(Answer { ciphertext, digest });
62        self.status = DecisionStatus::Answered;
63        Ok(())
64    }
65
66    pub fn release(&mut self) -> Result<()> {
67        if self.status != DecisionStatus::Answered {
68            Err(Error::InvalidDecisionStatus)
69        } else {
70            self.status = DecisionStatus::Releasing;
71            Ok(())
72        }
73    }
74
75    /// Add the decryption result.
76    ///
77    /// So that it can be read inside the game handler.
78    pub fn add_released(&mut self, value: String) -> Result<()> {
79        if self.status != DecisionStatus::Released {
80            Err(Error::InvalidDecisionStatus)
81        } else {
82            self.value = Some(value);
83            Ok(())
84        }
85    }
86
87    /// Add the shared secret and update the status.
88    ///
89    /// The secret will be used to decrypt the answer.
90    pub fn add_secret(&mut self, owner: &str, secret: SecretKey) -> Result<()> {
91        if self.status != DecisionStatus::Releasing {
92            Err(Error::InvalidDecisionStatus)
93        } else if self.owner.ne(owner) {
94            Err(Error::InvalidDecisionOwner)
95        } else {
96            self.secret = Some(secret);
97            self.status = DecisionStatus::Released;
98            Ok(())
99        }
100    }
101
102    pub fn get_secret(&self) -> Result<&SecretKey> {
103        match self.secret.as_ref() {
104            Some(secret) => Ok(secret),
105            None => Err(Error::InvalidDecisionStatus),
106        }
107    }
108
109    pub fn is_answered(&self) -> bool {
110        self.status == DecisionStatus::Answered
111    }
112
113    pub fn is_prompted(&self) -> bool {
114        self.status == DecisionStatus::Asked
115    }
116
117    pub fn is_revealed(&self) -> bool {
118        self.status == DecisionStatus::Released
119    }
120
121    pub fn is_revealing(&self) -> bool {
122        self.status == DecisionStatus::Releasing
123    }
124
125    pub fn get_answer(&self) -> Option<&Answer> {
126        self.answer.as_ref()
127    }
128
129    pub fn get_revealed(&self) -> Option<&String> {
130        self.value.as_ref()
131    }
132
133    pub fn get_owner(&self) -> &str {
134        self.owner.as_ref()
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_prompt() {
144        let st = DecisionState::new(1, "Alice".into());
145        assert!(st.is_prompted());
146    }
147
148    #[test]
149    fn test_answer() -> anyhow::Result<()> {
150        let mut st = DecisionState::new(1, "Alice".into());
151        st.answer("Alice", vec![1], vec![2])?;
152        assert_eq!(
153            st.answer,
154            Some(Answer {
155                digest: vec![2],
156                ciphertext: vec![1]
157            })
158        );
159        assert!(st.is_answered());
160        Ok(())
161    }
162
163    #[test]
164    fn test_reveal() -> anyhow::Result<()> {
165        let mut st = DecisionState::new(1, "Alice".into());
166        st.answer("Alice", vec![1], vec![2])?;
167        st.release()?;
168        assert!(st.is_revealing());
169        assert_eq!(st.release(), Err(Error::InvalidDecisionStatus));
170        Ok(())
171    }
172
173    #[test]
174    fn test_add_secret() -> anyhow::Result<()> {
175        let mut st = DecisionState::new(1, "Alice".into());
176        st.answer("Alice", vec![1], vec![2])?;
177        assert_eq!(
178            st.add_secret("Alice", vec![0]),
179            Err(Error::InvalidDecisionStatus)
180        );
181        st.release()?;
182        assert_eq!(
183            st.add_secret("Bob", vec![0]),
184            Err(Error::InvalidDecisionOwner)
185        );
186        st.add_secret("Alice", vec![0])?;
187        assert!(st.is_revealed());
188        Ok(())
189    }
190}