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