wamu_core/identity_challenge.rs
1//! Identity challenge implementation.
2//!
3//! Ref: <https://wamu.tech/specification#identity-challenge>.
4
5use crate::crypto::{Random32Bytes, Signature, VerifyingKey};
6use crate::errors::CryptoError;
7use crate::traits::IdentityProvider;
8use crate::{crypto, utils};
9
10/// Returns a challenge fragment for initiating an identity challenge.
11///
12/// Ref: <https://wamu.tech/specification#identity-challenge-initiation>.
13pub fn initiate() -> Random32Bytes {
14 Random32Bytes::generate()
15}
16
17/// Given a list of identity challenge fragments and an identity provider, returns the response signature for an identity challenge.
18///
19/// Ref: <https://wamu.tech/specification#identity-challenge-response>.
20pub fn respond(
21 challenge_fragments: &[Random32Bytes],
22 identity_provider: &impl IdentityProvider,
23) -> Signature {
24 identity_provider.sign(&challenge_message_bytes(challenge_fragments))
25}
26
27/// Given an identity challenge response signature, a list of identity challenge fragments and
28/// a verifying key for challenged party,
29/// returns an `Ok` result for valid identity challenge response signature, or an appropriate `Err` result otherwise.
30///
31/// Ref: <https://wamu.tech/specification#identity-challenge-verification>.
32pub fn verify(
33 signature: &Signature,
34 challenge_fragments: &[Random32Bytes],
35 verifying_key: &VerifyingKey,
36) -> Result<(), CryptoError> {
37 crypto::verify_signature(
38 verifying_key,
39 &challenge_message_bytes(challenge_fragments),
40 signature,
41 )
42}
43
44/// Returns sign-able message bytes for the identity challenge fragments.
45fn challenge_message_bytes(challenge_fragments: &[Random32Bytes]) -> Vec<u8> {
46 // Sort the challenge fragments so that we always get the same challenge regardless of order of receiving challenges.
47 let mut sorted_challenge_fragments = challenge_fragments.to_owned();
48 sorted_challenge_fragments.sort();
49 utils::prefix_message_bytes(&sorted_challenge_fragments.iter().fold(
50 Vec::<u8>::new(),
51 |mut acc, n| {
52 acc.append(&mut n.to_be_bytes().to_vec());
53 acc
54 },
55 ))
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::test_utils::MockECDSAIdentityProvider;
62 use crypto_bigint::U256;
63
64 #[test]
65 fn identity_challenge_works() {
66 // Generates identity provider.
67 let identity_provider = MockECDSAIdentityProvider::generate();
68
69 // Generates identity challenge fragments.
70 let challenge_fragments: Vec<Random32Bytes> = (0..5).map(|_| initiate()).collect();
71
72 for (actual_signer, fragments_to_sign, fragments_to_verify, expected_result) in [
73 // Valid response should be accepted.
74 (
75 &identity_provider,
76 &challenge_fragments,
77 &challenge_fragments,
78 Ok(()),
79 ),
80 // Response from the wrong signer should be rejected.
81 (
82 &MockECDSAIdentityProvider::generate(),
83 &challenge_fragments,
84 &challenge_fragments,
85 Err(CryptoError::InvalidSignature),
86 ),
87 // Response signing the wrong challenge fragments should be rejected.
88 (
89 &identity_provider,
90 &(0..3u8)
91 .map(|n| Random32Bytes::from(U256::from(n)))
92 .collect(),
93 &challenge_fragments,
94 Err(CryptoError::InvalidSignature),
95 ),
96 ] {
97 // Generates an identity challenge response using the "actual signer" and "signing challenge fragments" for this test case.
98 let challenge_response = respond(fragments_to_sign, actual_signer);
99
100 // Verifies identity challenge response using the challenged identity provider and "verification challenge fragments" for this test case.
101 let result = verify(
102 &challenge_response,
103 fragments_to_verify,
104 &identity_provider.verifying_key(),
105 );
106
107 // Verifies expected result.
108 assert_eq!(result, expected_result);
109 }
110 }
111}