mithril_stm/signature_scheme/schnorr_signature/
signature.rs1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct SchnorrSignature {
22 pub(crate) sigma: JubjubExtended,
24 pub(crate) signature: JubjubScalar,
26 pub(crate) challenge: JubjubScalar,
28}
29
30impl SchnorrSignature {
31 pub fn verify(&self, msg: &[u8], verification_key: &SchnorrVerificationKey) -> StmResult<()> {
51 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 let msg_hash = JubjubExtended::hash_to_point(msg);
62
63 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 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 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 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 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 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 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}