drand_client_rs/
verify.rs

1//! # verify
2//!
3//! this module contains some of the cruptographic internals that some users might wish to use
4//! manually without the client
5//!
6
7use 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
79/// verify a randomness beacon for a given scheme and public key
80pub 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
137/// verify a signature where the public key is on g1 and the signature is on g2 for a
138/// given domain separation tag
139pub 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
192/// verify a signature where the public key is on g2 and the signature is on g1 for a
193/// given domain separation tag
194pub 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, // wrong round for randomness
276            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            // updated the randomness hex to be wrong
295            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        // public key is not correct
326        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, // wrong round
399            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            // mismatching randomness
416            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        // valid public key, but for wrong scheme
446        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            // incorrect hash for the signature
578            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            // this is not a valid signature
595            signature: dehexify("a44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"),
596            // but the hash matches it
597            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}