eth_stealth_gas_tickets/
lib.rs

1mod types;
2
3use alloy::primitives::{Bytes, FixedBytes};
4use blind_rsa_signatures::reexports::rsa::BigUint;
5use blind_rsa_signatures::reexports::rsa::PublicKeyParts;
6use blind_rsa_signatures::reexports::rsa::RsaPublicKey as BlindRsaPublicKey;
7use blind_rsa_signatures::{
8    BlindSignature, MessageRandomizer, Options, PublicKey, Secret, Signature,
9};
10use rand::{CryptoRng, RngCore};
11use sha2::{Digest, Sha256};
12use std::error::Error;
13pub use types::{BlindedSignature, SignedTicket, UnsignedTicket};
14
15/// Custom error for the library
16#[derive(Debug)]
17pub enum VerifierError {
18    BlindingFailed(String),
19    FinalizationFailed(String),
20    VerificationFailed(String),
21    LengthMismatch,
22    IdMismatch,
23}
24
25impl std::fmt::Display for VerifierError {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            VerifierError::BlindingFailed(e) => write!(f, "Blinding failed: {}", e),
29            VerifierError::FinalizationFailed(e) => write!(f, "Finalization failed: {}", e),
30            VerifierError::VerificationFailed(e) => write!(f, "Verification failed: {}", e),
31            VerifierError::LengthMismatch => write!(f, "Mismatched lengths between inputs"),
32            VerifierError::IdMismatch => {
33                write!(f, "ID mismatch between ticket and blind signature")
34            }
35        }
36    }
37}
38
39impl Error for VerifierError {}
40
41/// Represents a Coordinator's public key for blind signature operations
42pub struct TicketsVerifier {
43    pub public_key: PublicKey,
44}
45
46impl TicketsVerifier {
47    /// Create a new TicketsVerifier from a raw `PublicKey`
48    pub fn new(public_key: PublicKey) -> Self {
49        Self { public_key }
50    }
51
52    pub fn get_options(&self) -> Options {
53        Options::default()
54    }
55
56    /// Create a TicketsVerifier from a hex string
57    pub fn from_hex_string(hex_key: &str) -> Result<Self, VerifierError> {
58        // Remove the "0x" prefix
59        let hex = hex_key.trim_start_matches("0x");
60
61        // Extract the exponent (first 6 hex characters) and modulus (rest)
62        let exponent_hex = &hex[0..6]; // e is typically 0x010001
63        let modulus_hex = &hex[8..];
64
65        // Decode hex strings into bytes
66        let exponent_bytes = hex::decode(exponent_hex).expect("Invalid exponent hex");
67        let modulus_bytes = hex::decode(modulus_hex).expect("Invalid modulus hex");
68
69        // Convert bytes to BigUint
70        let exponent = BigUint::from_bytes_be(&exponent_bytes);
71        let modulus = BigUint::from_bytes_be(&modulus_bytes);
72
73        // Construct the DER representation for the public key
74        let blind_key = BlindRsaPublicKey::new(modulus, exponent).expect("Failed to convert key");
75
76        let public_key = PublicKey(blind_key);
77        Ok(Self { public_key })
78    }
79
80    pub fn to_hex_string(&self) -> String {
81        // Get the modulus (n) and exponent (e)
82        let modulus = self.public_key.n().to_bytes_be();
83        let exponent = self.public_key.e().to_bytes_be();
84
85        // Convert modulus and exponent to hex
86        let modulus_hex = hex::encode(modulus);
87        let exponent_hex = hex::encode(exponent);
88
89        // Concatenate exponent and modulus with a 0x prefix
90        format!("0x{}00{}", exponent_hex, modulus_hex)
91    }
92
93    /// Generate new blind tickets
94    pub fn new_blind_tickets<R: RngCore + CryptoRng>(
95        &self,
96        rng: &mut R,
97        count: usize,
98    ) -> Result<Vec<UnsignedTicket>, VerifierError> {
99        let mut tickets = Vec::new();
100        let options = Options::default();
101
102        for _ in 0..count {
103            // Generate a random 32-byte message
104            let mut msg = vec![0u8; 32];
105            rng.fill_bytes(&mut msg);
106
107            // Blind the message
108            let blinding_result = self
109                .public_key
110                .blind(rng, &msg, true, &options)
111                .map_err(|e| VerifierError::BlindingFailed(format!("{:?}", e)))?;
112
113            // Compute the ID (sha256 hash of the blind message)
114            let mut hasher = Sha256::new();
115            hasher.update(&blinding_result.blind_msg);
116            let id = FixedBytes::from_slice(&hasher.finalize());
117
118            let msg_randomizer = match blinding_result.msg_randomizer {
119                Some(r) => FixedBytes::from_slice(r.as_ref()),
120                None => FixedBytes::from_slice(&[0; 32]),
121            };
122
123            tickets.push(UnsignedTicket {
124                msg: Bytes::copy_from_slice(&msg),
125                blind_msg: Bytes::copy_from_slice(&blinding_result.blind_msg),
126                msg_randomizer: msg_randomizer,
127                id: id,
128                secret: Bytes::copy_from_slice(&blinding_result.secret),
129            });
130        }
131
132        Ok(tickets)
133    }
134
135    /// Finalize blind signatures into signed tickets
136    pub fn finalize_tickets(
137        &self,
138        tickets: Vec<UnsignedTicket>,
139        blind_signatures: Vec<BlindedSignature>,
140    ) -> Result<Vec<SignedTicket>, VerifierError> {
141        if tickets.len() != blind_signatures.len() {
142            return Err(VerifierError::LengthMismatch);
143        }
144
145        let options = Options::default();
146        let mut signed_tickets = Vec::new();
147
148        for (ticket, blind_sig) in tickets.into_iter().zip(blind_signatures.into_iter()) {
149            if ticket.id != blind_sig.id {
150                return Err(VerifierError::IdMismatch);
151            }
152
153            let sig = BlindSignature(blind_sig.blind_sig.to_vec());
154            let secret = Secret(ticket.secret.to_vec());
155            let msg = ticket.msg.to_vec();
156            let msg_randomizer = {
157                let raw = ticket.msg_randomizer.to_vec();
158                if raw.iter().all(|&b| b == 0) {
159                    None
160                } else {
161                    let mut arr = [0u8; 32];
162                    arr.copy_from_slice(&raw);
163                    Some(MessageRandomizer(arr))
164                }
165            };
166
167            let finalized_sig = self
168                .public_key
169                .finalize(&sig, &secret, msg_randomizer, &msg, &options)
170                .map_err(|e| VerifierError::FinalizationFailed(format!("{:?}", e)))?;
171
172            signed_tickets.push(SignedTicket {
173                msg: ticket.msg,
174                msg_randomizer: ticket.msg_randomizer,
175                finalized_sig: Bytes::copy_from_slice(&finalized_sig),
176            });
177        }
178
179        Ok(signed_tickets)
180    }
181
182    pub fn verify_signed_ticket(
183        &self,
184        signed_ticket: &SignedTicket,
185        options: &Options,
186    ) -> Result<(), VerifierError> {
187        let sig = Signature(signed_ticket.finalized_sig.to_vec());
188        let msg = signed_ticket.msg.to_vec();
189        let msg_randomizer = {
190            let raw = signed_ticket.msg_randomizer.to_vec();
191            if raw.iter().all(|&b| b == 0) {
192                None
193            } else {
194                let mut arr = [0u8; 32];
195                arr.copy_from_slice(&raw);
196                Some(MessageRandomizer(arr))
197            }
198        };
199
200        self.public_key
201            .verify(&sig, msg_randomizer, &msg, options)
202            .map_err(|_| VerifierError::VerificationFailed("Invalid signature".to_string()))?;
203
204        Ok(())
205    }
206
207    /// Verify signed tickets
208    pub fn verify_signed_tickets(
209        &self,
210        signed_tickets: Vec<SignedTicket>,
211    ) -> Result<(), VerifierError> {
212        let options = Options::default();
213        for ticket in signed_tickets {
214            self.verify_signed_ticket(&ticket, &options)?;
215        }
216
217        Ok(())
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use blind_rsa_signatures::KeyPair;
225    use rand::thread_rng;
226
227    #[test]
228    fn test_verifier_from_hex_string() {
229        // Generate a keypair and extract the public key
230        let mut rng = thread_rng();
231        let kp = KeyPair::generate(&mut rng, 2048).unwrap();
232        let pub_key = kp.pk;
233
234        // Convert public key to hex
235        let modulus = pub_key.n().to_bytes_be();
236        let exponent = pub_key.e().to_bytes_be();
237        let hex_key = format!("0x{}00{}", hex::encode(exponent), hex::encode(modulus));
238
239        // Create TicketsVerifier from hex
240        let coordinator_pubkey = TicketsVerifier::from_hex_string(&hex_key)
241            .expect("Failed to parse public key from hex");
242
243        // Ensure the modulus and exponent match
244        assert_eq!(coordinator_pubkey.public_key.n(), pub_key.n());
245        assert_eq!(coordinator_pubkey.public_key.e(), pub_key.e());
246    }
247
248    #[test]
249    fn test_new_blind_tickets() {
250        // Setup TicketsVerifier
251        let mut rng = thread_rng();
252        let kp = KeyPair::generate(&mut rng, 2048).unwrap();
253        let coordinator = TicketsVerifier::new(kp.pk);
254
255        // Generate blind tickets
256        let tickets = coordinator
257            .new_blind_tickets(&mut rng, 5)
258            .expect("Failed to generate blind tickets");
259
260        // Validate tickets
261        assert_eq!(tickets.len(), 5);
262        for ticket in tickets {
263            assert!(!ticket.msg.is_empty());
264            assert!(!ticket.blind_msg.is_empty());
265            assert!(!ticket.msg_randomizer.is_zero());
266            assert!(!ticket.id.is_zero());
267            assert!(!ticket.secret.is_empty());
268        }
269    }
270
271    #[test]
272    fn test_finalize_tickets() {
273        // Setup TicketsVerifier
274        let mut rng = thread_rng();
275        let kp = KeyPair::generate(&mut rng, 2048).unwrap();
276        let priv_key = kp.sk;
277        let coordinator = TicketsVerifier::new(kp.pk);
278
279        // Generate blind tickets
280        let tickets = coordinator
281            .new_blind_tickets(&mut rng, 3)
282            .expect("Failed to generate blind tickets");
283
284        // Sign the blind messages
285        let mut blind_signatures = Vec::new();
286        for ticket in &tickets {
287            let blind_sig = priv_key
288                .blind_sign(&mut rng, ticket.blind_msg.as_ref(), &Options::default())
289                .expect("Failed to sign blind message");
290
291            blind_signatures.push(BlindedSignature {
292                blind_sig: Bytes::copy_from_slice(&blind_sig),
293                id: ticket.id.clone(),
294            });
295        }
296
297        // Finalize tickets
298        let signed_tickets = coordinator
299            .finalize_tickets(tickets, blind_signatures)
300            .expect("Failed to finalize tickets");
301
302        // Validate signed tickets
303        assert_eq!(signed_tickets.len(), 3);
304        for signed_ticket in &signed_tickets {
305            assert!(!signed_ticket.msg.is_empty());
306            assert!(!signed_ticket.msg_randomizer.is_zero());
307            assert!(!signed_ticket.finalized_sig.is_empty());
308        }
309    }
310
311    #[test]
312    fn test_verify_signed_tickets() {
313        // Setup TicketsVerifier
314        let mut rng = thread_rng();
315        let kp = KeyPair::generate(&mut rng, 2048).unwrap();
316        let priv_key = kp.sk;
317        let coordinator = TicketsVerifier::new(kp.pk);
318
319        // Generate blind tickets
320        let tickets = coordinator
321            .new_blind_tickets(&mut rng, 3)
322            .expect("Failed to generate blind tickets");
323
324        // Sign the blind messages
325        let mut blind_signatures = Vec::new();
326        for ticket in &tickets {
327            let blind_sig = priv_key
328                .blind_sign(&mut rng, ticket.blind_msg.as_ref(), &Options::default())
329                .expect("Failed to sign blind message");
330
331            blind_signatures.push(BlindedSignature {
332                blind_sig: Bytes::copy_from_slice(&blind_sig),
333                id: ticket.id.clone(),
334            });
335        }
336
337        // Finalize tickets
338        let signed_tickets = coordinator
339            .finalize_tickets(tickets, blind_signatures)
340            .expect("Failed to finalize tickets");
341
342        // Verify signed tickets
343        coordinator
344            .verify_signed_tickets(signed_tickets)
345            .expect("Verification failed");
346    }
347}