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