mithril_stm/circuits/halo2/off_circuit/
unique_signature.rs

1use ff::Field;
2use group::Group;
3use rand_core::{CryptoRng, RngCore};
4
5use crate::circuits::halo2::constants::DST_UNIQUE_SIGNATURE;
6use crate::circuits::halo2::hash::{HashCPU, HashToCurveCPU, JubjubHashToCurve, PoseidonHash};
7use crate::circuits::halo2::off_circuit::error::SignatureError;
8use crate::circuits::halo2::off_circuit::utils::{
9    get_coordinates, is_on_curve, jubjub_base_to_scalar,
10};
11use crate::circuits::halo2::types::{Jubjub, JubjubBase, JubjubScalar, JubjubSubgroup};
12
13#[derive(Debug, Clone)]
14pub struct SigningKey(JubjubScalar);
15
16impl SigningKey {
17    pub fn generate(rng: &mut (impl RngCore + CryptoRng)) -> Self {
18        let sk = JubjubScalar::random(rng);
19        SigningKey(sk)
20    }
21
22    pub fn sign(&self, msg: &[JubjubBase], rng: &mut (impl RngCore + CryptoRng)) -> Signature {
23        let g = JubjubSubgroup::generator();
24        let vk = g * self.0;
25
26        let hash = JubjubHashToCurve::hash_to_curve(msg);
27        let sigma = hash * self.0;
28        let r = JubjubScalar::random(rng);
29        let cap_r_1 = hash * r;
30        let cap_r_2 = g * r;
31
32        let (hx, hy) = get_coordinates(hash);
33        let (vk_x, vk_y) = get_coordinates(vk);
34        let (sigma_x, sigma_y) = get_coordinates(sigma);
35        let (cap_r_1_x, cap_r_1_y) = get_coordinates(cap_r_1);
36        let (cap_r_2_x, cap_r_2_y) = get_coordinates(cap_r_2);
37
38        let c = PoseidonHash::hash(&[
39            DST_UNIQUE_SIGNATURE,
40            hx,
41            hy,
42            vk_x,
43            vk_y,
44            sigma_x,
45            sigma_y,
46            cap_r_1_x,
47            cap_r_1_y,
48            cap_r_2_x,
49            cap_r_2_y,
50        ]);
51        let c_scalar = jubjub_base_to_scalar(c);
52        let s = r - self.0 * c_scalar;
53
54        Signature { sigma, s, c }
55    }
56
57    pub fn sign_long(
58        &self,
59        msg: &[JubjubBase],
60        rng: &mut (impl RngCore + CryptoRng),
61    ) -> LongSignature {
62        let g = JubjubSubgroup::generator();
63        let vk = g * self.0;
64
65        let hash = JubjubHashToCurve::hash_to_curve(msg);
66        let sigma = hash * self.0;
67        let r = JubjubScalar::random(rng);
68        let cap_r_1 = hash * r;
69        let cap_r_2 = g * r;
70
71        let (hx, hy) = get_coordinates(hash);
72        let (vk_x, vk_y) = get_coordinates(vk);
73        let (sigma_x, sigma_y) = get_coordinates(sigma);
74        let (cap_r_1_x, cap_r_1_y) = get_coordinates(cap_r_1);
75        let (cap_r_2_x, cap_r_2_y) = get_coordinates(cap_r_2);
76
77        let c = PoseidonHash::hash(&[
78            DST_UNIQUE_SIGNATURE,
79            hx,
80            hy,
81            vk_x,
82            vk_y,
83            sigma_x,
84            sigma_y,
85            cap_r_1_x,
86            cap_r_1_y,
87            cap_r_2_x,
88            cap_r_2_y,
89        ]);
90        let c_scalar = jubjub_base_to_scalar(c);
91        let s = r - self.0 * c_scalar;
92
93        LongSignature {
94            sigma,
95            cap_r_1,
96            cap_r_2,
97            s,
98        }
99    }
100}
101
102#[derive(Debug, Clone, Copy, Default)]
103pub struct VerificationKey(pub JubjubSubgroup);
104
105impl From<&SigningKey> for VerificationKey {
106    fn from(sk: &SigningKey) -> Self {
107        let g = JubjubSubgroup::generator();
108        let vk = g * sk.0;
109        VerificationKey(vk)
110    }
111}
112
113impl VerificationKey {
114    pub fn to_field(&self) -> [JubjubBase; 2] {
115        let (x, y) = get_coordinates(self.0);
116        [x, y]
117    }
118
119    pub fn to_bytes(&self) -> [u8; 64] {
120        let (x, y) = get_coordinates(self.0);
121        let mut bytes = [0u8; 64];
122        bytes[0..32].copy_from_slice(&x.to_bytes_le());
123        bytes[32..64].copy_from_slice(&y.to_bytes_le());
124        bytes
125    }
126
127    pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError> {
128        let bytes = bytes.get(0..64).ok_or(SignatureError::SerializationError)?;
129        let mut u_bytes = [0u8; 32];
130        u_bytes.copy_from_slice(&bytes[0..32]);
131        let mut v_bytes = [0u8; 32];
132        v_bytes.copy_from_slice(&bytes[32..64]);
133
134        let u = JubjubBase::from_bytes_le(&u_bytes)
135            .into_option()
136            .ok_or(SignatureError::SerializationError)?;
137        let v = JubjubBase::from_bytes_le(&v_bytes)
138            .into_option()
139            .ok_or(SignatureError::SerializationError)?;
140        if !bool::from(is_on_curve(u, v)) {
141            return Err(SignatureError::SerializationError);
142        }
143
144        let point = JubjubSubgroup::from_raw_unchecked(u, v);
145        if !bool::from(Jubjub::from(point).is_prime_order()) {
146            return Err(SignatureError::SerializationError);
147        }
148
149        Ok(VerificationKey(point))
150    }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct Signature {
155    pub sigma: JubjubSubgroup,
156    pub s: JubjubScalar,
157    pub c: JubjubBase,
158}
159
160impl Signature {
161    /// Verify a signature against a verification key.
162    pub fn verify(&self, msg: &[JubjubBase], vk: &VerificationKey) -> Result<(), SignatureError> {
163        let g = JubjubSubgroup::generator();
164        let hash = JubjubHashToCurve::hash_to_curve(msg);
165        let c_scalar = jubjub_base_to_scalar(self.c);
166
167        let (hx, hy) = get_coordinates(hash);
168        let (vk_x, vk_y) = get_coordinates(vk.0);
169        let (sigma_x, sigma_y) = get_coordinates(self.sigma);
170        let cap_r_1_prime = hash * self.s + self.sigma * c_scalar;
171        let cap_r_2_prime = g * self.s + vk.0 * c_scalar;
172        let (cap_r_1_x_prime, cap_r_1_y_prime) = get_coordinates(cap_r_1_prime);
173        let (cap_r_2_x_prime, cap_r_2_y_prime) = get_coordinates(cap_r_2_prime);
174        let c_prime = PoseidonHash::hash(&[
175            DST_UNIQUE_SIGNATURE,
176            hx,
177            hy,
178            vk_x,
179            vk_y,
180            sigma_x,
181            sigma_y,
182            cap_r_1_x_prime,
183            cap_r_1_y_prime,
184            cap_r_2_x_prime,
185            cap_r_2_y_prime,
186        ]);
187
188        if c_prime != self.c {
189            return Err(SignatureError::VerificationFailed);
190        }
191
192        Ok(())
193    }
194
195    pub fn sigma(&self) -> (JubjubBase, JubjubBase) {
196        let (x, y) = get_coordinates(self.sigma);
197        (x, y)
198    }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct LongSignature {
203    pub sigma: JubjubSubgroup,
204    pub cap_r_1: JubjubSubgroup,
205    pub cap_r_2: JubjubSubgroup,
206    pub s: JubjubScalar,
207}
208
209impl LongSignature {
210    pub fn verify(&self, msg: &[JubjubBase], vk: &VerificationKey) -> Result<(), SignatureError> {
211        let g = JubjubSubgroup::generator();
212        let hash = JubjubHashToCurve::hash_to_curve(msg);
213        let (hx, hy) = get_coordinates(hash);
214        let (vk_x, vk_y) = get_coordinates(vk.0);
215        let (sigma_x, sigma_y) = get_coordinates(self.sigma);
216        let (cap_r_1_x, cap_r_1_y) = get_coordinates(self.cap_r_1);
217        let (cap_r_2_x, cap_r_2_y) = get_coordinates(self.cap_r_2);
218
219        let c_prime = PoseidonHash::hash(&[
220            DST_UNIQUE_SIGNATURE,
221            hx,
222            hy,
223            vk_x,
224            vk_y,
225            sigma_x,
226            sigma_y,
227            cap_r_1_x,
228            cap_r_1_y,
229            cap_r_2_x,
230            cap_r_2_y,
231        ]);
232        let c = jubjub_base_to_scalar(c_prime);
233
234        {
235            let right = hash * self.s + self.sigma * c;
236            if self.cap_r_1 != right {
237                return Err(SignatureError::VerificationFailed);
238            }
239        }
240
241        {
242            let right = g * self.s + vk.0 * c;
243            if self.cap_r_2 != right {
244                return Err(SignatureError::VerificationFailed);
245            }
246        }
247
248        Ok(())
249    }
250
251    pub fn to_short_signature(&self, hash_msg: &JubjubSubgroup, vk: &VerificationKey) -> Signature {
252        let (hx, hy) = get_coordinates(*hash_msg);
253        let (vk_x, vk_y) = get_coordinates(vk.0);
254        let (sigma_x, sigma_y) = get_coordinates(self.sigma);
255        let (cap_r_1_x, cap_r_1_y) = get_coordinates(self.cap_r_1);
256        let (cap_r_2_x, cap_r_2_y) = get_coordinates(self.cap_r_2);
257
258        let c = PoseidonHash::hash(&[
259            DST_UNIQUE_SIGNATURE,
260            hx,
261            hy,
262            vk_x,
263            vk_y,
264            sigma_x,
265            sigma_y,
266            cap_r_1_x,
267            cap_r_1_y,
268            cap_r_2_x,
269            cap_r_2_y,
270        ]);
271
272        Signature {
273            sigma: self.sigma,
274            s: self.s,
275            c,
276        }
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283    use rand_core::OsRng;
284
285    /// Test signing functionality.
286    #[test]
287    fn test_signature_unique_verification_valid() {
288        let mut rng = OsRng;
289        let sk = SigningKey::generate(&mut rng);
290        let msg = JubjubBase::random(&mut rng);
291
292        // Sign the message
293        let signature = sk.sign(&[msg], &mut rng);
294
295        // Ensure the components of the signature are non-default values
296        assert_ne!(
297            signature.sigma,
298            JubjubSubgroup::identity(),
299            "Signature sigma should not be the identity element."
300        );
301        assert_ne!(
302            signature.s,
303            JubjubScalar::ZERO,
304            "Signature s component should not be zero."
305        );
306        assert_ne!(
307            signature.c,
308            JubjubBase::ZERO,
309            "Signature c component should not be zero."
310        );
311
312        signature.verify(&[msg], &VerificationKey::from(&sk)).unwrap();
313    }
314
315    #[test]
316    fn test_signature_unique_verification_invalid_signature() {
317        let mut rng = OsRng;
318        let sk = SigningKey::generate(&mut rng);
319        let msg = JubjubBase::random(&mut rng);
320        let vk: VerificationKey = (&sk).into();
321
322        // Generate signature and tamper with it
323        let mut signature = sk.sign(&[msg], &mut rng);
324        signature.s = JubjubScalar::random(&mut rng); // Modify `s` component
325
326        // Verify the modified signature
327        let result = signature.verify(&[msg], &vk);
328        assert!(
329            result.is_err(),
330            "Invalid signature should fail verification, but it passed."
331        );
332    }
333
334    #[test]
335    fn test_signature_unique_long_verification_valid() {
336        let mut rng = OsRng;
337        let sk = SigningKey::generate(&mut rng);
338        let vk: VerificationKey = (&sk).into();
339        let msg = JubjubBase::random(&mut rng);
340
341        // Generate a regular Signature and convert it to SignatureLong
342        let sig_long = sk.sign_long(&[msg], &mut rng);
343        // Verify the SignatureLong
344        assert!(sig_long.verify(&[msg], &vk).is_ok());
345
346        let hash_msg = JubjubHashToCurve::hash_to_curve(&[msg]);
347        let sig = sig_long.to_short_signature(&hash_msg, &vk);
348        assert!(sig.verify(&[msg], &vk).is_ok());
349    }
350}