Skip to main content

world_id_primitives/
lib.rs

1#![cfg_attr(all(),
2doc = ::embed_doc_image::embed_image!("world-id-protocol-parties", "assets/world-id-protocol-parties.png"))]
3#![doc = include_str!("../README.md")]
4#![cfg_attr(not(test), deny(unused_crate_dependencies))]
5#![deny(clippy::all, clippy::nursery, missing_docs, dead_code)]
6#![allow(clippy::option_if_let_else)]
7
8use alloy_primitives::Keccak256;
9use ark_babyjubjub::Fq;
10use ark_ff::{AdditiveGroup, Field, PrimeField, UniformRand};
11use ruint::aliases::{U160, U256};
12use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
13use std::{
14    fmt,
15    ops::{Deref, DerefMut},
16    str::FromStr,
17};
18
19#[cfg(target_arch = "wasm32")]
20use getrandom as _;
21
22/// Contains types related to the Authenticator.
23pub mod authenticator;
24
25mod key_set;
26pub use key_set::{
27    AuthenticatorPublicKeySet, MAX_AUTHENTICATOR_KEYS, SparseAuthenticatorPubkeysError,
28};
29
30/// Contains the global configuration for interacting with the World ID Protocol.
31mod config;
32pub use config::{Config, ServiceEndpoint};
33
34/// SAFE-style sponge utilities and helpers.
35pub mod sponge;
36
37/// Base definition of a "Credential" in the World ID Protocol.
38pub mod credential;
39pub use credential::{Credential, CredentialVersion};
40
41/// Contains base types for operations with Merkle trees.
42pub mod merkle;
43
44/// Contains API request/response types and shared API enums.
45pub mod api_types;
46
47/// Contains types specifically related to the OPRF services.
48pub mod oprf;
49
50/// A nullifier is a unique, one-time identifier. See [`Nullifier`] for more details.
51mod nullifier;
52pub use nullifier::Nullifier;
53
54/// Contains types relevant for Session Proofs.
55mod session;
56pub use session::{SessionFeType, SessionFieldElement, SessionId, SessionNullifier};
57
58/// Contains the quintessential zero-knowledge proof type.
59pub mod proof;
60pub use proof::{OwnershipProof, ZeroKnowledgeProof};
61
62/// Contains types specifically related to relying parties.
63pub mod rp;
64
65pub mod serde_utils;
66
67/// Contains signer primitives for on-chain and off-chain signatures.
68mod signer;
69pub use signer::Signer;
70
71/// Contains request/response types and validation helpers for RP proof requests.
72pub mod request;
73pub use request::{
74    ConstraintExpr, ConstraintKind, ConstraintNode, MAX_CONSTRAINT_NODES, ProofRequest,
75    ProofResponse, ProofType, RequestItem, RequestVersion, ResponseItem, ValidationError,
76};
77
78pub use eddsa_babyjubjub::{EdDSAPrivateKey, EdDSAPublicKey, EdDSASignature};
79pub use taceo_oprf::types::{OprfKeyId, ShareEpoch};
80
81/// The scalar field used in the World ID Protocol.
82///
83/// This is the scalar field of the `BabyJubJub` curve.
84pub type ScalarField = ark_babyjubjub::Fr;
85
86/// The depth of the Merkle tree used in the World ID Protocol for the `WorldIDRegistry` contract.
87pub const TREE_DEPTH: usize = 30;
88
89/// Represents an element of the field used in the World ID Protocol (`Fq`), which
90/// is the **`BabyJubJub` base field**.
91///
92/// Note the base field of `BabyJubJub` is the scalar field of the BN254 curve.
93///
94/// This wrapper ensures consistent serialization and deserialization of field elements, where
95/// string-based serialization is done with hex encoding and binary serialization is done with byte vectors.
96#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
97pub struct FieldElement(Fq);
98
99impl FieldElement {
100    /// The additive identity of the field.
101    pub const ZERO: Self = Self(Fq::ZERO);
102    /// The multiplicative identity of the field.
103    pub const ONE: Self = Self(Fq::ONE);
104
105    /// Returns the 32-byte big-endian representation of this field element.
106    #[must_use]
107    pub fn to_be_bytes(&self) -> [u8; 32] {
108        let as_num: U256 = self.to_u256();
109        as_num.to_be_bytes()
110    }
111
112    /// Constructs a field element from a 32-byte big-endian representation.
113    ///
114    /// Unlike `from_be_bytes_mod_order`, this rejects values >= the field modulus.
115    ///
116    /// # Errors
117    /// Returns [`PrimitiveError::NotInField`] if the value is >= the field modulus.
118    pub fn from_be_bytes(be_bytes: &[u8; 32]) -> Result<Self, PrimitiveError> {
119        U256::from_be_bytes(*be_bytes).try_into()
120    }
121
122    /// Deserializes a field element from a big-endian byte slice performing modulo
123    /// reduction if the value is larger than the field modulus.
124    ///
125    /// This can be used for instance to convert the output of a byte-based hash function into
126    /// a field element. It is **critical** to always use the same mechanism to bring elements into
127    /// the field. For example, [`Self::from_arbitrary_raw_bytes`] performs a different operation.
128    ///
129    /// # Warning
130    /// Use this function carefully. This function will perform modulo reduction on the input, which may
131    /// lead to unexpected results if the input should not be reduced. For example, this is **not** appropriate
132    /// when parsing a canonical field-element encoding.
133    #[must_use]
134    pub fn from_be_bytes_mod_order(bytes: &[u8]) -> Self {
135        let field_element = Fq::from_be_bytes_mod_order(bytes);
136        Self(field_element)
137    }
138
139    /// Takes arbitrary raw bytes, hashes them with a byte-friendly gas-efficient hash function
140    /// and reduces it to a field element. Particularly useful for EVM on-chain use.
141    #[must_use]
142    pub fn from_arbitrary_raw_bytes(bytes: &[u8]) -> Self {
143        let mut hasher = Keccak256::new();
144        hasher.update(bytes);
145        let output: [u8; 32] = hasher.finalize().into();
146
147        let n = U256::from_be_bytes(output);
148        // Shift right one byte to make it fit in the field
149        let n: U256 = n >> 8;
150
151        let field_element = Fq::from_bigint(n.into());
152
153        match field_element {
154            Some(element) => Self(element),
155            None => unreachable!(
156                "due to the byte reduction, the value is guaranteed to be within the field"
157            ),
158        }
159
160        // FIXME: add unit tests
161    }
162
163    /// Generates a random field element using the system's CSPRNG.
164    #[must_use]
165    pub fn random<R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> Self {
166        let field_element = Fq::rand(rng);
167        Self(field_element)
168    }
169
170    /// Converts the field element to a `U256`.
171    pub fn to_u256(&self) -> U256 {
172        self.0.into()
173    }
174}
175
176impl Deref for FieldElement {
177    type Target = Fq;
178    fn deref(&self) -> &Self::Target {
179        &self.0
180    }
181}
182
183impl DerefMut for FieldElement {
184    fn deref_mut(&mut self) -> &mut Self::Target {
185        &mut self.0
186    }
187}
188
189impl FromStr for FieldElement {
190    type Err = PrimitiveError;
191
192    /// Parses a field element from a hex string (with optional "0x" prefix).
193    ///
194    /// The value must be lower than the modulus and specifically for string encoding, proper padding is enforced (strictly 32 bytes). This
195    /// is because some values in the Protocol are meant to be enforced uniqueness with, and this reduces the possibility of accidental
196    /// string non-collisions.
197    fn from_str(s: &str) -> Result<Self, Self::Err> {
198        let s = s.trim_start_matches("0x");
199        let bytes = hex::decode(s)
200            .map_err(|e| PrimitiveError::Deserialization(format!("Invalid hex encoding: {e}")))?;
201        let bytes: [u8; 32] = bytes
202            .try_into()
203            .map_err(|_| PrimitiveError::Deserialization("expected 32 bytes".to_string()))?;
204        Self::from_be_bytes(&bytes)
205    }
206}
207
208impl fmt::Display for FieldElement {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        write!(f, "0x{}", hex::encode(self.to_be_bytes()))
211    }
212}
213
214impl From<Fq> for FieldElement {
215    fn from(value: Fq) -> Self {
216        Self(value)
217    }
218}
219
220impl TryFrom<U256> for FieldElement {
221    type Error = PrimitiveError;
222    fn try_from(value: U256) -> Result<Self, Self::Error> {
223        Ok(Self(
224            value.try_into().map_err(|_| PrimitiveError::NotInField)?,
225        ))
226    }
227}
228
229// safe because U160 is guaranteed to be less than the field modulus.
230impl From<U160> for FieldElement {
231    fn from(value: U160) -> Self {
232        // convert U160 to U256 to reuse existing implementations
233        let u256 = U256::from(value);
234        let big_int = ark_ff::BigInt(u256.into_limbs());
235        Self(ark_babyjubjub::Fq::new(big_int))
236    }
237}
238
239impl From<FieldElement> for U256 {
240    fn from(value: FieldElement) -> Self {
241        <Self as From<Fq>>::from(value.0)
242    }
243}
244
245impl From<u64> for FieldElement {
246    fn from(value: u64) -> Self {
247        Self(Fq::from(value))
248    }
249}
250
251impl From<u128> for FieldElement {
252    fn from(value: u128) -> Self {
253        Self(Fq::from(value))
254    }
255}
256
257impl TryFrom<FieldElement> for u64 {
258    type Error = PrimitiveError;
259    fn try_from(value: FieldElement) -> Result<Self, Self::Error> {
260        let u256 = <U256 as From<Fq>>::from(value.0);
261        u256.try_into().map_err(|_| PrimitiveError::OutOfBounds)
262    }
263}
264
265impl TryFrom<FieldElement> for usize {
266    type Error = PrimitiveError;
267    fn try_from(value: FieldElement) -> Result<Self, Self::Error> {
268        let u256 = <U256 as From<Fq>>::from(value.0);
269        u256.try_into().map_err(|_| PrimitiveError::OutOfBounds)
270    }
271}
272
273impl Serialize for FieldElement {
274    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
275    where
276        S: Serializer,
277    {
278        if serializer.is_human_readable() {
279            serializer.serialize_str(&self.to_string())
280        } else {
281            serializer.serialize_bytes(&self.to_be_bytes())
282        }
283    }
284}
285
286impl<'de> Deserialize<'de> for FieldElement {
287    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
288    where
289        D: Deserializer<'de>,
290    {
291        if deserializer.is_human_readable() {
292            let s = String::deserialize(deserializer)?;
293            Self::from_str(&s).map_err(D::Error::custom)
294        } else {
295            let bytes = Vec::<u8>::deserialize(deserializer)?;
296            let bytes: [u8; 32] = bytes
297                .try_into()
298                .map_err(|_| D::Error::custom("expected 32 bytes"))?;
299            Self::from_be_bytes(&bytes).map_err(D::Error::custom)
300        }
301    }
302}
303
304/// Generic errors that may occur with basic serialization and deserialization.
305#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
306pub enum PrimitiveError {
307    /// Error that occurs when serializing a value. Generally not expected.
308    #[error("Serialization error: {0}")]
309    Serialization(String),
310    /// Error that occurs when deserializing a value. This can happen often when not providing valid inputs.
311    #[error("Deserialization error: {0}")]
312    Deserialization(String),
313    /// Number is equal or larger than the target field modulus.
314    #[error("Provided value is not in the field")]
315    NotInField,
316    /// Index is out of bounds.
317    #[error("Provided index is out of bounds")]
318    OutOfBounds,
319    /// Invalid input provided (e.g., incorrect length, format, etc.)
320    #[error("Invalid input at {attribute}: {reason}")]
321    InvalidInput {
322        /// The attribute that is invalid
323        attribute: String,
324        /// The reason the input is invalid
325        reason: String,
326    },
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332    use ruint::uint;
333
334    #[test]
335    fn test_field_element_encoding() {
336        let root = FieldElement::try_from(uint!(
337            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
338        ))
339        .unwrap();
340
341        assert_eq!(
342            serde_json::to_string(&root).unwrap(),
343            "\"0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2\""
344        );
345
346        assert_eq!(
347            root.to_string(),
348            "0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2"
349        );
350
351        let fe = FieldElement::ONE;
352        assert_eq!(
353            serde_json::to_string(&fe).unwrap(),
354            "\"0x0000000000000000000000000000000000000000000000000000000000000001\""
355        );
356
357        let md = FieldElement::ZERO;
358        assert_eq!(
359            serde_json::to_string(&md).unwrap(),
360            "\"0x0000000000000000000000000000000000000000000000000000000000000000\""
361        );
362
363        assert_eq!(*FieldElement::ONE, Fq::ONE);
364    }
365
366    #[test]
367    fn test_field_element_decoding() {
368        let root = FieldElement::try_from(uint!(
369            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
370        ))
371        .unwrap();
372
373        assert_eq!(
374            serde_json::from_str::<FieldElement>(
375                "\"0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2\""
376            )
377            .unwrap(),
378            root
379        );
380
381        assert_eq!(
382            FieldElement::from_str(
383                "0x0000000000000000000000000000000000000000000000000000000000000001"
384            )
385            .unwrap(),
386            FieldElement::ONE
387        );
388    }
389
390    #[test]
391    fn test_simple_bytes_encoding() {
392        let fe = FieldElement::ONE;
393        let bytes = fe.to_be_bytes();
394        let mut expected = [0u8; 32];
395        expected[31] = 1;
396        assert_eq!(bytes, expected);
397
398        let reversed = FieldElement::from_be_bytes(&bytes).unwrap();
399        assert_eq!(reversed, fe);
400    }
401
402    #[test]
403    fn test_field_element_cbor_encoding_roundtrip() {
404        let root = FieldElement::try_from(uint!(
405            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
406        ))
407        .unwrap();
408
409        let mut buffer = Vec::new();
410        ciborium::into_writer(&root, &mut buffer).unwrap();
411
412        let decoded: FieldElement = ciborium::from_reader(&buffer[..]).unwrap();
413
414        assert_eq!(root, decoded);
415    }
416
417    #[test]
418    fn test_field_element_binary_encoding_format() {
419        let root = FieldElement::try_from(uint!(
420            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
421        ))
422        .unwrap();
423
424        // Serialize to CBOR (binary format)
425        let mut buffer = Vec::new();
426        ciborium::into_writer(&root, &mut buffer).unwrap();
427
428        assert_eq!(buffer.len(), 34); // CBOR header (2 bytes) + field element (32 bytes)
429        assert_eq!(buffer[0], 0x58); // CBOR byte string, 1-byte length follows
430        assert_eq!(buffer[1], 0x20); // Length = 32 bytes
431
432        let field_bytes = &buffer[2..];
433        assert_eq!(field_bytes.len(), 32);
434
435        let expected_be_bytes =
436            hex::decode("11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2")
437                .unwrap();
438        assert_eq!(field_bytes, expected_be_bytes.as_slice());
439    }
440
441    #[test]
442    fn test_to_be_bytes_from_be_bytes_roundtrip() {
443        let values = [
444            FieldElement::ZERO,
445            FieldElement::ONE,
446            FieldElement::from(255u64),
447            FieldElement::from(u64::MAX),
448            FieldElement::from(u128::MAX),
449            FieldElement::try_from(uint!(
450                0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
451            ))
452            .unwrap(),
453        ];
454        for fe in values {
455            let bytes = fe.to_be_bytes();
456            let recovered = FieldElement::from_be_bytes(&bytes).unwrap();
457            assert_eq!(fe, recovered);
458        }
459    }
460
461    /// This test is of particular importance because if we performed modulo reduction
462    /// or other techniques to fit into the field this could cause problems with uniqueness
463    /// for field elements that must be unique (e.g. nullifier)
464    #[test]
465    fn test_from_be_bytes_rejects_value_above_modulus() {
466        // The BN254 field is 254 bits
467        let bytes = [0xFF; 32];
468        assert_eq!(
469            FieldElement::from_be_bytes(&bytes),
470            Err(PrimitiveError::NotInField)
471        );
472    }
473
474    #[test]
475    fn test_from_str_rejects_wrong_length() {
476        // Too short (< 64 hex chars)
477        assert!(FieldElement::from_str("0x01").is_err());
478        // Too long (> 64 hex chars)
479        assert!(
480            FieldElement::from_str(
481                "0x000000000000000000000000000000000000000000000000000000000000000001"
482            )
483            .is_err()
484        );
485        // Not hex
486        assert!(
487            FieldElement::from_str(
488                "0xGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"
489            )
490            .is_err()
491        );
492    }
493
494    #[test]
495    fn test_display_from_str_roundtrip() {
496        let fe = FieldElement::try_from(uint!(
497            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
498        ))
499        .unwrap();
500        let s = fe.to_string();
501        assert_eq!(FieldElement::from_str(&s).unwrap(), fe);
502    }
503
504    #[test]
505    fn test_json_cbor_consistency() {
506        // The same value serialized through JSON and CBOR should
507        // produce the same FieldElement when deserialized back.
508        let fe = FieldElement::try_from(uint!(
509            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
510        ))
511        .unwrap();
512
513        let json_str = serde_json::to_string(&fe).unwrap();
514        let from_json: FieldElement = serde_json::from_str(&json_str).unwrap();
515
516        let mut cbor_buf = Vec::new();
517        ciborium::into_writer(&fe, &mut cbor_buf).unwrap();
518        let from_cbor: FieldElement = ciborium::from_reader(&cbor_buf[..]).unwrap();
519
520        assert_eq!(from_json, from_cbor);
521    }
522
523    #[test]
524    fn test_to_be_bytes_is_big_endian() {
525        let fe = FieldElement::from(1u64);
526        let bytes = fe.to_be_bytes();
527        assert_eq!(bytes[31], 1); // 1 is in LSB
528        assert_eq!(bytes[..31], [0u8; 31]);
529
530        let fe256 = FieldElement::from(256u64);
531        let bytes = fe256.to_be_bytes();
532        assert_eq!(bytes[30], 1);
533        assert_eq!(bytes[31], 0);
534    }
535
536    #[test]
537    fn test_u256_roundtrip() {
538        let original =
539            uint!(0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256);
540        let fe = FieldElement::try_from(original).unwrap();
541        let back: U256 = fe.into();
542        assert_eq!(original, back);
543    }
544}