1use crate::crypto::{Random32Bytes, VerifyingKey};
6use crate::errors::{IdentityAuthedRequestError, QuorumApprovedRequestError};
7use crate::payloads::{
8 CommandApprovalPayload, IdentityAuthedRequestPayload, QuorumApprovedChallengeResponsePayload,
9};
10use crate::traits::IdentityProvider;
11use crate::{crypto, identity_authed_request, identity_challenge, utils, wrappers};
12
13pub fn initiate(
15 command: &'static str,
16 identity_provider: &impl IdentityProvider,
17) -> IdentityAuthedRequestPayload {
18 identity_authed_request::initiate(command, identity_provider)
19}
20
21pub fn verify_request_and_initiate_challenge(
25 command: &str,
26 request: &IdentityAuthedRequestPayload,
27 identity_provider: &impl IdentityProvider,
28 verified_parties: &[VerifyingKey],
29) -> Result<CommandApprovalPayload, IdentityAuthedRequestError> {
30 let challenge_fragment = wrappers::verify_identity_authed_request_and_initiate_challenge(
31 command,
32 request,
33 verified_parties,
34 )?;
35 let signature = identity_provider.sign(&command_approval_message_bytes(
36 &challenge_fragment,
37 request.command,
38 request.timestamp,
39 ));
40 Ok(CommandApprovalPayload {
41 challenge_fragment,
42 verifying_key: identity_provider.verifying_key(),
43 signature,
44 })
45}
46
47pub fn challenge_response(
52 approvals: &[CommandApprovalPayload],
53 identity_provider: &impl IdentityProvider,
54 request: &IdentityAuthedRequestPayload,
55 quorum_size: usize,
56 verified_parties: &[VerifyingKey],
57) -> Result<QuorumApprovedChallengeResponsePayload, QuorumApprovedRequestError> {
58 let valid_approvals = verify_approvals(approvals, request, quorum_size - 1, verified_parties)?;
60 let approving_quorum = valid_approvals
61 .iter()
62 .map(|approval| approval.verifying_key.clone())
63 .collect();
64 Ok(QuorumApprovedChallengeResponsePayload {
65 signature: identity_challenge::respond(
66 &extract_challenge_fragments(&valid_approvals).collect::<Vec<Random32Bytes>>(),
67 identity_provider,
68 ),
69 approving_quorum,
70 })
71}
72
73pub fn verify_challenge_response(
78 response: &QuorumApprovedChallengeResponsePayload,
79 approvals: &[CommandApprovalPayload],
80 verifying_key: &VerifyingKey,
81 request: &IdentityAuthedRequestPayload,
82 quorum_size: usize,
83 verified_parties: &[VerifyingKey],
84) -> Result<(), QuorumApprovedRequestError> {
85 let initiator_acknowledged_approvals: Vec<CommandApprovalPayload> = approvals
86 .iter()
87 .filter(|approval| response.approving_quorum.contains(&approval.verifying_key))
88 .cloned()
89 .collect();
90 verify_approvals(
91 &initiator_acknowledged_approvals,
92 request,
93 quorum_size - 1,
95 verified_parties,
96 )?;
97 Ok(identity_challenge::verify(
98 &response.signature,
99 &extract_challenge_fragments(&initiator_acknowledged_approvals)
100 .collect::<Vec<Random32Bytes>>(),
101 verifying_key,
102 )?)
103}
104
105fn verify_approvals(
110 approvals: &[CommandApprovalPayload],
111 request: &IdentityAuthedRequestPayload,
112 quorum_size: usize,
113 verified_parties: &[VerifyingKey],
114) -> Result<Vec<CommandApprovalPayload>, QuorumApprovedRequestError> {
115 let valid_approvals = filter_valid_approvals(approvals, request, verified_parties);
116 if valid_approvals.len() < quorum_size {
117 Err(QuorumApprovedRequestError::InsufficientApprovals)
118 } else {
119 Ok(valid_approvals)
120 }
121}
122
123fn filter_valid_approvals(
126 approvals: &[CommandApprovalPayload],
127 request: &IdentityAuthedRequestPayload,
128 verified_parties: &[VerifyingKey],
129) -> Vec<CommandApprovalPayload> {
130 approvals
131 .iter()
132 .filter(|approval| {
133 verified_parties.contains(&approval.verifying_key)
134 && crypto::verify_signature(
135 &approval.verifying_key,
136 &command_approval_message_bytes(
137 &approval.challenge_fragment,
138 request.command,
139 request.timestamp,
140 ),
141 &approval.signature,
142 )
143 .is_ok()
144 })
145 .cloned()
146 .collect()
147}
148
149fn command_approval_message_bytes(
151 challenge_fragment: &Random32Bytes,
152 command: &str,
153 timestamp: u64,
154) -> Vec<u8> {
155 utils::prefix_message_bytes(
156 format!("{}{}{}", challenge_fragment, command, timestamp).as_bytes(),
157 )
158}
159
160fn extract_challenge_fragments(
162 approvals: &[CommandApprovalPayload],
163) -> impl Iterator<Item = Random32Bytes> + '_ {
164 approvals.iter().map(|item| item.challenge_fragment)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use crate::errors::{CryptoError, Error};
171 use crate::test_utils::MockECDSAIdentityProvider;
172 use crypto_bigint::U256;
173
174 #[test]
175 fn quorum_approved_request_initiation_and_verification_works() {
176 let initiator_identity_provider = MockECDSAIdentityProvider::generate();
178
179 let approver_identity_providers: Vec<MockECDSAIdentityProvider> = (0..5)
181 .map(|_| MockECDSAIdentityProvider::generate())
182 .collect();
183
184 let quorum_size = 5;
186
187 let verified_parties: Vec<VerifyingKey> = approver_identity_providers
189 .iter()
190 .map(|identity_provider| identity_provider.verifying_key())
191 .chain([initiator_identity_provider.verifying_key()])
192 .collect();
193
194 let command = "command";
196
197 let init_payload = initiate(command, &initiator_identity_provider);
199
200 let init_results: Vec<Result<CommandApprovalPayload, IdentityAuthedRequestError>> =
202 approver_identity_providers
203 .iter()
204 .map(|identity_provider| {
205 verify_request_and_initiate_challenge(
206 command,
207 &init_payload,
208 identity_provider,
209 &verified_parties,
210 )
211 })
212 .collect();
213
214 assert!(!init_results.iter().any(|result| result.is_err()));
216
217 let approvals: Vec<CommandApprovalPayload> = init_results
219 .into_iter()
220 .map(|result| result.unwrap())
221 .collect();
222
223 for (
224 actual_current_signer,
225 approvals_to_sign,
226 quorum_size_to_sign,
227 expected_challenge_result,
228 ) in [
229 (
231 &initiator_identity_provider,
232 &approvals,
233 quorum_size,
234 Ok(()),
235 ),
236 (
237 &initiator_identity_provider,
238 &approvals[0..4].to_vec(), quorum_size,
240 Ok(()),
241 ),
242 (
244 &MockECDSAIdentityProvider::generate(),
245 &approvals,
246 quorum_size,
247 Err(QuorumApprovedRequestError::Unauthorized(Error::Crypto(
248 CryptoError::InvalidSignature,
249 ))),
250 ),
251 (
253 &initiator_identity_provider,
254 &approvals[0..3].to_vec(), 4, Err(QuorumApprovedRequestError::InsufficientApprovals),
257 ),
258 (
260 &initiator_identity_provider,
261 &approver_identity_providers
262 .iter()
263 .map(|identity_provider| {
264 let challenge_fragment = Random32Bytes::from(U256::ONE);
265 let signature = identity_provider.sign(&command_approval_message_bytes(
266 &challenge_fragment,
267 init_payload.command,
268 init_payload.timestamp,
269 ));
270 CommandApprovalPayload {
271 challenge_fragment,
272 verifying_key: identity_provider.verifying_key(),
273 signature,
274 }
275 })
276 .collect(),
277 quorum_size,
278 Err(QuorumApprovedRequestError::Unauthorized(Error::Crypto(
279 CryptoError::InvalidSignature,
280 ))),
281 ),
282 ] {
283 let challenge_response_result = challenge_response(
285 approvals_to_sign,
286 actual_current_signer,
287 &init_payload,
288 quorum_size_to_sign,
289 &verified_parties,
290 );
291
292 assert!(challenge_response_result.is_ok());
294
295 let challenge_payload = challenge_response_result.unwrap();
297
298 let challenge_result = verify_challenge_response(
300 &challenge_payload,
301 &approvals,
302 &initiator_identity_provider.verifying_key(),
303 &init_payload,
304 quorum_size,
305 &verified_parties,
306 );
307
308 assert_eq!(challenge_result, expected_challenge_result);
310 }
311 }
312}