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