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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17pub struct UniqueSchnorrSignature {
18 pub(crate) commitment_point: ProjectivePoint,
20 pub(crate) response: ScalarFieldElement,
22 pub(crate) challenge: BaseFieldElement,
24}
25
26impl UniqueSchnorrSignature {
27 pub fn verify(
47 &self,
48 msg: &[BaseFieldElement],
49 verification_key: &SchnorrVerificationKey,
50 ) -> StmResult<()> {
51 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 let msg_hash_point = ProjectivePoint::hash_to_projective_point(msg)?;
60
61 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 let random_point_2_recomputed =
68 self.response * prime_order_generator_point + challenge_as_scalar * verification_key.0;
69
70 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 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 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 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 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 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 let sig = sk.sign(&[base_input], &mut rng).unwrap();
259 sig.verify(&[base_input], &vk)
260 .expect("Original signature should verify");
261
262 let sig_bytes = sig.to_bytes();
264 let sig_restored = UniqueSchnorrSignature::from_bytes(&sig_bytes).unwrap();
265
266 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}