mithril_stm/signature_scheme/unique_schnorr_signature/
signature.rs

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