world_id_primitives/
rp.rs

1#![allow(clippy::unreadable_literal)]
2
3use std::{fmt, str::FromStr};
4
5use ark_serde_compat::babyjubjub;
6use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
7
8use crate::FieldElement;
9
10/// The id of a relying party.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct RpId(u128);
13
14impl RpId {
15    /// Converts the RP id to an u128
16    #[must_use]
17    pub const fn into_inner(self) -> u128 {
18        self.0
19    }
20
21    /// Creates a new `RpId` by wrapping a `u128`
22    #[must_use]
23    pub const fn new(value: u128) -> Self {
24        Self(value)
25    }
26}
27
28impl fmt::Display for RpId {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        write!(f, "rp_{:032x}", self.0)
31    }
32}
33
34impl FromStr for RpId {
35    type Err = String;
36
37    fn from_str(s: &str) -> Result<Self, Self::Err> {
38        if let Some(id) = s.strip_prefix("rp_") {
39            Ok(Self(u128::from_str_radix(id, 16).map_err(|_| {
40                "Invalid RP ID format: expected hex string".to_string()
41            })?))
42        } else {
43            Err("A valid RP ID must start with 'rp_'".to_string())
44        }
45    }
46}
47
48impl From<u128> for RpId {
49    fn from(value: u128) -> Self {
50        Self(value)
51    }
52}
53
54impl From<RpId> for FieldElement {
55    fn from(value: RpId) -> Self {
56        Self::from(value.0)
57    }
58}
59
60impl Serialize for RpId {
61    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62    where
63        S: Serializer,
64    {
65        if serializer.is_human_readable() {
66            serializer.serialize_str(&self.to_string())
67        } else {
68            serializer.serialize_u128(self.0)
69        }
70    }
71}
72
73impl<'de> Deserialize<'de> for RpId {
74    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75    where
76        D: Deserializer<'de>,
77    {
78        if deserializer.is_human_readable() {
79            let s = String::deserialize(deserializer)?;
80            Self::from_str(&s).map_err(D::Error::custom)
81        } else {
82            let value = u128::deserialize(deserializer)?;
83            Ok(Self(value))
84        }
85    }
86}
87
88/// The public key of a relying party, used to verify computed nullifiers.
89///
90/// Constructed by multiplying the `BabyJubJub` generator with the secret shared among the OPRF peers.
91///
92/// TODO: Refactor to use `EdDSAPublicKey` instead and serialize compressed.
93#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
94#[serde(transparent)]
95pub struct RpNullifierKey(
96    #[serde(serialize_with = "babyjubjub::serialize_affine")]
97    #[serde(deserialize_with = "babyjubjub::deserialize_affine")]
98    ark_babyjubjub::EdwardsAffine,
99);
100
101impl RpNullifierKey {
102    /// Creates a new `RpNullifierKey` by wrapping an `EdwardsAffine` point
103    #[must_use]
104    pub const fn new(value: ark_babyjubjub::EdwardsAffine) -> Self {
105        Self(value)
106    }
107
108    /// Returns the inner `EdwardsAffine` point
109    pub const fn into_inner(self) -> ark_babyjubjub::EdwardsAffine {
110        self.0
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_rpid_display() {
120        let rp_id = RpId::new(0x123456789abcdef0);
121        assert_eq!(rp_id.to_string(), "rp_0000000000000000123456789abcdef0");
122
123        let rp_id = RpId::new(u128::MAX);
124        assert_eq!(rp_id.to_string(), "rp_ffffffffffffffffffffffffffffffff");
125
126        let rp_id = RpId::new(0);
127        assert_eq!(rp_id.to_string(), "rp_00000000000000000000000000000000");
128    }
129
130    #[test]
131    fn test_rpid_from_str() {
132        let rp_id = "rp_0000000000000000123456789abcdef0"
133            .parse::<RpId>()
134            .unwrap();
135        assert_eq!(rp_id.0, 0x123456789abcdef0);
136
137        let rp_id = "rp_ffffffffffffffffffffffffffffffff"
138            .parse::<RpId>()
139            .unwrap();
140        assert_eq!(rp_id.0, u128::MAX);
141
142        let rp_id = "rp_00000000000000000000000000000000"
143            .parse::<RpId>()
144            .unwrap();
145        assert_eq!(rp_id.0, 0);
146
147        let rp_id = "rp_0000000000000000123456789ABCDEF0"
148            .parse::<RpId>()
149            .unwrap();
150        assert_eq!(rp_id.0, 0x123456789abcdef0);
151    }
152
153    #[test]
154    fn test_rpid_from_str_errors() {
155        assert!("123456789abcdef0".parse::<RpId>().is_err());
156        assert!("rp_invalid".parse::<RpId>().is_err());
157        assert!("rp_".parse::<RpId>().is_err());
158    }
159
160    #[test]
161    fn test_rpid_roundtrip() {
162        let original = RpId::new(0x123456789abcdef0);
163        let s = original.to_string();
164        let parsed = s.parse::<RpId>().unwrap();
165        assert_eq!(original, parsed);
166    }
167
168    #[test]
169    fn test_rpid_json_serialization() {
170        let rp_id = RpId::new(0x123456789abcdef0);
171        let json = serde_json::to_string(&rp_id).unwrap();
172        assert_eq!(json, "\"rp_0000000000000000123456789abcdef0\"");
173
174        let deserialized: RpId = serde_json::from_str(&json).unwrap();
175        assert_eq!(rp_id, deserialized);
176    }
177
178    #[test]
179    fn test_rpid_binary_serialization() {
180        let rp_id = RpId::new(0x123456789abcdef0);
181
182        let mut buffer = Vec::new();
183        ciborium::into_writer(&rp_id, &mut buffer).unwrap();
184
185        let decoded: RpId = ciborium::from_reader(&buffer[..]).unwrap();
186
187        assert_eq!(rp_id, decoded);
188    }
189}