world_id_primitives/
lib.rs1#![deny(
7 clippy::all,
8 clippy::pedantic,
9 clippy::nursery,
10 missing_docs,
11 dead_code
12)]
13#![allow(clippy::option_if_let_else)]
14
15use alloy_primitives::Keccak256;
16
17pub mod serde_utils;
18use ark_babyjubjub::Fq;
19use ark_ff::{AdditiveGroup, Field, PrimeField, UniformRand};
20use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
21use ruint::aliases::{U160, U256};
22use serde::{de::Error as _, ser::Error as _, Deserialize, Deserializer, Serialize, Serializer};
23use std::{
24 fmt,
25 io::{Cursor, Read, Write},
26 ops::{Deref, DerefMut},
27 str::FromStr,
28};
29
30pub mod authenticator;
32
33mod config;
35pub use config::Config;
36
37pub mod circuit_inputs;
41
42pub mod sponge;
44
45pub mod credential;
47pub use credential::{Credential, CredentialVersion};
48
49pub mod merkle;
51
52pub mod oprf;
54
55pub mod proof;
57pub use proof::WorldIdProof;
58
59pub mod rp;
61
62pub type ScalarField = ark_babyjubjub::Fr;
66
67pub const TREE_DEPTH: usize = 30;
69
70#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
78pub struct FieldElement(Fq);
79
80impl FieldElement {
81 pub const ZERO: Self = Self(Fq::ZERO);
83 pub const ONE: Self = Self(Fq::ONE);
85
86 pub fn serialize_as_bytes<W: Write>(&self, writer: &mut W) -> Result<(), PrimitiveError> {
91 self.0
92 .serialize_compressed(writer)
93 .map_err(|e| PrimitiveError::Serialization(e.to_string()))
94 }
95
96 pub fn deserialize_from_bytes<R: Read>(bytes: &mut R) -> Result<Self, PrimitiveError> {
101 let field_element = Fq::deserialize_compressed(bytes)
102 .map_err(|e| PrimitiveError::Deserialization(e.to_string()))?;
103 Ok(Self(field_element))
104 }
105
106 #[must_use]
108 pub fn from_be_bytes_mod_order(bytes: &[u8]) -> Self {
109 let field_element = Fq::from_be_bytes_mod_order(bytes);
110 Self(field_element)
111 }
112
113 #[must_use]
117 pub fn from_arbitrary_raw_bytes(bytes: &[u8]) -> Self {
118 let mut hasher = Keccak256::new();
119 hasher.update(bytes);
120 let output: [u8; 32] = hasher.finalize().into();
121
122 let n = U256::from_be_bytes(output);
123 let n: U256 = n >> 8;
125
126 let field_element = Fq::from_bigint(n.into());
127
128 match field_element {
129 Some(element) => Self(element),
130 None => unreachable!(
131 "due to the byte reduction, the value is guaranteed to be within the field"
132 ),
133 }
134
135 }
137
138 #[must_use]
140 pub fn random<R: rand::CryptoRng + rand::RngCore>(rng: &mut R) -> Self {
141 let field_element = Fq::rand(rng);
142 Self(field_element)
143 }
144}
145
146impl Deref for FieldElement {
147 type Target = Fq;
148 fn deref(&self) -> &Self::Target {
149 &self.0
150 }
151}
152
153impl DerefMut for FieldElement {
154 fn deref_mut(&mut self) -> &mut Self::Target {
155 &mut self.0
156 }
157}
158
159impl FromStr for FieldElement {
160 type Err = PrimitiveError;
161
162 fn from_str(s: &str) -> Result<Self, Self::Err> {
163 let s = s.trim_start_matches("0x");
164 let u256 = U256::from_str_radix(s, 16).map_err(|_| {
165 PrimitiveError::Deserialization("not a valid hex-encoded number".to_string())
166 })?;
167 u256.try_into()
168 }
169}
170
171impl fmt::Display for FieldElement {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 let u256: U256 = (*self).into();
174 write!(f, "{u256:#066x}")
175 }
176}
177
178impl From<Fq> for FieldElement {
179 fn from(value: Fq) -> Self {
180 Self(value)
181 }
182}
183
184impl TryFrom<U256> for FieldElement {
185 type Error = PrimitiveError;
186 fn try_from(value: U256) -> Result<Self, Self::Error> {
187 Ok(Self(
188 value.try_into().map_err(|_| PrimitiveError::NotInField)?,
189 ))
190 }
191}
192
193impl From<U160> for FieldElement {
195 fn from(value: U160) -> Self {
196 let u256 = U256::from(value);
198 let big_int = ark_ff::BigInt(u256.into_limbs());
199 Self(ark_babyjubjub::Fq::new(big_int))
200 }
201}
202
203impl From<FieldElement> for U256 {
204 fn from(value: FieldElement) -> Self {
205 <Self as From<Fq>>::from(value.0)
206 }
207}
208
209impl From<u64> for FieldElement {
210 fn from(value: u64) -> Self {
211 Self(Fq::from(value))
212 }
213}
214
215impl From<u128> for FieldElement {
216 fn from(value: u128) -> Self {
217 Self(Fq::from(value))
218 }
219}
220
221impl Serialize for FieldElement {
222 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
223 where
224 S: Serializer,
225 {
226 if serializer.is_human_readable() {
227 serializer.serialize_str(&self.to_string())
228 } else {
229 let mut writer = Vec::new();
230 self.serialize_compressed(&mut writer)
231 .map_err(S::Error::custom)?;
232 serializer.serialize_bytes(&writer)
233 }
234 }
235}
236
237impl<'de> Deserialize<'de> for FieldElement {
238 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
239 where
240 D: Deserializer<'de>,
241 {
242 if deserializer.is_human_readable() {
243 let s = String::deserialize(deserializer)?;
244 Self::from_str(&s).map_err(D::Error::custom)
245 } else {
246 let bytes = Vec::<u8>::deserialize(deserializer)?;
247 Self::deserialize_from_bytes(&mut Cursor::new(bytes)).map_err(D::Error::custom)
248 }
249 }
250}
251
252#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
254pub enum PrimitiveError {
255 #[error("Serialization error: {0}")]
257 Serialization(String),
258 #[error("Deserialization error: {0}")]
260 Deserialization(String),
261 #[error("Provided value is not in the field")]
263 NotInField,
264 #[error("Provided index is out of bounds")]
266 OutOfBounds,
267 #[error("Invalid input at {attribute}: {reason}")]
269 InvalidInput {
270 attribute: String,
272 reason: String,
274 },
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use ruint::uint;
281
282 #[test]
283 fn test_field_element_encoding() {
284 let root = FieldElement::try_from(uint!(
285 0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
286 ))
287 .unwrap();
288
289 assert_eq!(
290 serde_json::to_string(&root).unwrap(),
291 "\"0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2\""
292 );
293
294 assert_eq!(
295 root.to_string(),
296 "0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2"
297 );
298
299 let fe = FieldElement::ONE;
300 assert_eq!(
301 serde_json::to_string(&fe).unwrap(),
302 "\"0x0000000000000000000000000000000000000000000000000000000000000001\""
303 );
304
305 let md = FieldElement::ZERO;
306 assert_eq!(
307 serde_json::to_string(&md).unwrap(),
308 "\"0x0000000000000000000000000000000000000000000000000000000000000000\""
309 );
310
311 assert_eq!(*FieldElement::ONE, Fq::ONE);
312 }
313
314 #[test]
315 fn test_field_element_decoding() {
316 let root = FieldElement::try_from(uint!(
317 0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
318 ))
319 .unwrap();
320
321 assert_eq!(
322 serde_json::from_str::<FieldElement>(
323 "\"0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2\""
324 )
325 .unwrap(),
326 root
327 );
328
329 assert_eq!(
330 FieldElement::from_str(
331 "0x0000000000000000000000000000000000000000000000000000000000000001"
332 )
333 .unwrap(),
334 FieldElement::ONE
335 );
336 }
337
338 #[test]
339 fn test_field_element_binary_encoding_roundtrip() {
340 let root = FieldElement::try_from(uint!(
341 0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
342 ))
343 .unwrap();
344
345 let mut buffer = Vec::new();
346 ciborium::into_writer(&root, &mut buffer).unwrap();
347
348 let decoded: FieldElement = ciborium::from_reader(&buffer[..]).unwrap();
349
350 assert_eq!(root, decoded);
351 }
352
353 #[test]
354 fn test_field_element_binary_encoding_format() {
355 let root = FieldElement::try_from(uint!(
356 0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
357 ))
358 .unwrap();
359
360 let mut buffer = Vec::new();
362 ciborium::into_writer(&root, &mut buffer).unwrap();
363
364 assert_eq!(buffer.len(), 34); assert_eq!(buffer[0], 0x58); assert_eq!(buffer[1], 0x20); let field_bytes = &buffer[2..];
369 assert_eq!(field_bytes.len(), 32);
370
371 let expected_le_bytes =
372 hex::decode("c224d31e05d194f1b7ac32eaa4dbfce39a43a3f050cf422f21ac917bce23d211")
373 .unwrap();
374 assert_eq!(field_bytes, expected_le_bytes.as_slice());
375 }
376}