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