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