wamu_core/
identity_rotation.rs

1//! Identity rotation implementation.
2//!
3//! Ref: <https://wamu.tech/specification#identity-rotation>.
4
5use crate::crypto::{Random32Bytes, VerifyingKey};
6use crate::errors::{Error, IdentityAuthedRequestError};
7use crate::payloads::{IdentityAuthedRequestPayload, IdentityRotationChallengeResponsePayload};
8use crate::share::{SigningShare, SubShare};
9use crate::traits::IdentityProvider;
10use crate::{identity_authed_request, identity_challenge, share_split_reconstruct, wrappers};
11
12const IDENTITY_ROTATION: &str = "identity-rotation";
13
14/// Given an identity provider, returns the payload for initiating an identity rotation request.
15pub fn initiate(identity_provider: &impl IdentityProvider) -> IdentityAuthedRequestPayload {
16    identity_authed_request::initiate(IDENTITY_ROTATION, identity_provider)
17}
18
19/// Given an identity rotation request payload and a list of verifying keys for the other parties,
20/// returns an ok result with a challenge fragment for initiating an identity challenge for a valid request
21/// or an appropriate error result for an invalid request.
22pub fn verify_request_and_initiate_challenge(
23    request: &IdentityAuthedRequestPayload,
24    verified_parties: &[VerifyingKey],
25) -> Result<Random32Bytes, IdentityAuthedRequestError> {
26    wrappers::verify_identity_authed_request_and_initiate_challenge(
27        IDENTITY_ROTATION,
28        request,
29        verified_parties,
30    )
31}
32
33/// Given a list of identity challenge fragments, the current identity provider and the new identity provider,
34/// returns the identity rotation challenge response payload that includes the new verifying key and
35/// challenge response signatures from both the current and the new identity providers.
36pub fn challenge_response(
37    challenge_fragments: &[Random32Bytes],
38    current_identity_provider: &impl IdentityProvider,
39    new_identity_provider: &impl IdentityProvider,
40) -> IdentityRotationChallengeResponsePayload {
41    IdentityRotationChallengeResponsePayload {
42        new_verifying_key: new_identity_provider.verifying_key(),
43        current_signature: identity_challenge::respond(
44            challenge_fragments,
45            current_identity_provider,
46        ),
47        new_signature: identity_challenge::respond(challenge_fragments, new_identity_provider),
48    }
49}
50
51/// Given an identity rotation challenge response, a list of identity challenge fragments and
52/// a verifying key for challenged party,
53/// returns an `Ok` result for valid identity rotation challenge response signature, or an appropriate `Err` result otherwise.
54pub fn verify_challenge_response(
55    response: &IdentityRotationChallengeResponsePayload,
56    challenge_fragments: &[Random32Bytes],
57    verifying_key: &VerifyingKey,
58) -> Result<(), Error> {
59    // Verifies current identity.
60    identity_challenge::verify(
61        &response.current_signature,
62        challenge_fragments,
63        verifying_key,
64    )?;
65    // Verifies new identity.
66    Ok(identity_challenge::verify(
67        &response.new_signature,
68        challenge_fragments,
69        &response.new_verifying_key,
70    )?)
71}
72
73/// Given the current "signing share", "sub-share" and identity provider, and the new identity provider,
74/// returns an `Ok` result wrapping the new "signing share" and "sub-share" associated with the new identity provider,
75/// that can be used to reconstruct the current "secret share" given the new identity provider, or an appropriate `Err` result.
76pub fn rotate_signing_and_sub_share(
77    signing_share: &SigningShare,
78    sub_share_b: &SubShare,
79    current_identity_provider: &impl IdentityProvider,
80    new_identity_provider: &impl IdentityProvider,
81) -> Result<(SigningShare, SubShare), Error> {
82    let secret_share = share_split_reconstruct::reconstruct(
83        signing_share,
84        sub_share_b,
85        current_identity_provider,
86    )?;
87    share_split_reconstruct::split(&secret_share, new_identity_provider)
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::crypto::Random32Bytes;
94    use crate::errors::CryptoError;
95    use crate::share::SecretShare;
96    use crate::test_utils::MockECDSAIdentityProvider;
97    use crypto_bigint::U256;
98
99    #[test]
100    fn identity_rotation_works() {
101        // Generates current identity provider.
102        let current_identity_provider = MockECDSAIdentityProvider::generate();
103
104        // Generate secret share.
105        let secret_share = SecretShare::from(Random32Bytes::generate());
106
107        // Computes "signing share" and "sub-share".
108        let (current_signing_share, current_sub_share_b) =
109            share_split_reconstruct::split(&secret_share, &current_identity_provider).unwrap();
110
111        // Generates new identity provider.
112        let new_identity_provider = MockECDSAIdentityProvider::generate();
113
114        // Generates identity rotation request payload.
115        let init_payload = initiate(&current_identity_provider);
116
117        // Verifies identity rotation request and initiates challenge.
118        let init_results: Vec<Result<Random32Bytes, IdentityAuthedRequestError>> = (0..5)
119            .map(|_| {
120                verify_request_and_initiate_challenge(
121                    &init_payload,
122                    &[current_identity_provider.verifying_key()],
123                )
124            })
125            .collect();
126
127        // Verifies expected result.
128        assert!(!init_results.iter().any(|result| result.is_err()));
129
130        // Unwrap challenge fragments.
131        let challenge_fragments: Vec<Random32Bytes> = init_results
132            .into_iter()
133            .map(|result| result.unwrap())
134            .collect();
135
136        for (
137            actual_current_signer,
138            fragments_to_sign,
139            fragments_to_verify,
140            expected_challenge_result,
141        ) in [
142            // Valid challenge response should be accepted.
143            (
144                &current_identity_provider,
145                &challenge_fragments,
146                &challenge_fragments,
147                Ok(()),
148            ),
149            // Challenge response from the wrong signer should be rejected.
150            (
151                &MockECDSAIdentityProvider::generate(),
152                &challenge_fragments,
153                &challenge_fragments,
154                Err(Error::Crypto(CryptoError::InvalidSignature)),
155            ),
156            // Challenge response signing the wrong challenge fragments should be rejected.
157            (
158                &current_identity_provider,
159                &(0..3u8)
160                    .map(|n| Random32Bytes::from(U256::from(n)))
161                    .collect(),
162                &challenge_fragments,
163                Err(Error::Crypto(CryptoError::InvalidSignature)),
164            ),
165        ] {
166            // Generates identity rotation challenge response using the "actual signer" and "signing challenge fragments" for this test case.
167            let challenge_payload = challenge_response(
168                fragments_to_sign,
169                actual_current_signer,
170                &new_identity_provider,
171            );
172
173            // Verifies identity rotation challenge response using the challenged identity provider and "verification challenge fragments" for this test case.
174            let challenge_result = verify_challenge_response(
175                &challenge_payload,
176                fragments_to_verify,
177                &current_identity_provider.verifying_key(),
178            );
179
180            // Verifies expected result.
181            assert_eq!(challenge_result, expected_challenge_result);
182        }
183
184        // Computes the new "signing share" and "sub-share".
185        let (new_signing_share, new_sub_share_b) = rotate_signing_and_sub_share(
186            &current_signing_share,
187            &current_sub_share_b,
188            &current_identity_provider,
189            &new_identity_provider,
190        )
191        .unwrap();
192
193        // Reconstructs "secret share" from new "signing share", "sub-share" and identity provider.
194        let reconstructed_secret_share = share_split_reconstruct::reconstruct(
195            &new_signing_share,
196            &new_sub_share_b,
197            &new_identity_provider,
198        )
199        .unwrap();
200
201        // Verifies reconstructed "secret share".
202        assert_eq!(
203            &reconstructed_secret_share.to_be_bytes(),
204            &secret_share.to_be_bytes()
205        );
206    }
207}