Skip to main content

krusty_kms_common/
types.rs

1//! Common type definitions for TONGO protocol.
2
3use serde::{Deserialize, Serialize};
4use starknet_types_core::curve::{AffinePoint, ProjectivePoint};
5use starknet_types_core::felt::Felt;
6
7/// Represents a point on the Stark curve in serializable form.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct SerializablePoint {
10    pub x: String,
11    pub y: String,
12}
13
14impl SerializablePoint {
15    pub fn from_affine(point: &AffinePoint) -> Self {
16        Self {
17            x: format!("{:#x}", point.x()),
18            y: format!("{:#x}", point.y()),
19        }
20    }
21
22    /// Converts a projective point to a serializable point.
23    ///
24    /// # Errors
25    /// Returns `KmsError::PointAtInfinity` if the point is at infinity.
26    pub fn try_from_projective(point: &ProjectivePoint) -> crate::Result<Self> {
27        let affine = point
28            .to_affine()
29            .map_err(|_| crate::KmsError::PointAtInfinity)?;
30        Ok(Self::from_affine(&affine))
31    }
32
33    /// Converts a projective point to a serializable point.
34    ///
35    /// # Panics
36    /// Panics if the point is at infinity. Use `try_from_projective` for fallible conversion.
37    #[deprecated(
38        since = "0.2.0",
39        note = "Use try_from_projective for fallible conversion"
40    )]
41    pub fn from_projective(point: &ProjectivePoint) -> Self {
42        Self::try_from_projective(point).expect("Point at infinity cannot be serialized")
43    }
44
45    pub fn to_affine(&self) -> crate::Result<AffinePoint> {
46        let x = Felt::from_hex(&self.x)
47            .map_err(|e| crate::KmsError::DeserializationError(e.to_string()))?;
48        let y = Felt::from_hex(&self.y)
49            .map_err(|e| crate::KmsError::DeserializationError(e.to_string()))?;
50
51        AffinePoint::new(x, y)
52            .map_err(|e| crate::KmsError::InvalidPublicKey(format!("Invalid point: {:?}", e)))
53    }
54}
55
56/// Proof of Exponentiation (PoE) proof structure.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct PoeProof {
59    #[serde(rename = "A")]
60    pub a: SerializablePoint,
61    pub s: String,
62    pub c: String,
63}
64
65/// Proof of Exponentiation 2 (PoE2) proof structure.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct Poe2Proof {
68    #[serde(rename = "A")]
69    pub a: SerializablePoint,
70    pub s1: String,
71    pub s2: String,
72    pub c: String,
73}
74
75/// ElGamal encryption proof structure.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ElGamalProof {
78    #[serde(rename = "AL")]
79    pub al: SerializablePoint,
80    #[serde(rename = "AR")]
81    pub ar: SerializablePoint,
82    pub sb: String,
83    pub sr: String,
84    pub c: String,
85}
86
87/// Audit proof structure (SameEncryptUnknownRandom protocol).
88/// Proves that two ciphertexts encrypt the same plaintext.
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct AuditProof {
91    #[serde(rename = "Ax")]
92    pub ax: SerializablePoint,
93    #[serde(rename = "AL0")]
94    pub al0: SerializablePoint,
95    #[serde(rename = "AL1")]
96    pub al1: SerializablePoint,
97    #[serde(rename = "AR1")]
98    pub ar1: SerializablePoint,
99    pub sx: String,
100    pub sb: String,
101    pub sr: String,
102    pub c: String,
103}
104
105/// ElGamal ciphertext.
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct ElGamalCiphertext {
108    pub l: ProjectivePoint,
109    pub r: ProjectivePoint,
110}
111
112/// Tongo account state.
113#[derive(Debug, Clone, Default)]
114pub struct AccountState {
115    /// Available balance (can be spent immediately)
116    pub balance: u128,
117    /// Pending balance (requires rollover)
118    pub pending_balance: u128,
119    /// Nonce for replay protection
120    pub nonce: u64,
121}
122
123/// Transaction types in TONGO protocol.
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
125#[serde(rename_all = "lowercase")]
126pub enum TransactionType {
127    Fund,
128    Send,
129    Rollover,
130    Withdraw,
131}
132
133/// Proof of Bit (proves a committed value is either 0 or 1).
134/// This is an OR proof: either V = h^r (bit=0) OR V/g = h^r (bit=1).
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct ProofOfBit {
137    #[serde(rename = "A0")]
138    pub a0: SerializablePoint,
139    #[serde(rename = "A1")]
140    pub a1: SerializablePoint,
141    pub c0: String,
142    pub s0: String,
143    pub s1: String,
144}
145
146/// Range proof structure proving a value is in [0, 2^bit_size - 1].
147/// Contains commitments V_i = g^b_i * h^r_i for each bit and corresponding proofs.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct Range {
150    pub commitments: Vec<SerializablePoint>,
151    pub proofs: Vec<ProofOfBit>,
152}
153
154/// Proof of Transfer structure matching Cairo contract expectations.
155/// This proves:
156/// 1. Knowledge of private key (A_x, s_x)
157/// 2. Correct encryption for recipient and self (A_b, A_bar, s_b, s_r)
158/// 3. Transfer amount is in valid range (range, R_aux)
159/// 4. Leftover balance is in valid range (range2, R_aux2)
160/// 5. Balance equations verify correctly (A_b2, s_b2)
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct ProofOfTransfer {
163    #[serde(rename = "A_x")]
164    pub a_x: SerializablePoint,
165    #[serde(rename = "A_r")]
166    pub a_r: SerializablePoint,
167    #[serde(rename = "A_r2")]
168    pub a_r2: SerializablePoint,
169    #[serde(rename = "A_b")]
170    pub a_b: SerializablePoint,
171    #[serde(rename = "A_b2")]
172    pub a_b2: SerializablePoint,
173    #[serde(rename = "A_v")]
174    pub a_v: SerializablePoint,
175    #[serde(rename = "A_v2")]
176    pub a_v2: SerializablePoint,
177    #[serde(rename = "A_bar")]
178    pub a_bar: SerializablePoint,
179    pub s_x: String,
180    pub s_r: String,
181    pub s_b: String,
182    pub s_b2: String,
183    pub s_r2: String,
184    pub range: Range,
185    pub range2: Range,
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_serializable_point_from_affine() {
194        // Use valid generator point coordinates
195        let x = Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
196            .unwrap();
197        let y = Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
198            .unwrap();
199        let affine = AffinePoint::new(x, y).unwrap();
200        let point = SerializablePoint::from_affine(&affine);
201        assert!(point.x.starts_with("0x"));
202        assert!(point.y.starts_with("0x"));
203    }
204
205    #[test]
206    fn test_serializable_point_try_from_projective() {
207        // Use the generator point which is always valid
208        let g_x =
209            Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
210                .unwrap();
211        let g_y =
212            Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
213                .unwrap();
214        let projective = ProjectivePoint::from_affine(g_x, g_y).unwrap();
215
216        let result = SerializablePoint::try_from_projective(&projective);
217        assert!(result.is_ok());
218        let point = result.unwrap();
219        assert!(point.x.starts_with("0x"));
220        assert!(point.y.starts_with("0x"));
221    }
222
223    #[test]
224    fn test_serializable_point_try_from_projective_identity() {
225        // Identity point (point at infinity) should fail
226        let identity = ProjectivePoint::identity();
227        let result = SerializablePoint::try_from_projective(&identity);
228        assert!(result.is_err());
229    }
230
231    #[test]
232    fn test_serializable_point_to_affine() {
233        // Use valid generator point coordinates
234        let point = SerializablePoint {
235            x: "0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca".to_string(),
236            y: "0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f".to_string(),
237        };
238        let affine = point.to_affine().unwrap();
239        assert_eq!(
240            affine.x(),
241            Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
242                .unwrap()
243        );
244    }
245
246    #[test]
247    fn test_serializable_point_to_affine_invalid_hex() {
248        let point = SerializablePoint {
249            x: "invalid_hex".to_string(),
250            y: "0x2".to_string(),
251        };
252        let result = point.to_affine();
253        assert!(result.is_err());
254    }
255
256    #[test]
257    fn test_serializable_point_roundtrip() {
258        // Create a valid point, serialize, deserialize
259        let g_x =
260            Felt::from_hex("0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca")
261                .unwrap();
262        let g_y =
263            Felt::from_hex("0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f")
264                .unwrap();
265        let original = AffinePoint::new(g_x, g_y).unwrap();
266
267        let serialized = SerializablePoint::from_affine(&original);
268        let recovered = serialized.to_affine().unwrap();
269
270        assert_eq!(original.x(), recovered.x());
271        assert_eq!(original.y(), recovered.y());
272    }
273}