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