mithril_stm/signature_scheme/schnorr_signature/
signature.rs

1use anyhow::{Context, anyhow};
2use dusk_jubjub::{
3    ExtendedPoint as JubjubExtended, Fq as JubjubBase, Fr as JubjubScalar,
4    SubgroupPoint as JubjubSubgroup,
5};
6use dusk_poseidon::{Domain, Hash};
7use group::{Group, GroupEncoding};
8
9use super::{
10    DST_SIGNATURE, SchnorrSignatureError, SchnorrVerificationKey, get_coordinates_several_points,
11    is_on_curve,
12};
13use crate::StmResult;
14
15/// Structure of the Schnorr signature to use with the SNARK
16///
17/// This signature includes a value `sigma` which depends only on
18/// the message and the signing key.
19/// This value is used in the lottery process to determine the correct indices.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct SchnorrSignature {
22    /// Deterministic value depending on the message and secret key
23    pub(crate) sigma: JubjubExtended,
24    /// Part of the Schnorr signature depending on the secret key
25    pub(crate) signature: JubjubScalar,
26    /// Part of the Schnorr signature NOT depending on the secret key
27    pub(crate) challenge: JubjubScalar,
28}
29
30impl SchnorrSignature {
31    /// This function performs the verification of a Schnorr signature given the signature, the signed message
32    /// and a verification key derived from the secret key used to sign.
33    ///
34    /// Input:
35    ///     - a Schnorr signature
36    ///     - a message: some bytes
37    ///     - a verification key: a value depending on the secret key
38    /// Output:
39    ///     - Ok(()) if the signature verifies and an error if not
40    ///
41    /// The protocol computes:
42    ///     - msg_hash = H(Sha256(msg))
43    ///     - random_point_1_recomputed = msg_hash * signature + sigma * challenge
44    ///     - random_point_2_recomputed = generator * signature + verification_key * challenge
45    ///     - challenge_recomputed = Poseidon(DST || H(Sha256(msg)) || verification_key
46    ///     || sigma || random_point_1_recomputed || random_point_2_recomputed)
47    ///
48    /// Check: challenge == challenge_recomputed
49    ///
50    pub fn verify(&self, msg: &[u8], verification_key: &SchnorrVerificationKey) -> StmResult<()> {
51        // Check that the verification key is on the curve
52        if !is_on_curve(verification_key.0.into()) {
53            return Err(anyhow!(SchnorrSignatureError::VerificationKeyInvalid(
54                Box::new(*verification_key)
55            )));
56        }
57
58        let generator = JubjubSubgroup::generator();
59
60        // First hashing the message to a scalar then hashing it to a curve point
61        let msg_hash = JubjubExtended::hash_to_point(msg);
62
63        // Computing R1 = H(msg) * s + sigma * c
64        let msg_hash_times_signature = msg_hash * self.signature;
65        let sigma_times_challenge = self.sigma * self.challenge;
66        let random_point_1_recomputed = msg_hash_times_signature + sigma_times_challenge;
67
68        // Computing R2 = g * s + vk * c
69        let generator_times_signature = generator * self.signature;
70        let vk_times_challenge = verification_key.0 * self.challenge;
71        let random_point_2_recomputed = generator_times_signature + vk_times_challenge;
72
73        // Since the hash function takes as input scalar elements
74        // We need to convert the EC points to their coordinates
75        let points_coordinates = get_coordinates_several_points(&[
76            msg_hash,
77            verification_key.0.into(),
78            self.sigma,
79            random_point_1_recomputed,
80            random_point_2_recomputed.into(),
81        ]);
82
83        let mut poseidon_input = vec![DST_SIGNATURE];
84        poseidon_input.extend(
85            points_coordinates
86                .into_iter()
87                .flat_map(|(x, y)| [x, y])
88                .collect::<Vec<JubjubBase>>(),
89        );
90
91        let challenge_recomputed = Hash::digest_truncated(Domain::Other, &poseidon_input)[0];
92
93        if challenge_recomputed != self.challenge {
94            return Err(anyhow!(SchnorrSignatureError::SignatureInvalid(Box::new(
95                *self
96            ))));
97        }
98
99        Ok(())
100    }
101
102    /// Convert an `SchnorrSignature` into bytes.
103    pub fn to_bytes(self) -> [u8; 96] {
104        let mut out = [0; 96];
105        out[0..32].copy_from_slice(&self.sigma.to_bytes());
106        out[32..64].copy_from_slice(&self.signature.to_bytes());
107        out[64..96].copy_from_slice(&self.challenge.to_bytes());
108
109        out
110    }
111
112    /// Convert bytes into a `SchnorrSignature`.
113    pub fn from_bytes(bytes: &[u8]) -> StmResult<Self> {
114        if bytes.len() < 96 {
115            return Err(anyhow!(SchnorrSignatureError::SerializationError))
116                .with_context(|| "Not enough bytes provided to create a signature.");
117        }
118
119        let sigma_bytes = bytes[0..32]
120            .try_into()
121            .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError))
122            .with_context(|| "Failed to obtain sigma's bytes.")?;
123        let sigma = JubjubExtended::from_bytes(&sigma_bytes)
124            .into_option()
125            .ok_or(anyhow!(SchnorrSignatureError::SerializationError))
126            .with_context(|| "Unable to convert bytes into a sigma value.")?;
127
128        let signature_bytes = bytes[32..64]
129            .try_into()
130            .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError))
131            .with_context(|| "Failed to obtain signature's bytes.")?;
132        let signature = JubjubScalar::from_bytes(&signature_bytes)
133            .into_option()
134            .ok_or(anyhow!(SchnorrSignatureError::SerializationError))
135            .with_context(|| "Unable to convert bytes into a signature value.")?;
136
137        let challenge_bytes = bytes[64..96]
138            .try_into()
139            .map_err(|_| anyhow!(SchnorrSignatureError::SerializationError))
140            .with_context(|| "Failed to obtain challenge's bytes.")?;
141        let challenge = JubjubScalar::from_bytes(&challenge_bytes)
142            .into_option()
143            .ok_or(anyhow!(SchnorrSignatureError::SerializationError))
144            .with_context(|| "Unable to convert bytes into a challenge value.")?;
145
146        Ok(Self {
147            sigma,
148            signature,
149            challenge,
150        })
151    }
152}
153
154#[cfg(test)]
155mod tests {
156
157    use crate::{SchnorrSignature, SchnorrSigningKey, SchnorrVerificationKey};
158    use rand_chacha::ChaCha20Rng;
159    use rand_core::SeedableRng;
160
161    #[test]
162    fn invalid_sig() {
163        let msg = vec![0, 0, 0, 1];
164        let msg2 = vec![0, 0, 0, 2];
165        let seed = [0u8; 32];
166        let mut rng = ChaCha20Rng::from_seed(seed);
167        let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap();
168        let vk = SchnorrVerificationKey::from(&sk);
169        let sk2 = SchnorrSigningKey::try_generate(&mut rng).unwrap();
170        let vk2 = SchnorrVerificationKey::from(&sk2);
171
172        let sig = sk.sign(&msg, &mut rng).unwrap();
173        let sig2 = sk.sign(&msg2, &mut rng).unwrap();
174
175        // Wrong verification key is used
176        let result1 = sig.verify(&msg, &vk2);
177        let result2 = sig2.verify(&msg, &vk);
178
179        result1.expect_err("Wrong verification key used, test should fail.");
180        // Wrong message is verified
181        result2.expect_err("Wrong message used, test should fail.");
182    }
183
184    #[test]
185    fn serialize_deserialize_signature() {
186        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
187
188        let msg = vec![0, 0, 0, 1];
189        let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap();
190
191        let sig = sk.sign(&msg, &mut rng).unwrap();
192        let sig_bytes: [u8; 96] = sig.to_bytes();
193        let sig2 = SchnorrSignature::from_bytes(&sig_bytes).unwrap();
194
195        assert_eq!(sig, sig2);
196    }
197
198    #[test]
199    fn from_bytes_signature_not_enough_bytes() {
200        let msg = vec![0u8; 95];
201
202        let result = SchnorrSignature::from_bytes(&msg);
203
204        result.expect_err("Not enough bytes.");
205    }
206
207    mod golden {
208
209        use rand_chacha::ChaCha20Rng;
210        use rand_core::SeedableRng;
211
212        use crate::{SchnorrSignature, SchnorrSigningKey};
213
214        const GOLDEN_BYTES: &[u8; 96] = &[
215            143, 53, 198, 62, 178, 1, 88, 253, 21, 92, 100, 13, 72, 180, 198, 127, 39, 175, 102,
216            69, 147, 249, 244, 224, 122, 121, 248, 68, 217, 242, 158, 113, 94, 57, 200, 241, 208,
217            145, 251, 8, 92, 119, 163, 38, 81, 85, 54, 36, 193, 221, 254, 242, 21, 129, 110, 161,
218            142, 184, 107, 156, 100, 34, 190, 9, 200, 20, 178, 142, 61, 253, 193, 11, 5, 180, 97,
219            73, 125, 88, 162, 36, 30, 177, 225, 52, 136, 21, 138, 93, 81, 23, 19, 64, 82, 78, 229,
220            3,
221        ];
222
223        fn golden_value() -> SchnorrSignature {
224            let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
225            let sk = SchnorrSigningKey::try_generate(&mut rng).unwrap();
226            let msg = [0u8; 32];
227            sk.sign(&msg, &mut rng).unwrap()
228        }
229
230        #[test]
231        fn golden_conversions() {
232            let value = SchnorrSignature::from_bytes(GOLDEN_BYTES)
233                .expect("This from bytes should not fail");
234            assert_eq!(golden_value(), value);
235
236            let serialized = SchnorrSignature::to_bytes(value);
237            let golden_serialized = SchnorrSignature::to_bytes(golden_value());
238            assert_eq!(golden_serialized, serialized);
239        }
240    }
241}