Skip to main content

world_id_authenticator/
account.rs

1//! This module contains account management operations for the user's World ID. It lets
2//! the user add, update, remove authenticators.
3use alloy::primitives::Address;
4use eddsa_babyjubjub::EdDSAPublicKey;
5use ruint::aliases::U256;
6
7use crate::{
8    api_types::{
9        GatewayRequestId, GatewayRequestState, GatewayStatusResponse, InsertAuthenticatorRequest,
10        RemoveAuthenticatorRequest, UpdateAuthenticatorRequest,
11    },
12    authenticator::Authenticator,
13    error::AuthenticatorError,
14    registry::{
15        domain, sign_insert_authenticator, sign_remove_authenticator, sign_update_authenticator,
16    },
17    traits::OnchainKeyRepresentable,
18};
19
20impl Authenticator {
21    /// Inserts a new authenticator to the account.
22    ///
23    /// # Errors
24    /// Will error if the provided RPC URL is not valid or if there are HTTP call failures.
25    ///
26    /// # Note
27    /// TODO: After successfully inserting an authenticator, the `packed_account_data` should be
28    /// refreshed from the registry to reflect the new `pubkey_id` commitment.
29    pub async fn insert_authenticator(
30        &self,
31        new_authenticator_pubkey: EdDSAPublicKey,
32        new_authenticator_address: Address,
33    ) -> Result<GatewayRequestId, AuthenticatorError> {
34        let leaf_index = self.leaf_index();
35        let nonce = self.signing_nonce().await?;
36        let mut key_set = self.fetch_authenticator_pubkeys().await?;
37        let old_offchain_signer_commitment = key_set.leaf_hash();
38        let encoded_offchain_pubkey = new_authenticator_pubkey.to_ethereum_representation()?;
39        let index =
40            Self::insert_or_reuse_authenticator_key(&mut key_set, new_authenticator_pubkey)?;
41        let new_offchain_signer_commitment = key_set.leaf_hash();
42
43        let eip712_domain = domain(self.config.chain_id(), *self.config.registry_address());
44
45        #[allow(clippy::cast_possible_truncation)]
46        // truncating is intentional, and index will always fit in 32 bits
47        let signature = sign_insert_authenticator(
48            &self.signer.onchain_signer(),
49            leaf_index,
50            new_authenticator_address,
51            index as u32,
52            encoded_offchain_pubkey,
53            new_offchain_signer_commitment.into(),
54            nonce,
55            &eip712_domain,
56        )
57        .map_err(|e| {
58            AuthenticatorError::Generic(format!("Failed to sign insert authenticator: {e}"))
59        })?;
60
61        #[allow(clippy::cast_possible_truncation)]
62        // truncating is intentional, and index will always fit in 32 bits
63        let req = InsertAuthenticatorRequest {
64            leaf_index,
65            new_authenticator_address,
66            pubkey_id: index as u32,
67            new_authenticator_pubkey: encoded_offchain_pubkey,
68            old_offchain_signer_commitment: old_offchain_signer_commitment.into(),
69            new_offchain_signer_commitment: new_offchain_signer_commitment.into(),
70            signature,
71            nonce,
72        };
73
74        let body: GatewayStatusResponse = self
75            .gateway_client
76            .post_json(self.config.gateway_url(), "/insert-authenticator", &req)
77            .await?;
78        Ok(body.request_id)
79    }
80
81    /// Updates an existing authenticator slot with a new authenticator.
82    ///
83    /// # Errors
84    /// Returns an error if the gateway rejects the request or a network error occurs.
85    ///
86    /// # Note
87    /// TODO: After successfully updating an authenticator, the `packed_account_data` should be
88    /// refreshed from the registry to reflect the new `pubkey_id` commitment.
89    pub async fn update_authenticator(
90        &self,
91        old_authenticator_address: Address,
92        new_authenticator_address: Address,
93        new_authenticator_pubkey: EdDSAPublicKey,
94        index: u32,
95    ) -> Result<GatewayRequestId, AuthenticatorError> {
96        let leaf_index = self.leaf_index();
97        let nonce = self.signing_nonce().await?;
98        let mut key_set = self.fetch_authenticator_pubkeys().await?;
99        let old_commitment: U256 = key_set.leaf_hash().into();
100        let encoded_offchain_pubkey = new_authenticator_pubkey.to_ethereum_representation()?;
101        key_set.try_set_at_index(index as usize, new_authenticator_pubkey)?;
102        let new_commitment: U256 = key_set.leaf_hash().into();
103
104        let eip712_domain = domain(self.config.chain_id(), *self.config.registry_address());
105
106        let signature = sign_update_authenticator(
107            &self.signer.onchain_signer(),
108            leaf_index,
109            old_authenticator_address,
110            new_authenticator_address,
111            index,
112            encoded_offchain_pubkey,
113            new_commitment,
114            nonce,
115            &eip712_domain,
116        )
117        .map_err(|e| {
118            AuthenticatorError::Generic(format!("Failed to sign update authenticator: {e}"))
119        })?;
120
121        let req = UpdateAuthenticatorRequest {
122            leaf_index,
123            old_authenticator_address,
124            new_authenticator_address,
125            old_offchain_signer_commitment: old_commitment,
126            new_offchain_signer_commitment: new_commitment,
127            signature,
128            nonce,
129            pubkey_id: index,
130            new_authenticator_pubkey: encoded_offchain_pubkey,
131        };
132
133        let gateway_resp: GatewayStatusResponse = self
134            .gateway_client
135            .post_json(self.config.gateway_url(), "/update-authenticator", &req)
136            .await?;
137        Ok(gateway_resp.request_id)
138    }
139
140    /// Removes an authenticator from the account.
141    ///
142    /// # Errors
143    /// Returns an error if the gateway rejects the request or a network error occurs.
144    ///
145    /// # Note
146    /// TODO: After successfully removing an authenticator, the `packed_account_data` should be
147    /// refreshed from the registry to reflect the new `pubkey_id` commitment.
148    pub async fn remove_authenticator(
149        &self,
150        authenticator_address: Address,
151        index: u32,
152    ) -> Result<GatewayRequestId, AuthenticatorError> {
153        let leaf_index = self.leaf_index();
154        let nonce = self.signing_nonce().await?;
155        let mut key_set = self.fetch_authenticator_pubkeys().await?;
156        let old_commitment: U256 = key_set.leaf_hash().into();
157        let existing_pubkey = key_set
158            .get(index as usize)
159            .ok_or(AuthenticatorError::PublicKeyNotFound)?;
160
161        let encoded_old_offchain_pubkey = existing_pubkey.to_ethereum_representation()?;
162
163        key_set.try_clear_at_index(index as usize)?;
164        let new_commitment: U256 = key_set.leaf_hash().into();
165
166        let eip712_domain = domain(self.config.chain_id(), *self.config.registry_address());
167
168        let signature = sign_remove_authenticator(
169            &self.signer.onchain_signer(),
170            leaf_index,
171            authenticator_address,
172            index,
173            encoded_old_offchain_pubkey,
174            new_commitment,
175            nonce,
176            &eip712_domain,
177        )
178        .map_err(|e| {
179            AuthenticatorError::Generic(format!("Failed to sign remove authenticator: {e}"))
180        })?;
181
182        let req = RemoveAuthenticatorRequest {
183            leaf_index,
184            authenticator_address,
185            old_offchain_signer_commitment: old_commitment,
186            new_offchain_signer_commitment: new_commitment,
187            signature,
188            nonce,
189            pubkey_id: Some(index),
190            authenticator_pubkey: Some(encoded_old_offchain_pubkey),
191        };
192
193        let gateway_resp: GatewayStatusResponse = self
194            .gateway_client
195            .post_json(self.config.gateway_url(), "/remove-authenticator", &req)
196            .await?;
197        Ok(gateway_resp.request_id)
198    }
199
200    /// Polls the gateway for the current status of a previously submitted request.
201    ///
202    /// Use the [`GatewayRequestId`] returned by [`insert_authenticator`](Self::insert_authenticator),
203    /// [`update_authenticator`](Self::update_authenticator), or
204    /// [`remove_authenticator`](Self::remove_authenticator) to track the operation.
205    ///
206    /// # Errors
207    /// - Will error if the network request fails.
208    /// - Will error if the gateway returns an error response (e.g. request not found).
209    pub async fn poll_status(
210        &self,
211        request_id: &GatewayRequestId,
212    ) -> Result<GatewayRequestState, AuthenticatorError> {
213        let path = format!("/status/{request_id}");
214        let body: GatewayStatusResponse = self
215            .gateway_client
216            .get_json(self.config.gateway_url(), &path)
217            .await?;
218        Ok(body.status)
219    }
220}