1use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve};
8use bls12_381::{
9 multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt,
10};
11use serde::{Deserialize, Deserializer};
12use sha2::{Digest, Sha256};
13use std::ops::Neg;
14use thiserror::Error;
15
16#[derive(Deserialize, Debug, PartialEq, Clone)]
17pub struct Beacon {
18 #[serde(alias = "round")]
19 pub round_number: u64,
20 #[serde(with = "hex")]
21 pub randomness: Vec<u8>,
22 #[serde(with = "hex")]
23 pub signature: Vec<u8>,
24 #[serde(default, with = "hex")]
25 pub previous_signature: Vec<u8>,
26}
27
28const DST_G1: &str = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_";
29const DST_G2: &str = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_";
30
31#[derive(Debug, PartialEq, Clone)]
32pub enum SchemeID {
33 PedersenBlsChained,
34 PedersenBlsUnchained,
35 UnchainedOnG1,
36 UnchainedOnG1RFC9380,
37}
38
39impl<'de> Deserialize<'de> for SchemeID {
40 fn deserialize<D>(deserializer: D) -> Result<SchemeID, D::Error>
41 where
42 D: Deserializer<'de>,
43 {
44 let s = Deserialize::deserialize(deserializer)?;
45 match s {
46 "pedersen-bls-chained" => Ok(SchemeID::PedersenBlsChained),
47 "pedersen-bls-unchained" => Ok(SchemeID::PedersenBlsUnchained),
48 "bls-unchained-on-g1" => Ok(SchemeID::UnchainedOnG1),
49 "bls-unchained-g1-rfc9380" => Ok(SchemeID::UnchainedOnG1RFC9380),
50 _ => Err(serde::de::Error::unknown_variant(
51 s,
52 &[
53 "pedersen-bls-chained",
54 "pedersen-bls-unchained",
55 "bls-unchained-on-g1",
56 "bls-unchained-g1-rfc9380",
57 ],
58 )),
59 }
60 }
61}
62
63#[derive(Error, Debug, PartialEq)]
64pub enum VerificationError {
65 #[error("chained beacons must have a `previous_signature`")]
66 ChainedBeaconNeedsPreviousSignature,
67 #[error("invalid signature length")]
68 InvalidSignatureLength,
69 #[error("invalid public key")]
70 InvalidPublicKey,
71 #[error("message can't be empty")]
72 EmptyMessage,
73 #[error("signature verification failed")]
74 SignatureFailedVerification,
75 #[error("the randomness for the beacon did not match the signature")]
76 InvalidRandomness,
77}
78
79pub fn verify_beacon(
81 scheme_id: &SchemeID,
82 public_key: &[u8],
83 beacon: &Beacon,
84) -> Result<(), VerificationError> {
85 if Sha256::digest(&beacon.signature).to_vec() != beacon.randomness {
86 return Err(VerificationError::InvalidRandomness);
87 }
88 match scheme_id {
89 SchemeID::PedersenBlsChained => verify_on_g2(
90 public_key,
91 &chained_beacon_message(beacon)?,
92 &beacon.signature,
93 DST_G2,
94 ),
95 SchemeID::PedersenBlsUnchained => verify_on_g2(
96 public_key,
97 &unchained_beacon_message(beacon)?,
98 &beacon.signature,
99 DST_G2,
100 ),
101 SchemeID::UnchainedOnG1 => verify_on_g1(
102 public_key,
103 &unchained_beacon_message(beacon)?,
104 &beacon.signature,
105 DST_G2,
106 ),
107 SchemeID::UnchainedOnG1RFC9380 => verify_on_g1(
108 public_key,
109 &unchained_beacon_message(beacon)?,
110 &beacon.signature,
111 DST_G1,
112 ),
113 }
114}
115
116fn unchained_beacon_message(beacon: &Beacon) -> Result<Vec<u8>, VerificationError> {
117 let round_bytes = beacon.round_number.to_be_bytes();
118
119 Ok(Sha256::digest(&round_bytes).to_vec())
120}
121
122fn chained_beacon_message(beacon: &Beacon) -> Result<Vec<u8>, VerificationError> {
123 if beacon.previous_signature.is_empty() {
124 Err(VerificationError::ChainedBeaconNeedsPreviousSignature)
125 } else {
126 let message: Vec<u8> = beacon
127 .previous_signature
128 .clone()
129 .into_iter()
130 .chain(beacon.round_number.to_be_bytes())
131 .collect();
132
133 Ok(Sha256::digest(message.as_slice()).to_vec())
134 }
135}
136
137pub fn verify_on_g2(
140 public_key: &[u8],
141 message: &[u8],
142 signature: &[u8],
143 domain_separation_tag: &str,
144) -> Result<(), VerificationError> {
145 let pub_key_bytes: &[u8; 48] = public_key
146 .try_into()
147 .map_err(|_| VerificationError::InvalidPublicKey)?;
148
149 let sig_bytes: &[u8; 96] = signature
150 .try_into()
151 .map_err(|_| VerificationError::InvalidSignatureLength)?;
152
153 let p = G1Affine::from_compressed(pub_key_bytes).unwrap_or(G1Affine::identity());
154
155 let q = G2Affine::from_compressed(sig_bytes).unwrap_or(G2Affine::identity());
156
157 if p.is_on_curve().unwrap_u8() != 1 {
158 return Err(VerificationError::InvalidPublicKey);
159 }
160
161 if p.is_identity().unwrap_u8() == 1 {
162 return Err(VerificationError::InvalidPublicKey);
163 }
164
165 if message.is_empty() {
166 return Err(VerificationError::EmptyMessage);
167 }
168
169 if signature.is_empty() {
170 return Err(VerificationError::InvalidSignatureLength);
171 }
172
173 let m = <G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve(
174 message,
175 domain_separation_tag.as_bytes(),
176 );
177
178 let m_prepared = G2Prepared::from(G2Affine::from(m));
179 let q_prepared = G2Prepared::from(q);
180 let exp = multi_miller_loop(&[
181 (&p.neg(), &m_prepared),
182 (&G1Affine::generator(), &q_prepared),
183 ]);
184
185 if exp.final_exponentiation() != Gt::identity() {
186 Err(VerificationError::SignatureFailedVerification)
187 } else {
188 Ok(())
189 }
190}
191
192pub fn verify_on_g1(
195 public_key: &[u8],
196 message: &[u8],
197 signature: &[u8],
198 domain_separation_tag: &str,
199) -> Result<(), VerificationError> {
200 let pub_key_bytes: &[u8; 96] = public_key
201 .try_into()
202 .map_err(|_| VerificationError::InvalidPublicKey)?;
203
204 let sig_bytes: &[u8; 48] = signature
205 .try_into()
206 .map_err(|_| VerificationError::InvalidSignatureLength)?;
207
208 let signature_point = G1Affine::from_compressed(sig_bytes).unwrap_or(G1Affine::identity());
209 let pubkey_point = G2Affine::from_compressed(pub_key_bytes).unwrap_or(G2Affine::identity());
210
211 if pubkey_point.is_on_curve().unwrap_u8() != 1 {
212 return Err(VerificationError::InvalidPublicKey);
213 }
214
215 if pubkey_point.is_identity().unwrap_u8() == 1 {
216 return Err(VerificationError::InvalidPublicKey);
217 }
218
219 if message.is_empty() {
220 return Err(VerificationError::EmptyMessage);
221 }
222
223 if signature.is_empty() {
224 return Err(VerificationError::InvalidSignatureLength);
225 }
226
227 let m = <G1Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve(
228 message,
229 domain_separation_tag.as_bytes(),
230 );
231
232 let pubkey_prepared = G2Prepared::from(pubkey_point.neg());
233 let g2_base = G2Prepared::from(G2Affine::generator());
234 let exp = multi_miller_loop(&[
235 (&G1Affine::from(m), &pubkey_prepared),
236 (&signature_point, &g2_base),
237 ]);
238
239 if exp.final_exponentiation() != Gt::identity() {
240 Err(VerificationError::SignatureFailedVerification)
241 } else {
242 Ok(())
243 }
244}
245
246#[cfg(test)]
247mod test {
248 use crate::verify::{verify_beacon, Beacon, SchemeID, VerificationError};
249 use bls12_381::{G1Affine, G2Affine};
250
251 #[test]
252 fn default_beacon_verifies() {
253 let public_key = dehexify("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb");
254 let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce");
255
256 let beacon = Beacon {
257 round_number: 397089,
258 randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"),
259 signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"),
260 previous_signature: prev_sig,
261 };
262
263 assert!(matches!(
264 verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon),
265 Ok(()),
266 ));
267 }
268
269 #[test]
270 fn default_wrong_round_fails() {
271 let public_key = dehexify("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb");
272 let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce");
273
274 let beacon = Beacon {
275 round_number: 1, randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"),
277 signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"),
278 previous_signature: prev_sig,
279 };
280
281 assert_error(
282 verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon),
283 VerificationError::SignatureFailedVerification,
284 );
285 }
286
287 #[test]
288 fn default_with_invalid_randomness_fails() {
289 let public_key = dehexify("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb");
290 let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce");
291
292 let beacon = Beacon {
293 round_number: 397089,
294 randomness: dehexify("bd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"),
296 signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"),
297 previous_signature: prev_sig,
298 };
299
300 assert_error(
301 verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon),
302 VerificationError::InvalidRandomness,
303 );
304 }
305
306 #[test]
307 fn default_beacon_missing_previous_sig_fails() {
308 let public_key = dehexify("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb");
309
310 let beacon = Beacon {
311 round_number: 397089,
312 randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"),
313 signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"),
314 previous_signature: Vec::new(),
315 };
316
317 assert_error(
318 verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon),
319 VerificationError::ChainedBeaconNeedsPreviousSignature,
320 );
321 }
322
323 #[test]
324 fn default_beacon_invalid_public_key_fails() {
325 let public_key = dehexify("78a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb");
327 let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce");
328
329 let beacon = Beacon {
330 round_number: 397089,
331 randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"),
332 signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"),
333 previous_signature: prev_sig,
334 };
335
336 assert_error(
337 verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon),
338 VerificationError::InvalidPublicKey,
339 );
340 }
341
342 #[test]
343 fn default_beacon_empty_public_key_fails() {
344 let public_key = Vec::new();
345 let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce");
346
347 let beacon = Beacon {
348 round_number: 397089,
349 randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"),
350 signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"),
351 previous_signature: prev_sig,
352 };
353
354 assert_error(
355 verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon),
356 VerificationError::InvalidPublicKey,
357 );
358 }
359
360 #[test]
361 fn default_beacon_infinity_public_key_fails() {
362 let public_key = G1Affine::identity().to_compressed();
363 let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce");
364
365 let beacon = Beacon {
366 round_number: 397089,
367 randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"),
368 signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"),
369 previous_signature: prev_sig,
370 };
371
372 assert_error(
373 verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon),
374 VerificationError::InvalidPublicKey,
375 );
376 }
377
378 #[test]
379 fn testnet_unchained_beacon_verifies() {
380 let public_key = dehexify("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65");
381 let beacon = Beacon {
382 round_number: 397092,
383 randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"),
384 signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"),
385 previous_signature: Vec::new(),
386 };
387
388 assert!(matches!(
389 verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon),
390 Ok(_),
391 ));
392 }
393
394 #[test]
395 fn testnet_unchained_beacon_wrong_round_fails() {
396 let public_key = dehexify("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65");
397 let beacon = Beacon {
398 round_number: 1, randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"),
400 signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"),
401 previous_signature: Vec::new(),
402 };
403
404 assert_error(
405 verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon),
406 VerificationError::SignatureFailedVerification,
407 );
408 }
409
410 #[test]
411 fn testnet_unchained_beacon_randomness_not_matching_signature_fails() {
412 let public_key = dehexify("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65");
413 let beacon = Beacon {
414 round_number: 397092,
415 randomness: dehexify("a731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"),
417 signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"),
418 previous_signature: Vec::new(),
419 };
420
421 assert_error(
422 verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon),
423 VerificationError::InvalidRandomness,
424 );
425 }
426
427 #[test]
428 fn testnet_unchained_beacon_containing_previous_sig_ignores_previous_sig() {
429 let public_key = dehexify("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65");
430 let beacon = Beacon {
431 round_number: 397092,
432 randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"),
433 signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"),
434 previous_signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"),
435 };
436
437 assert!(matches!(
438 verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon),
439 Ok(())
440 ));
441 }
442
443 #[test]
444 fn testnet_unchained_invalid_public_key_fails() {
445 let public_key = dehexify("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31");
447 let beacon = Beacon {
448 round_number: 397092,
449 randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"),
450 signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"),
451 previous_signature: Vec::new(),
452 };
453
454 assert_error(
455 verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon),
456 VerificationError::SignatureFailedVerification,
457 );
458 }
459
460 #[test]
461 fn testnet_unchained_beacon_empty_public_key_fails() {
462 let public_key = Vec::new();
463 let beacon = Beacon {
464 round_number: 397092,
465 randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"),
466 signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"),
467 previous_signature: Vec::new(),
468 };
469
470 assert_error(
471 verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon),
472 VerificationError::InvalidPublicKey,
473 );
474 }
475
476 #[test]
477 fn testnet_unchained_beacon_infinity_public_key_fails() {
478 let public_key = G2Affine::identity().to_uncompressed();
479 let beacon = Beacon {
480 round_number: 397092,
481 randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"),
482 signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"),
483 previous_signature: Vec::new(),
484 };
485
486 assert_error(
487 verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon),
488 VerificationError::InvalidPublicKey,
489 );
490 }
491
492 #[test]
493 fn g1g2_swap_non_rfc_beacon_verifies() {
494 let public_key = dehexify("a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e");
495 let beacon = Beacon {
496 round_number: 3,
497 randomness: dehexify("a4eb0ed6c4132da066843c3bfdce732ce5013eda86e74c136ab8ccc387b798dd"),
498 signature: dehexify("8176555f90d71aa49ceb37739683749491c2bab15a46094b255289ed25cf8f01cdfb1fe8bd9cd5a19eb09448a3e53186"),
499 previous_signature: Vec::new(),
500 };
501
502 assert!(matches!(
503 verify_beacon(&SchemeID::UnchainedOnG1, &public_key, &beacon),
504 Ok(_)
505 ));
506 }
507
508 #[test]
509 fn g1g2_swap_rfc_beacon_verifies() {
510 let public_key = dehexify("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a");
511 let beacon = Beacon {
512 round_number: 1000,
513 randomness: dehexify("fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"),
514 signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"),
515 previous_signature: Vec::new(),
516 };
517
518 assert!(matches!(
519 verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon),
520 Ok(_)
521 ));
522 }
523
524 #[test]
525 fn g1g2_swap_empty_public_key_fails() {
526 let public_key = Vec::new();
527 let beacon = Beacon {
528 round_number: 1000,
529 randomness: dehexify("fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"),
530 signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"),
531 previous_signature: Vec::new(),
532 };
533
534 assert_error(
535 verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon),
536 VerificationError::InvalidPublicKey,
537 );
538 }
539
540 #[test]
541 fn g1g2_swap_infinity_public_key_fails() {
542 let public_key = G2Affine::identity().to_uncompressed();
543 let beacon = Beacon {
544 round_number: 1000,
545 randomness: dehexify("fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"),
546 signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"),
547 previous_signature: Vec::new(),
548 };
549
550 assert_error(
551 verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon),
552 VerificationError::InvalidPublicKey,
553 );
554 }
555
556 #[test]
557 fn g1g2_swap_wrong_round_fails() {
558 let public_key = dehexify("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a");
559 let beacon = Beacon {
560 round_number: 1,
561 randomness: dehexify("fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"),
562 signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"),
563 previous_signature: Vec::new(),
564 };
565
566 assert_error(
567 verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon),
568 VerificationError::SignatureFailedVerification,
569 );
570 }
571
572 #[test]
573 fn g1g2_swap_invalid_randomness_fails() {
574 let public_key = dehexify("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a");
575 let beacon = Beacon {
576 round_number: 1000,
577 randomness: dehexify("aa290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"),
579 signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"),
580 previous_signature: Vec::new(),
581 };
582
583 assert_error(
584 verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon),
585 VerificationError::InvalidRandomness,
586 );
587 }
588
589 #[test]
590 fn g1g2_swap_invalid_signature_fails() {
591 let public_key = dehexify("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a");
592 let beacon = Beacon {
593 round_number: 1000,
594 signature: dehexify("a44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"),
596 randomness: dehexify("5993706587c56d4e7079d175bfa5d52295694896e68c691b93765242096c9fa7"),
598 previous_signature: Vec::new(),
599 };
600
601 assert_error(
602 verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon),
603 VerificationError::SignatureFailedVerification,
604 );
605 }
606
607 fn dehexify(s: &str) -> Vec<u8> {
608 hex::decode(s).unwrap().to_vec()
609 }
610
611 fn assert_error(actual: Result<(), VerificationError>, expected: VerificationError) {
612 match actual {
613 Ok(_) => panic!("expected error but got success"),
614 Err(e) => {
615 if e != expected {
616 panic!("expected {expected:?} but got {e:?}");
617 }
618 }
619 }
620 }
621}