Skip to main content

world_id_primitives/
lib.rs

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