Skip to main content

world_id_primitives/
authenticator.rs

1use std::{
2    ops::{Deref, DerefMut},
3    str::FromStr,
4};
5
6use ark_babyjubjub::{EdwardsAffine, Fq};
7use ark_ff::{AdditiveGroup, PrimeField as _};
8use arrayvec::ArrayVec;
9use eddsa_babyjubjub::{EdDSAPublicKey, EdDSASignature};
10use serde::{Deserialize, Serialize};
11
12use crate::{FieldElement, PrimitiveError};
13
14/// The maximum number of authenticator public keys that can be registered at any time
15/// per World ID Account.
16///
17/// This constrained is introduced to maintain proof performance reasonable even
18/// in devices with limited resources.
19pub const MAX_AUTHENTICATOR_KEYS: usize = 7;
20
21/// Domain separator for the authenticator OPRF query digest.
22const OPRF_QUERY_DS: &[u8] = b"World ID Query";
23
24/// Computes the Poseidon2 digest for an authenticator OPRF query.
25///
26/// # Arguments
27/// * `leaf_index` - The leaf index of the authenticator in the World ID Registry.
28/// * `action` - The action field element.
29/// * `query_origin_id` - The `RpId` or `issuer_schema_id`.
30#[must_use]
31pub fn oprf_query_digest(
32    leaf_index: u64,
33    action: FieldElement,
34    query_origin_id: FieldElement,
35) -> FieldElement {
36    let input = [
37        ark_babyjubjub::Fq::from_be_bytes_mod_order(OPRF_QUERY_DS),
38        leaf_index.into(),
39        *query_origin_id,
40        *action,
41    ];
42    poseidon2::bn254::t4::permutation(&input)[1].into()
43}
44
45/// A set of **off-chain** authenticator public keys for a World ID Account.
46///
47/// Each World ID Account has a number of public keys for each authorized authenticator;
48/// a commitment to the entire set of public keys is stored in the `WorldIDRegistry` contract.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct AuthenticatorPublicKeySet(ArrayVec<EdDSAPublicKey, MAX_AUTHENTICATOR_KEYS>);
51
52impl AuthenticatorPublicKeySet {
53    /// Creates a new authenticator public key set with the provided public keys or defaults to none.
54    ///
55    /// # Errors
56    /// Returns an error if the number of public keys exceeds [`MAX_AUTHENTICATOR_KEYS`].
57    pub fn new(pubkeys: Option<Vec<EdDSAPublicKey>>) -> Result<Self, PrimitiveError> {
58        if let Some(pubkeys) = pubkeys {
59            if pubkeys.len() > MAX_AUTHENTICATOR_KEYS {
60                return Err(PrimitiveError::OutOfBounds);
61            }
62
63            Ok(Self(
64                pubkeys
65                    .into_iter()
66                    .collect::<ArrayVec<_, MAX_AUTHENTICATOR_KEYS>>(),
67            ))
68        } else {
69            Ok(Self(ArrayVec::new()))
70        }
71    }
72
73    /// Converts the set of public keys to a fixed-length array of Affine points.
74    ///
75    /// This is usually used to serialize to the circuit input which expects defaulted Affine points for unused slots.
76    pub fn as_affine_array(&self) -> [EdwardsAffine; MAX_AUTHENTICATOR_KEYS] {
77        let mut array = [EdwardsAffine::default(); MAX_AUTHENTICATOR_KEYS];
78        for (i, pubkey) in self.0.iter().enumerate() {
79            array[i] = pubkey.pk;
80        }
81        array
82    }
83
84    /// Returns the number of public keys in the set.
85    #[must_use]
86    pub const fn len(&self) -> usize {
87        self.0.len()
88    }
89
90    /// Returns `true` if the set is empty.
91    #[must_use]
92    pub const fn is_empty(&self) -> bool {
93        self.0.is_empty()
94    }
95
96    /// Returns the public key at the given index.
97    ///
98    /// It will return `None` if the index is out of bounds, even if it's less than `MAX_AUTHENTICATOR_KEYS` but
99    /// the key is not initialized.
100    #[must_use]
101    pub fn get(&self, index: usize) -> Option<&EdDSAPublicKey> {
102        self.0.get(index)
103    }
104
105    /// Sets a new public key at the given index if it's within bounds of the initialized set.
106    ///
107    /// # Errors
108    /// Returns an error if the index is out of bounds.
109    pub fn try_set_at_index(
110        &mut self,
111        index: usize,
112        pubkey: EdDSAPublicKey,
113    ) -> Result<(), PrimitiveError> {
114        if index >= self.len() || index >= MAX_AUTHENTICATOR_KEYS {
115            return Err(PrimitiveError::OutOfBounds);
116        }
117        self.0[index] = pubkey;
118        Ok(())
119    }
120
121    /// Pushes a new public key onto the set.
122    ///
123    /// # Errors
124    /// Returns an error if the set is full.
125    pub fn try_push(&mut self, pubkey: EdDSAPublicKey) -> Result<(), PrimitiveError> {
126        self.0
127            .try_push(pubkey)
128            .map_err(|_| PrimitiveError::OutOfBounds)
129    }
130
131    /// Computes the Poseidon2 leaf hash commitment for this key set as stored in the `WorldIDRegistry`.
132    ///
133    /// # Panics
134    /// Panics if the domain separator constant cannot be converted into an `Fq`.
135    #[must_use]
136    pub fn leaf_hash(&self) -> Fq {
137        let mut input = [Fq::ZERO; 16];
138
139        input[0] =
140            Fq::from_str("105702839725298824521994315").expect("domain separator fits in field");
141
142        let pk_array = self.as_affine_array();
143        for i in 0..MAX_AUTHENTICATOR_KEYS {
144            input[i * 2 + 1] = pk_array[i].x;
145            input[i * 2 + 2] = pk_array[i].y;
146        }
147
148        poseidon2::bn254::t16::permutation(&input)[1]
149    }
150}
151
152impl Deref for AuthenticatorPublicKeySet {
153    type Target = ArrayVec<EdDSAPublicKey, MAX_AUTHENTICATOR_KEYS>;
154    fn deref(&self) -> &Self::Target {
155        &self.0
156    }
157}
158
159impl DerefMut for AuthenticatorPublicKeySet {
160    fn deref_mut(&mut self) -> &mut Self::Target {
161        &mut self.0
162    }
163}
164
165/// Enables entities that sign messages within the Protocol for use with the ZK circuits.
166///
167/// This is in particular used by Authenticators to authorize requests for nullifier generation.
168pub trait ProtocolSigner {
169    /// Signs a message with the protocol signer using the `EdDSA` scheme (**off-chain** signer), for use
170    /// with the Protocol ZK circuits.
171    fn sign(&self, message: FieldElement) -> EdDSASignature
172    where
173        Self: Sized + Send + Sync;
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    fn create_test_pubkey() -> EdDSAPublicKey {
181        EdDSAPublicKey {
182            pk: EdwardsAffine::default(),
183        }
184    }
185
186    #[test]
187    fn test_try_set_at_index_within_bounds() {
188        let mut key_set = AuthenticatorPublicKeySet::new(None).unwrap();
189        let pubkey = create_test_pubkey();
190
191        // Setting at index 0 when empty should fail (index >= len)
192        let result = key_set.try_set_at_index(0, pubkey.clone());
193        assert!(result.is_err(), "Should not panic, should return error");
194
195        // Push a key first
196        key_set.try_push(pubkey.clone()).unwrap();
197
198        // Now setting at index 0 should succeed
199        key_set.try_set_at_index(0, pubkey).unwrap();
200    }
201
202    #[test]
203    fn test_try_set_at_index_at_length() {
204        let mut key_set = AuthenticatorPublicKeySet::new(None).unwrap();
205        let pubkey = create_test_pubkey();
206
207        // Push one key
208        key_set.try_push(pubkey.clone()).unwrap();
209
210        // Try to set at index == len (should fail, not panic)
211        let result = key_set.try_set_at_index(1, pubkey);
212        assert!(result.is_err(), "Should not panic when index equals length");
213    }
214
215    #[test]
216    fn test_try_set_at_index_out_of_bounds() {
217        let mut key_set = AuthenticatorPublicKeySet::new(None).unwrap();
218        let pubkey = create_test_pubkey();
219
220        // Try to set at index beyond MAX_AUTHENTICATOR_KEYS
221        let result = key_set.try_set_at_index(MAX_AUTHENTICATOR_KEYS, pubkey.clone());
222        assert!(
223            result.is_err(),
224            "Should not panic when index >= MAX_AUTHENTICATOR_KEYS"
225        );
226
227        let result = key_set.try_set_at_index(MAX_AUTHENTICATOR_KEYS + 1, pubkey);
228        assert!(
229            result.is_err(),
230            "Should not panic when index > MAX_AUTHENTICATOR_KEYS"
231        );
232    }
233
234    #[test]
235    fn test_try_push_within_capacity() {
236        let mut key_set = AuthenticatorPublicKeySet::new(None).unwrap();
237        let pubkey = create_test_pubkey();
238
239        // Should be able to push up to MAX_AUTHENTICATOR_KEYS without panicking
240        for i in 0..MAX_AUTHENTICATOR_KEYS {
241            let result = key_set.try_push(pubkey.clone());
242            assert!(
243                result.is_ok(),
244                "Should not panic when pushing element {} of {}",
245                i + 1,
246                MAX_AUTHENTICATOR_KEYS
247            );
248        }
249
250        assert_eq!(key_set.len(), MAX_AUTHENTICATOR_KEYS);
251
252        let result = key_set.try_push(pubkey);
253        assert!(result.is_err()); // should return an error because the set is full, but not panic
254    }
255
256    #[test]
257    fn test_as_affine_array_empty_set() {
258        let key_set = AuthenticatorPublicKeySet::new(None).unwrap();
259        let array = key_set.as_affine_array();
260
261        // All elements should be default
262        assert_eq!(array.len(), MAX_AUTHENTICATOR_KEYS);
263        for affine in &array {
264            assert_eq!(*affine, EdwardsAffine::default());
265        }
266    }
267
268    #[test]
269    fn test_as_affine_array_partial_set() {
270        let mut key_set = AuthenticatorPublicKeySet::new(None).unwrap();
271        let pubkey = create_test_pubkey();
272
273        // Add 3 keys
274        for _ in 0..3 {
275            key_set.try_push(pubkey.clone()).unwrap();
276        }
277
278        let array = key_set.as_affine_array();
279
280        // Array should have correct length
281        assert_eq!(array.len(), MAX_AUTHENTICATOR_KEYS);
282
283        // First 3 should match the pubkey
284        for item in array.iter().take(3) {
285            assert_eq!(item, &pubkey.pk);
286        }
287
288        for item in array.iter().take(MAX_AUTHENTICATOR_KEYS).skip(3) {
289            assert_eq!(item, &EdwardsAffine::default());
290        }
291    }
292}