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
12pub mod serde_utils;
13use ark_babyjubjub::Fq;
14use ark_ff::{AdditiveGroup, Field, PrimeField, UniformRand};
15use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
16use ruint::aliases::{U160, U256};
17use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _, ser::Error as _};
18use std::{
19    fmt,
20    io::{Cursor, Read, Write},
21    ops::{Deref, DerefMut},
22    str::FromStr,
23};
24
25/// Contains types related to the Authenticator.
26pub mod authenticator;
27
28/// Contains the global configuration for interacting with the World ID Protocol.
29mod config;
30pub use config::Config;
31
32/// Contains the raw circuit input types for the World ID Protocol.
33///
34/// These types are used to prepare the inputs for the Groth16 circuits.
35/// Requires the `circuits` feature (not available in WASM builds).
36#[cfg(feature = "circuits")]
37pub mod circuit_inputs;
38
39/// SAFE-style sponge utilities and helpers.
40pub mod sponge;
41
42/// Base definition of a "Credential" in the World ID Protocol.
43pub mod credential;
44pub use credential::{Credential, CredentialVersion};
45
46/// Contains base types for operations with Merkle trees.
47pub mod merkle;
48
49/// Contains API request/response types and shared API enums.
50pub mod api_types;
51
52/// Contains types specifically related to the OPRF services.
53/// Requires the `circuits` feature (not available in WASM builds).
54#[cfg(feature = "circuits")]
55pub mod oprf;
56
57/// Contains the session nullifier type for session proof responses.
58pub mod nullifier;
59pub use nullifier::SessionNullifier;
60
61/// Contains the quintessential zero-knowledge proof type.
62pub mod proof;
63pub use proof::ZeroKnowledgeProof;
64
65/// Contains types specifically related to relying parties.
66pub mod rp;
67
68/// Contains signer primitives for on-chain and off-chain signatures.
69mod signer;
70pub use signer::Signer;
71
72/// Contains request/response types and validation helpers for RP proof requests.
73pub mod request;
74pub use request::{
75    ConstraintExpr, ConstraintKind, ConstraintNode, MAX_CONSTRAINT_NODES, ProofRequest,
76    ProofResponse, RequestItem, RequestVersion, ResponseItem, ValidationError,
77};
78
79/// The scalar field used in the World ID Protocol.
80///
81/// This is the scalar field of the `BabyJubJub` curve.
82pub type ScalarField = ark_babyjubjub::Fr;
83
84/// The depth of the Merkle tree used in the World ID Protocol for the `WorldIDRegistry` contract.
85pub const TREE_DEPTH: usize = 30;
86
87/// Represents a field element of the base field (`Fq`) in the World ID Protocol.
88///
89/// The World ID Protocol uses the `BabyJubJub` curve throughout. Note the
90/// base field of `BabyJubJub` is the scalar field of the BN254 curve.
91///
92/// This wrapper ensures consistent serialization and deserialization of field elements, where
93/// string-based serialization is done with hex encoding and binary serialization is done with byte vectors.
94#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
95pub struct FieldElement(Fq);
96
97impl FieldElement {
98    /// The additive identity of the field.
99    pub const ZERO: Self = Self(Fq::ZERO);
100    /// The multiplicative identity of the field.
101    pub const ONE: Self = Self(Fq::ONE);
102
103    /// Serializes the field element into a byte vector.
104    ///
105    /// # Errors
106    /// Will return an error if the serialization unexpectedly fails.
107    pub fn serialize_as_bytes<W: Write>(&self, writer: &mut W) -> Result<(), PrimitiveError> {
108        self.0
109            .serialize_compressed(writer)
110            .map_err(|e| PrimitiveError::Serialization(e.to_string()))
111    }
112
113    /// Deserializes a field element from a byte vector.
114    ///
115    /// # Errors
116    /// Will return an error if the provided input is not a valid field element (e.g. not on the curve).
117    pub fn deserialize_from_bytes<R: Read>(bytes: &mut R) -> Result<Self, PrimitiveError> {
118        let field_element = Fq::deserialize_compressed(bytes)
119            .map_err(|e| PrimitiveError::Deserialization(e.to_string()))?;
120        Ok(Self(field_element))
121    }
122
123    /// Deserializes a field element from a big-endian byte slice.
124    #[must_use]
125    pub fn from_be_bytes_mod_order(bytes: &[u8]) -> Self {
126        let field_element = Fq::from_be_bytes_mod_order(bytes);
127        Self(field_element)
128    }
129
130    /// Takes arbitrary raw bytes, hashes them with a byte-friendly gas-efficient hash function
131    /// and reduces it to a field element.
132    ///
133    #[must_use]
134    pub fn from_arbitrary_raw_bytes(bytes: &[u8]) -> Self {
135        let mut hasher = Keccak256::new();
136        hasher.update(bytes);
137        let output: [u8; 32] = hasher.finalize().into();
138
139        let n = U256::from_be_bytes(output);
140        // Shift right one byte to make it fit in the field
141        let n: U256 = n >> 8;
142
143        let field_element = Fq::from_bigint(n.into());
144
145        match field_element {
146            Some(element) => Self(element),
147            None => unreachable!(
148                "due to the byte reduction, the value is guaranteed to be within the field"
149            ),
150        }
151
152        // FIXME: add unit tests
153    }
154
155    /// Generates a random field element using the system's CSPRNG.
156    #[must_use]
157    pub fn random<R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> Self {
158        let field_element = Fq::rand(rng);
159        Self(field_element)
160    }
161}
162
163impl Deref for FieldElement {
164    type Target = Fq;
165    fn deref(&self) -> &Self::Target {
166        &self.0
167    }
168}
169
170impl DerefMut for FieldElement {
171    fn deref_mut(&mut self) -> &mut Self::Target {
172        &mut self.0
173    }
174}
175
176impl FromStr for FieldElement {
177    type Err = PrimitiveError;
178
179    fn from_str(s: &str) -> Result<Self, Self::Err> {
180        let s = s.trim_start_matches("0x");
181        let u256 = U256::from_str_radix(s, 16).map_err(|_| {
182            PrimitiveError::Deserialization("not a valid hex-encoded number".to_string())
183        })?;
184        u256.try_into()
185    }
186}
187
188impl fmt::Display for FieldElement {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        let u256: U256 = (*self).into();
191        write!(f, "{u256:#066x}")
192    }
193}
194
195impl From<Fq> for FieldElement {
196    fn from(value: Fq) -> Self {
197        Self(value)
198    }
199}
200
201impl TryFrom<U256> for FieldElement {
202    type Error = PrimitiveError;
203    fn try_from(value: U256) -> Result<Self, Self::Error> {
204        Ok(Self(
205            value.try_into().map_err(|_| PrimitiveError::NotInField)?,
206        ))
207    }
208}
209
210// safe because U160 is guaranteed to be less than the field modulus.
211impl From<U160> for FieldElement {
212    fn from(value: U160) -> Self {
213        // convert U160 to U256 to reuse existing implementations
214        let u256 = U256::from(value);
215        let big_int = ark_ff::BigInt(u256.into_limbs());
216        Self(ark_babyjubjub::Fq::new(big_int))
217    }
218}
219
220impl From<FieldElement> for U256 {
221    fn from(value: FieldElement) -> Self {
222        <Self as From<Fq>>::from(value.0)
223    }
224}
225
226impl From<u64> for FieldElement {
227    fn from(value: u64) -> Self {
228        Self(Fq::from(value))
229    }
230}
231
232impl From<u128> for FieldElement {
233    fn from(value: u128) -> Self {
234        Self(Fq::from(value))
235    }
236}
237
238impl Serialize for FieldElement {
239    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
240    where
241        S: Serializer,
242    {
243        if serializer.is_human_readable() {
244            serializer.serialize_str(&self.to_string())
245        } else {
246            let mut writer = Vec::new();
247            self.serialize_compressed(&mut writer)
248                .map_err(S::Error::custom)?;
249            serializer.serialize_bytes(&writer)
250        }
251    }
252}
253
254impl<'de> Deserialize<'de> for FieldElement {
255    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
256    where
257        D: Deserializer<'de>,
258    {
259        if deserializer.is_human_readable() {
260            let s = String::deserialize(deserializer)?;
261            Self::from_str(&s).map_err(D::Error::custom)
262        } else {
263            let bytes = Vec::<u8>::deserialize(deserializer)?;
264            Self::deserialize_from_bytes(&mut Cursor::new(bytes)).map_err(D::Error::custom)
265        }
266    }
267}
268
269/// Generic errors that may occur with basic serialization and deserialization.
270#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
271pub enum PrimitiveError {
272    /// Error that occurs when serializing a value. Generally not expected.
273    #[error("Serialization error: {0}")]
274    Serialization(String),
275    /// Error that occurs when deserializing a value. This can happen often when not providing valid inputs.
276    #[error("Deserialization error: {0}")]
277    Deserialization(String),
278    /// Number is equal or larger than the target field modulus.
279    #[error("Provided value is not in the field")]
280    NotInField,
281    /// Index is out of bounds.
282    #[error("Provided index is out of bounds")]
283    OutOfBounds,
284    /// Invalid input provided (e.g., incorrect length, format, etc.)
285    #[error("Invalid input at {attribute}: {reason}")]
286    InvalidInput {
287        /// The attribute that is invalid
288        attribute: String,
289        /// The reason the input is invalid
290        reason: String,
291    },
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297    use ruint::uint;
298
299    #[test]
300    fn test_field_element_encoding() {
301        let root = FieldElement::try_from(uint!(
302            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
303        ))
304        .unwrap();
305
306        assert_eq!(
307            serde_json::to_string(&root).unwrap(),
308            "\"0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2\""
309        );
310
311        assert_eq!(
312            root.to_string(),
313            "0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2"
314        );
315
316        let fe = FieldElement::ONE;
317        assert_eq!(
318            serde_json::to_string(&fe).unwrap(),
319            "\"0x0000000000000000000000000000000000000000000000000000000000000001\""
320        );
321
322        let md = FieldElement::ZERO;
323        assert_eq!(
324            serde_json::to_string(&md).unwrap(),
325            "\"0x0000000000000000000000000000000000000000000000000000000000000000\""
326        );
327
328        assert_eq!(*FieldElement::ONE, Fq::ONE);
329    }
330
331    #[test]
332    fn test_field_element_decoding() {
333        let root = FieldElement::try_from(uint!(
334            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
335        ))
336        .unwrap();
337
338        assert_eq!(
339            serde_json::from_str::<FieldElement>(
340                "\"0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2\""
341            )
342            .unwrap(),
343            root
344        );
345
346        assert_eq!(
347            FieldElement::from_str(
348                "0x0000000000000000000000000000000000000000000000000000000000000001"
349            )
350            .unwrap(),
351            FieldElement::ONE
352        );
353    }
354
355    #[test]
356    fn test_field_element_binary_encoding_roundtrip() {
357        let root = FieldElement::try_from(uint!(
358            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
359        ))
360        .unwrap();
361
362        let mut buffer = Vec::new();
363        ciborium::into_writer(&root, &mut buffer).unwrap();
364
365        let decoded: FieldElement = ciborium::from_reader(&buffer[..]).unwrap();
366
367        assert_eq!(root, decoded);
368    }
369
370    #[test]
371    fn test_field_element_binary_encoding_format() {
372        let root = FieldElement::try_from(uint!(
373            0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
374        ))
375        .unwrap();
376
377        // Serialize to CBOR (binary format)
378        let mut buffer = Vec::new();
379        ciborium::into_writer(&root, &mut buffer).unwrap();
380
381        assert_eq!(buffer.len(), 34); // CBOR header (2 bytes) + field element (32 bytes)
382        assert_eq!(buffer[0], 0x58); // CBOR byte string, 1-byte length follows
383        assert_eq!(buffer[1], 0x20); // Length = 32 bytes
384
385        let field_bytes = &buffer[2..];
386        assert_eq!(field_bytes.len(), 32);
387
388        let expected_le_bytes =
389            hex::decode("c224d31e05d194f1b7ac32eaa4dbfce39a43a3f050cf422f21ac917bce23d211")
390                .unwrap();
391        assert_eq!(field_bytes, expected_le_bytes.as_slice());
392    }
393}