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