1use std::{fmt::Display, ops::Deref, str::FromStr};
2
3use ruint::aliases::U256;
4use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
5
6use crate::{FieldElement, PrimitiveError};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct Nullifier {
15 pub inner: FieldElement,
17}
18
19impl Nullifier {
20 const PREFIX: &str = "nil_";
21 const ENCODING_LENGTH: usize = 64;
22
23 pub const fn new(nullifier: FieldElement) -> Self {
25 Self { inner: nullifier }
26 }
27
28 pub fn as_number(&self) -> U256 {
32 self.inner.into()
33 }
34
35 pub fn to_canonical_string(&self) -> String {
48 let value = self
49 .inner
50 .to_string()
51 .trim_start_matches("0x")
52 .to_lowercase();
53 format!(
55 "{}{}{value}",
56 Self::PREFIX,
57 "0".repeat(Self::ENCODING_LENGTH - value.len())
58 )
59 }
60
61 pub fn from_canonical_string(nullifier: String) -> Result<Self, PrimitiveError> {
70 let nullifier = nullifier.strip_prefix(Self::PREFIX).ok_or_else(|| {
71 PrimitiveError::Deserialization(format!(
72 "nullifier must start with the {}",
73 Self::PREFIX
74 ))
75 })?;
76
77 if nullifier
78 .chars()
79 .any(|c| !c.is_ascii_hexdigit() || c.is_ascii_uppercase())
80 {
81 return Err(PrimitiveError::Deserialization(
82 "nullifier has invalid characters. only lowercase hex characters allowed."
83 .to_string(),
84 ));
85 }
86
87 if nullifier.len() != Self::ENCODING_LENGTH {
88 return Err(PrimitiveError::Deserialization(format!(
89 "nullifier does not have the right length. length: {}",
90 nullifier.len()
91 )));
92 }
93
94 let nullifier = FieldElement::from_str(nullifier)?;
95
96 Ok(Self { inner: nullifier })
97 }
98}
99
100impl Serialize for Nullifier {
101 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102 where
103 S: Serializer,
104 {
105 if serializer.is_human_readable() {
106 serializer.serialize_str(&self.to_canonical_string())
107 } else {
108 serializer.serialize_bytes(&self.inner.to_be_bytes())
110 }
111 }
112}
113
114impl<'de> Deserialize<'de> for Nullifier {
115 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
116 where
117 D: Deserializer<'de>,
118 {
119 if deserializer.is_human_readable() {
120 let value = String::deserialize(deserializer)?;
121 Self::from_canonical_string(value).map_err(|e| D::Error::custom(e.to_string()))
122 } else {
123 let bytes = Vec::deserialize(deserializer)?;
124 let bytes: [u8; 32] = bytes
125 .try_into()
126 .map_err(|_| D::Error::custom("expected 32 bytes"))?;
127 let nullifier = FieldElement::from_be_bytes(&bytes)
128 .map_err(|_| D::Error::custom("invalid field element"))?;
129 Ok(Self { inner: nullifier })
130 }
131 }
132}
133
134impl Display for Nullifier {
135 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136 self.to_canonical_string().fmt(f)
137 }
138}
139
140impl From<Nullifier> for FieldElement {
141 fn from(value: Nullifier) -> Self {
142 value.inner
143 }
144}
145
146impl From<FieldElement> for Nullifier {
147 fn from(value: FieldElement) -> Self {
148 Self { inner: value }
149 }
150}
151
152impl From<Nullifier> for U256 {
153 fn from(value: Nullifier) -> Self {
154 value.as_number()
155 }
156}
157
158impl Deref for Nullifier {
159 type Target = FieldElement;
160 fn deref(&self) -> &Self::Target {
161 &self.inner
162 }
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
178pub struct SessionNullifier {
179 nullifier: FieldElement,
181 action: FieldElement,
183}
184
185impl SessionNullifier {
186 const JSON_PREFIX: &str = "snil_";
187
188 #[must_use]
190 pub const fn new(nullifier: FieldElement, action: FieldElement) -> Self {
191 Self { nullifier, action }
192 }
193
194 #[must_use]
196 pub const fn nullifier(&self) -> FieldElement {
197 self.nullifier
198 }
199
200 #[must_use]
202 pub const fn action(&self) -> FieldElement {
203 self.action
204 }
205
206 #[must_use]
210 pub fn as_ethereum_representation(&self) -> [U256; 2] {
211 [self.nullifier.into(), self.action.into()]
212 }
213
214 pub fn from_ethereum_representation(value: [U256; 2]) -> Result<Self, String> {
219 let nullifier =
220 FieldElement::try_from(value[0]).map_err(|e| format!("invalid nullifier: {e}"))?;
221 let action =
222 FieldElement::try_from(value[1]).map_err(|e| format!("invalid action: {e}"))?;
223 Ok(Self { nullifier, action })
224 }
225
226 #[must_use]
228 pub fn to_compressed_bytes(&self) -> [u8; 64] {
229 let mut bytes = [0u8; 64];
230 bytes[..32].copy_from_slice(&self.nullifier.to_be_bytes());
231 bytes[32..].copy_from_slice(&self.action.to_be_bytes());
232 bytes
233 }
234
235 pub fn from_compressed_bytes(bytes: &[u8]) -> Result<Self, String> {
240 if bytes.len() != 64 {
241 return Err(format!(
242 "Invalid length: expected 64 bytes, got {}",
243 bytes.len()
244 ));
245 }
246
247 let nullifier = FieldElement::from_be_bytes(bytes[..32].try_into().unwrap())
248 .map_err(|e| format!("invalid nullifier: {e}"))?;
249 let action = FieldElement::from_be_bytes(bytes[32..].try_into().unwrap())
250 .map_err(|e| format!("invalid action: {e}"))?;
251
252 Ok(Self { nullifier, action })
253 }
254}
255
256impl Default for SessionNullifier {
257 fn default() -> Self {
258 Self {
259 nullifier: FieldElement::ZERO,
260 action: FieldElement::ZERO,
261 }
262 }
263}
264
265impl Serialize for SessionNullifier {
266 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
267 where
268 S: Serializer,
269 {
270 let bytes = self.to_compressed_bytes();
271 if serializer.is_human_readable() {
272 serializer.serialize_str(&format!("{}{}", Self::JSON_PREFIX, hex::encode(bytes)))
274 } else {
275 serializer.serialize_bytes(&bytes)
277 }
278 }
279}
280
281impl<'de> Deserialize<'de> for SessionNullifier {
282 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
283 where
284 D: Deserializer<'de>,
285 {
286 let bytes = if deserializer.is_human_readable() {
287 let value = String::deserialize(deserializer)?;
288 let hex_str = value.strip_prefix(Self::JSON_PREFIX).ok_or_else(|| {
289 D::Error::custom(format!(
290 "session nullifier must start with '{}'",
291 Self::JSON_PREFIX
292 ))
293 })?;
294 hex::decode(hex_str).map_err(D::Error::custom)?
295 } else {
296 Vec::deserialize(deserializer)?
297 };
298
299 Self::from_compressed_bytes(&bytes).map_err(D::Error::custom)
300 }
301}
302
303impl From<SessionNullifier> for [U256; 2] {
304 fn from(value: SessionNullifier) -> Self {
305 value.as_ethereum_representation()
306 }
307}
308
309impl From<(FieldElement, FieldElement)> for SessionNullifier {
310 fn from((nullifier, action): (FieldElement, FieldElement)) -> Self {
311 Self::new(nullifier, action)
312 }
313}
314
315#[cfg(test)]
316mod nullifier_tests {
317 use super::*;
318 use ruint::uint;
319
320 fn nil(value: u64) -> Nullifier {
321 Nullifier::new(FieldElement::from(value))
322 }
323
324 #[test]
325 fn canonical_string_roundtrip() {
326 let nullifier = nil(42);
327 let canonical = nullifier.to_canonical_string();
328 let recovered = Nullifier::from_canonical_string(canonical.clone()).unwrap();
329 assert_eq!(nullifier, recovered);
330
331 let to_string_representation = nullifier.to_string();
332 assert_eq!(to_string_representation, canonical);
333 }
334
335 #[test]
336 fn canonical_string_roundtrip_zero() {
337 let nullifier = nil(0);
338 let canonical = nullifier.to_canonical_string();
339 assert_eq!(
340 canonical,
341 "nil_0000000000000000000000000000000000000000000000000000000000000000"
342 );
343 let recovered = Nullifier::from_canonical_string(canonical).unwrap();
344 assert_eq!(nullifier, recovered);
345 }
346
347 #[test]
348 fn canonical_string_roundtrip_large_value() {
349 let fe = FieldElement::try_from(uint!(
350 0x11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2_U256
351 ))
352 .unwrap();
353 let nullifier = Nullifier::new(fe);
354 let canonical = nullifier.to_canonical_string();
355 assert_eq!(
356 canonical,
357 "nil_11d223ce7b91ac212f42cf50f0a3439ae3fcdba4ea32acb7f194d1051ed324c2"
358 );
359 let recovered = Nullifier::from_canonical_string(canonical).unwrap();
360 assert_eq!(nullifier, recovered);
361 }
362
363 #[test]
364 fn canonical_string_is_lowercase_and_zero_padded() {
365 let canonical = nil(0xff).to_canonical_string();
366 let hex_part = canonical.strip_prefix("nil_").unwrap();
367 assert!(
368 hex_part
369 .chars()
370 .all(|c| c.is_ascii_digit() || matches!(c, 'a'..='f'))
371 );
372 assert_eq!(hex_part.len(), 64);
373 assert!(
374 hex_part.starts_with("000000000000000000000000000000000000000000000000000000000000")
375 );
376 assert!(hex_part.ends_with("ff"));
377 }
378
379 #[test]
380 fn rejects_missing_prefix() {
381 let s = "0000000000000000000000000000000000000000000000000000000000000001";
382 let err = Nullifier::from_canonical_string(s.to_string()).unwrap_err();
383 assert_eq!(
384 err.to_string(),
385 "Deserialization error: nullifier must start with the nil_".to_string()
386 );
387 }
388
389 #[test]
390 fn rejects_wrong_prefix() {
391 let s = "nul_0000000000000000000000000000000000000000000000000000000000000001";
392 let err = Nullifier::from_canonical_string(s.to_string()).unwrap_err();
393 assert_eq!(
394 err.to_string(),
395 "Deserialization error: nullifier must start with the nil_".to_string()
396 );
397 }
398
399 #[test]
400 fn rejects_uppercase_hex() {
401 let s = "nil_000000000000000000000000000000000000000000000000000000000000000A";
402 let err = Nullifier::from_canonical_string(s.to_string()).unwrap_err();
403 assert_eq!(
404 err.to_string(),
405 "Deserialization error: nullifier has invalid characters. only lowercase hex characters allowed.".to_string()
406 );
407 }
408
409 #[test]
410 fn rejects_mixed_case() {
411 let s = "nil_000000000000000000000000000000000000000000000000000000000000aAbB";
412 let err = Nullifier::from_canonical_string(s.to_string()).unwrap_err();
413 assert_eq!(
414 err.to_string(),
415 "Deserialization error: nullifier has invalid characters. only lowercase hex characters allowed.".to_string()
416 );
417 }
418
419 #[test]
420 fn rejects_unpadded_short() {
421 let s = "nil_a";
423 let err = Nullifier::from_canonical_string(s.to_string()).unwrap_err();
424 assert_eq!(
425 err.to_string(),
426 "Deserialization error: nullifier does not have the right length. length: 1"
427 .to_string()
428 );
429 }
430
431 #[test]
432 fn rejects_too_long() {
433 let s = "nil_00000000000000000000000000000000000000000000000000000000000000001";
434 assert!(Nullifier::from_canonical_string(s.to_string()).is_err());
435 }
436
437 #[test]
438 fn rejects_non_hex_characters() {
439 let s = "nil_000000000000000000000000000000000000000000000000000000000000gggg";
440 assert!(Nullifier::from_canonical_string(s.to_string()).is_err());
441 }
442
443 #[test]
444 fn rejects_0x_prefix_inside_canonical() {
445 let s = "nil_0x0000000000000000000000000000000000000000000000000000000000000a";
446 assert!(Nullifier::from_canonical_string(s.to_string()).is_err());
447 }
448
449 #[test]
450 fn non_canonical_representations_of_same_value_rejected() {
451 let non_canonical = [
453 "nil_000000000000000000000000000000000000000000000000000000000000000A", "nil_a", "nil_0a", "nil_0A", "nil_00000000000000000000000000000000000000000000000000000000000000a", "nil_0000000000000000000000000000000000000000000000000000000000000000a", ];
460
461 for s in non_canonical {
462 assert!(
463 Nullifier::from_canonical_string(s.to_string()).is_err(),
464 "should reject non-canonical: {s}"
465 );
466 }
467 }
468
469 #[test]
470 fn as_number_returns_inner_u256() {
471 let nullifier = nil(12345);
472 assert_eq!(nullifier.as_number(), U256::from(12345));
473 }
474
475 #[test]
476 fn json_roundtrip() {
477 let nullifier = nil(42);
478 let json = serde_json::to_string(&nullifier).unwrap();
479 let recovered: Nullifier = serde_json::from_str(&json).unwrap();
480 assert_eq!(nullifier, recovered);
481 }
482
483 #[test]
484 fn json_uses_canonical_format() {
485 let nullifier = nil(255);
486 let json = serde_json::to_string(&nullifier).unwrap();
487 let expected = format!("\"{}\"", nullifier.to_canonical_string());
488 assert_eq!(json, expected);
489 }
490
491 #[test]
492 fn json_rejects_non_canonical_input() {
493 let json = "\"nil_000000000000000000000000000000000000000000000000000000000000000A\"";
495 assert!(serde_json::from_str::<Nullifier>(json).is_err());
496
497 let json = "\"0000000000000000000000000000000000000000000000000000000000000001\"";
499 assert!(serde_json::from_str::<Nullifier>(json).is_err());
500 }
501
502 #[test]
503 fn cbor_roundtrip() {
504 let nullifier = nil(42);
505 let mut buf = Vec::new();
506 ciborium::into_writer(&nullifier, &mut buf).unwrap();
507 let recovered: Nullifier = ciborium::from_reader(&buf[..]).unwrap();
508 assert_eq!(nullifier, recovered);
509 }
510
511 #[test]
512 fn json_and_cbor_decode_to_same_value() {
513 let nullifier = nil(999);
514
515 let json = serde_json::to_string(&nullifier).unwrap();
516 let from_json: Nullifier = serde_json::from_str(&json).unwrap();
517
518 let mut cbor_buf = Vec::new();
519 ciborium::into_writer(&nullifier, &mut cbor_buf).unwrap();
520 let from_cbor: Nullifier = ciborium::from_reader(&cbor_buf[..]).unwrap();
521
522 assert_eq!(from_json, from_cbor);
523 }
524}
525
526#[cfg(test)]
527mod session_nullifier_tests {
528 use super::*;
529
530 fn test_field_element(value: u64) -> FieldElement {
531 FieldElement::from(value)
532 }
533
534 #[test]
535 fn test_new_and_accessors() {
536 let nullifier = test_field_element(1001);
537 let action = test_field_element(42);
538 let session = SessionNullifier::new(nullifier, action);
539
540 assert_eq!(session.nullifier(), nullifier);
541 assert_eq!(session.action(), action);
542 }
543
544 #[test]
545 fn test_as_ethereum_representation() {
546 let nullifier = test_field_element(100);
547 let action = test_field_element(200);
548 let session = SessionNullifier::new(nullifier, action);
549
550 let repr = session.as_ethereum_representation();
551 assert_eq!(repr[0], U256::from(100));
552 assert_eq!(repr[1], U256::from(200));
553 }
554
555 #[test]
556 fn test_from_ethereum_representation() {
557 let repr = [U256::from(100), U256::from(200)];
558 let session = SessionNullifier::from_ethereum_representation(repr).unwrap();
559
560 assert_eq!(session.nullifier(), test_field_element(100));
561 assert_eq!(session.action(), test_field_element(200));
562 }
563
564 #[test]
565 fn test_json_roundtrip() {
566 let session = SessionNullifier::new(test_field_element(1001), test_field_element(42));
567 let json = serde_json::to_string(&session).unwrap();
568
569 assert!(json.starts_with("\"snil_"));
571 assert!(json.ends_with('"'));
572
573 let decoded: SessionNullifier = serde_json::from_str(&json).unwrap();
575 assert_eq!(session, decoded);
576 }
577
578 #[test]
579 fn test_json_format() {
580 let session = SessionNullifier::new(test_field_element(1), test_field_element(2));
581 let json = serde_json::to_string(&session).unwrap();
582
583 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
585 assert!(parsed.is_string());
586 let value = parsed.as_str().unwrap();
587 assert!(value.starts_with("snil_"));
588 }
589
590 #[test]
591 fn test_bytes_roundtrip() {
592 let session = SessionNullifier::new(test_field_element(1001), test_field_element(42));
593 let bytes = session.to_compressed_bytes();
594
595 assert_eq!(bytes.len(), 64); let decoded = SessionNullifier::from_compressed_bytes(&bytes).unwrap();
598 assert_eq!(session, decoded);
599 }
600
601 #[test]
602 fn test_bytes_use_field_element_encoding() {
603 let session = SessionNullifier::new(test_field_element(1001), test_field_element(42));
604 let bytes = session.to_compressed_bytes();
605
606 let mut expected = [0u8; 64];
607 expected[..32].copy_from_slice(&session.nullifier().to_be_bytes());
608 expected[32..].copy_from_slice(&session.action().to_be_bytes());
609 assert_eq!(bytes, expected);
610 }
611
612 #[test]
613 fn test_invalid_bytes_length() {
614 let too_short = vec![0u8; 63];
615 let result = SessionNullifier::from_compressed_bytes(&too_short);
616 assert!(result.is_err());
617 assert!(result.unwrap_err().contains("Invalid length"));
618
619 let too_long = vec![0u8; 65];
620 let result = SessionNullifier::from_compressed_bytes(&too_long);
621 assert!(result.is_err());
622 assert!(result.unwrap_err().contains("Invalid length"));
623 }
624
625 #[test]
626 fn test_default() {
627 let session = SessionNullifier::default();
628 assert_eq!(session.nullifier(), FieldElement::ZERO);
629 assert_eq!(session.action(), FieldElement::ZERO);
630 }
631
632 #[test]
633 fn test_from_tuple() {
634 let nullifier = test_field_element(100);
635 let action = test_field_element(200);
636 let session: SessionNullifier = (nullifier, action).into();
637
638 assert_eq!(session.nullifier(), nullifier);
639 assert_eq!(session.action(), action);
640 }
641
642 #[test]
643 fn test_into_u256_array() {
644 let session = SessionNullifier::new(test_field_element(100), test_field_element(200));
645 let arr: [U256; 2] = session.into();
646
647 assert_eq!(arr[0], U256::from(100));
648 assert_eq!(arr[1], U256::from(200));
649 }
650}