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