mithril_stm/signature_scheme/unique_schnorr_signature/
signature.rs

1use anyhow::{Context, anyhow};
2use serde::{Deserialize, Serialize};
3
4use crate::StmResult;
5
6use super::{
7    BaseFieldElement, PrimeOrderProjectivePoint, ProjectivePoint, ScalarFieldElement,
8    SchnorrVerificationKey, UniqueSchnorrSignatureError, compute_poseidon_digest,
9};
10
11/// Structure of the Unique Schnorr signature to use with the SNARK
12///
13/// This signature includes a value `commitment_point` which depends only on
14/// the message and the signing key.
15/// This value is used in the lottery process to determine the correct indices.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub struct UniqueSchnorrSignature {
18    /// Deterministic value depending on the message and signing key
19    pub(crate) commitment_point: ProjectivePoint,
20    /// Part of the Unique Schnorr signature depending on the signing key
21    pub(crate) response: ScalarFieldElement,
22    /// Part of the Unique Schnorr signature NOT depending on the signing key
23    pub(crate) challenge: BaseFieldElement,
24}
25
26impl UniqueSchnorrSignature {
27    /// This function performs the verification of a Unique Schnorr signature given the signature, the signed message
28    /// and a verification key derived from the signing key used to sign.
29    ///
30    /// Input:
31    ///     - a Unique Schnorr signature
32    ///     - a message: some BaseFieldElements
33    ///     - a verification key: a value depending on the signing key
34    /// Output:
35    ///     - Ok(()) if the signature verifies and an error if not
36    ///
37    /// The protocol computes:
38    ///     - msg_hash_point = H(msg)
39    ///     - random_point_1_recomputed = response * msg_hash_point + challenge * commitment_point
40    ///     - random_point_2_recomputed = response * prime_order_generator_point + challenge * verification_key
41    ///     - challenge_recomputed = Poseidon(DST || H(msg) || verification_key
42    ///     || commitment_point || random_point_1_recomputed || random_point_2_recomputed)
43    ///
44    /// Check: challenge == challenge_recomputed
45    ///
46    pub fn verify(
47        &self,
48        msg: &[BaseFieldElement],
49        verification_key: &SchnorrVerificationKey,
50    ) -> StmResult<()> {
51        // Check that the verification key is valid
52        verification_key
53            .is_valid()
54            .with_context(|| "Signature verification failed due to invalid verification key")?;
55
56        let prime_order_generator_point = PrimeOrderProjectivePoint::create_generator();
57
58        // First hashing the message to a scalar then hashing it to a curve point
59        let msg_hash_point = ProjectivePoint::hash_to_projective_point(msg)?;
60
61        // Computing random_point_1_recomputed = response *  H(msg) + challenge * commitment_point
62        let challenge_as_scalar = ScalarFieldElement::from_base_field(&self.challenge)?;
63        let random_point_1_recomputed =
64            self.response * msg_hash_point + challenge_as_scalar * self.commitment_point;
65
66        // Computing random_point_2_recomputed = response * prime_order_generator_point + challenge * vk
67        let random_point_2_recomputed =
68            self.response * prime_order_generator_point + challenge_as_scalar * verification_key.0;
69
70        // Since the hash function takes as input scalar elements
71        // We need to convert the EC points to their coordinates
72        let points_coordinates: Vec<BaseFieldElement> = [
73            msg_hash_point,
74            ProjectivePoint::from(verification_key.0),
75            self.commitment_point,
76            random_point_1_recomputed,
77            ProjectivePoint::from(random_point_2_recomputed),
78        ]
79        .iter()
80        .flat_map(|point| {
81            let (u, v) = point.get_coordinates();
82            [u, v]
83        })
84        .collect();
85
86        let challenge_recomputed = compute_poseidon_digest(&points_coordinates);
87
88        if challenge_recomputed != self.challenge {
89            return Err(anyhow!(UniqueSchnorrSignatureError::SignatureInvalid(
90                Box::new(*self)
91            )));
92        }
93
94        Ok(())
95    }
96
97    /// Convert a `UniqueSchnorrSignature` into bytes.
98    pub fn to_bytes(self) -> [u8; 96] {
99        let mut out = [0; 96];
100        out[0..32].copy_from_slice(&self.commitment_point.to_bytes());
101        out[32..64].copy_from_slice(&self.response.to_bytes());
102        out[64..96].copy_from_slice(&self.challenge.to_bytes());
103
104        out
105    }
106
107    /// Convert bytes into a `UniqueSchnorrSignature`.
108    pub fn from_bytes(bytes: &[u8]) -> StmResult<Self> {
109        if bytes.len() < 96 {
110            return Err(anyhow!(UniqueSchnorrSignatureError::Serialization))
111                .with_context(|| "Not enough bytes provided to create a signature.");
112        }
113
114        let commitment_point = ProjectivePoint::from_bytes(
115            bytes
116                .get(0..32)
117                .ok_or(UniqueSchnorrSignatureError::Serialization)
118                .with_context(|| "Could not get the bytes of `commitment_point`")?,
119        )
120        .with_context(|| "Could not convert bytes to `commitment_point`")?;
121
122        let response = ScalarFieldElement::from_bytes(
123            bytes
124                .get(32..64)
125                .ok_or(UniqueSchnorrSignatureError::Serialization)
126                .with_context(|| "Could not get the bytes of `response`")?,
127        )
128        .with_context(|| "Could not convert the bytes to `response`")?;
129
130        let challenge = BaseFieldElement::from_bytes(
131            bytes
132                .get(64..96)
133                .ok_or(UniqueSchnorrSignatureError::Serialization)
134                .with_context(|| "Could not get the bytes of `challenge`")?,
135        )
136        .with_context(|| "Could not convert bytes to `challenge`")?;
137
138        Ok(Self {
139            commitment_point,
140            response,
141            challenge,
142        })
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use rand_chacha::ChaCha20Rng;
149    use rand_core::SeedableRng;
150
151    use crate::signature_scheme::{
152        BaseFieldElement, SchnorrSigningKey, SchnorrVerificationKey, UniqueSchnorrSignature,
153    };
154
155    #[test]
156    fn valid_signature_verification() {
157        let msg = vec![0, 0, 0, 1];
158        let base_input = BaseFieldElement::try_from(msg.as_slice()).unwrap();
159        let seed = [0u8; 32];
160        let mut rng = ChaCha20Rng::from_seed(seed);
161        let sk = SchnorrSigningKey::generate(&mut rng);
162        let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone());
163
164        let sig = sk.sign(&[base_input], &mut rng).unwrap();
165
166        sig.verify(&[base_input], &vk)
167            .expect("Valid signature should verify successfully");
168    }
169
170    #[test]
171    fn invalid_signature() {
172        let msg = vec![0, 0, 0, 1];
173        let base_input = BaseFieldElement::try_from(msg.as_slice()).unwrap();
174        let msg2 = vec![0, 0, 0, 2];
175        let base_input2 = BaseFieldElement::try_from(msg2.as_slice()).unwrap();
176        let seed = [0u8; 32];
177        let mut rng = ChaCha20Rng::from_seed(seed);
178        let sk = SchnorrSigningKey::generate(&mut rng);
179        let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone());
180        let sk2 = SchnorrSigningKey::generate(&mut rng);
181        let vk2 = SchnorrVerificationKey::new_from_signing_key(sk2);
182
183        let sig = sk.sign(&[base_input], &mut rng).unwrap();
184        let sig2 = sk.sign(&[base_input2], &mut rng).unwrap();
185
186        // Wrong verification key is used
187        let result1 = sig.verify(&[base_input], &vk2);
188        let result2 = sig2.verify(&[base_input], &vk);
189
190        result1.expect_err("Wrong verification key used, test should fail.");
191        // Wrong message is verified
192        result2.expect_err("Wrong message used, test should fail.");
193    }
194
195    #[test]
196    fn from_bytes_signature_not_enough_bytes() {
197        let msg = vec![0u8; 95];
198        let result = UniqueSchnorrSignature::from_bytes(&msg);
199        result.expect_err("Not enough bytes.");
200    }
201
202    #[test]
203    fn from_bytes_signature_exact_size() {
204        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
205        let msg = vec![1, 2, 3];
206        let base_input = BaseFieldElement::try_from(msg.as_slice()).unwrap();
207        let sk = SchnorrSigningKey::generate(&mut rng);
208
209        let sig = sk.sign(&[base_input], &mut rng).unwrap();
210        let sig_bytes: [u8; 96] = sig.to_bytes();
211
212        let sig_restored = UniqueSchnorrSignature::from_bytes(&sig_bytes).unwrap();
213        assert_eq!(sig, sig_restored);
214    }
215
216    #[test]
217    fn from_bytes_signature_extra_bytes() {
218        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
219        let msg = vec![1, 2, 3];
220        let base_input = BaseFieldElement::try_from(msg.as_slice()).unwrap();
221        let sk = SchnorrSigningKey::generate(&mut rng);
222
223        let sig = sk.sign(&[base_input], &mut rng).unwrap();
224        let sig_bytes: [u8; 96] = sig.to_bytes();
225
226        let mut extended_bytes = sig_bytes.to_vec();
227        extended_bytes.extend_from_slice(&[0xFF; 10]);
228
229        let sig_restored = UniqueSchnorrSignature::from_bytes(&extended_bytes).unwrap();
230        assert_eq!(sig, sig_restored);
231    }
232
233    #[test]
234    fn to_bytes_is_deterministic() {
235        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
236        let msg = vec![1, 2, 3];
237        let base_input = BaseFieldElement::try_from(msg.as_slice()).unwrap();
238        let sk = SchnorrSigningKey::generate(&mut rng);
239
240        let sig = sk.sign(&[base_input], &mut rng).unwrap();
241
242        // Converting to bytes multiple times should give same result
243        let bytes1 = sig.to_bytes();
244        let bytes2 = sig.to_bytes();
245
246        assert_eq!(bytes1, bytes2);
247    }
248
249    #[test]
250    fn signature_roundtrip_preserves_verification() {
251        let mut rng = ChaCha20Rng::from_seed([42u8; 32]);
252        let msg = vec![5, 6, 7, 8, 9];
253        let base_input = BaseFieldElement::try_from(msg.as_slice()).unwrap();
254        let sk = SchnorrSigningKey::generate(&mut rng);
255        let vk = SchnorrVerificationKey::new_from_signing_key(sk.clone());
256
257        // Create and verify original signature
258        let sig = sk.sign(&[base_input], &mut rng).unwrap();
259        sig.verify(&[base_input], &vk)
260            .expect("Original signature should verify");
261
262        // Roundtrip through bytes
263        let sig_bytes = sig.to_bytes();
264        let sig_restored = UniqueSchnorrSignature::from_bytes(&sig_bytes).unwrap();
265
266        // Restored signature should still verify
267        sig_restored
268            .verify(&[base_input], &vk)
269            .expect("Restored signature should verify");
270    }
271
272    mod golden {
273        use super::*;
274
275        const GOLDEN_BYTES: &[u8; 96] = &[
276            39, 90, 41, 56, 174, 106, 33, 173, 254, 49, 113, 116, 208, 4, 121, 177, 236, 223, 173,
277            108, 193, 135, 214, 159, 99, 93, 108, 202, 201, 200, 141, 148, 230, 175, 77, 63, 232,
278            229, 34, 36, 7, 205, 254, 86, 70, 160, 49, 87, 114, 98, 20, 88, 141, 224, 113, 109,
279            208, 226, 177, 140, 55, 79, 174, 10, 121, 59, 197, 98, 35, 17, 213, 33, 65, 143, 110,
280            106, 145, 86, 141, 107, 204, 91, 39, 205, 113, 69, 87, 194, 239, 242, 188, 14, 194,
281            239, 173, 14,
282        ];
283
284        fn golden_value() -> UniqueSchnorrSignature {
285            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
286            let sk = SchnorrSigningKey::generate(&mut rng);
287            let msg = [0u8; 32];
288            let base_input = BaseFieldElement::try_from(msg.as_slice()).unwrap();
289            sk.sign(&[base_input], &mut rng).unwrap()
290        }
291
292        #[test]
293        fn golden_conversions() {
294            let value = UniqueSchnorrSignature::from_bytes(GOLDEN_BYTES)
295                .expect("This from bytes should not fail");
296            assert_eq!(golden_value(), value);
297
298            let serialized = UniqueSchnorrSignature::to_bytes(value);
299            let golden_serialized = UniqueSchnorrSignature::to_bytes(golden_value());
300            assert_eq!(golden_serialized, serialized);
301        }
302    }
303
304    mod golden_json {
305        use super::*;
306
307        const GOLDEN_JSON: &str = r#"
308        {
309            "commitment_point": [39, 90, 41, 56, 174, 106, 33, 173, 254, 49, 113, 116, 208, 4, 121, 177, 236, 223, 173, 108, 193, 135, 214, 159, 99, 93, 108, 202, 201, 200, 141, 148], 
310            "response": [230, 175, 77, 63, 232, 229, 34, 36, 7, 205, 254, 86, 70, 160, 49, 87, 114, 98, 20, 88, 141, 224, 113, 109, 208, 226, 177, 140, 55, 79, 174, 10], 
311            "challenge": [121, 59, 197, 98, 35, 17, 213, 33, 65, 143, 110, 106, 145, 86, 141, 107, 204, 91, 39, 205, 113, 69, 87, 194, 239, 242, 188, 14, 194, 239, 173, 14]
312        }"#;
313
314        fn golden_value() -> UniqueSchnorrSignature {
315            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
316            let sk = SchnorrSigningKey::generate(&mut rng);
317            let msg = [0u8; 32];
318            let base_input = BaseFieldElement::try_from(msg.as_slice()).unwrap();
319            sk.sign(&[base_input], &mut rng).unwrap()
320        }
321
322        #[test]
323        fn golden_conversions() {
324            let value = serde_json::from_str(GOLDEN_JSON)
325                .expect("This JSON deserialization should not fail");
326            assert_eq!(golden_value(), value);
327
328            let serialized =
329                serde_json::to_string(&value).expect("This JSON serialization should not fail");
330            let golden_serialized = serde_json::to_string(&golden_value())
331                .expect("This JSON serialization should not fail");
332            assert_eq!(golden_serialized, serialized);
333        }
334    }
335}