diff --git a/src/chain_info.rs b/src/chain_info.rs index cde7c11..fd9d83b 100644 --- a/src/chain_info.rs +++ b/src/chain_info.rs @@ -1,5 +1,5 @@ +use crate::verify::SchemeID; use serde::Deserialize; -use crate::scheme::SchemeID; #[derive(Deserialize, Debug, PartialEq, Clone)] pub struct ChainInfo { diff --git a/src/http.rs b/src/http.rs index 64f4f73..1a66195 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,6 +1,6 @@ +use crate::{Transport, TransportError}; use reqwest::blocking::Client; use reqwest::StatusCode; -use crate::{Transport, TransportError}; pub struct HttpTransport { pub client: Client, @@ -26,6 +26,6 @@ impl Transport for HttpTransport { pub fn new_http_transport() -> HttpTransport { HttpTransport { - client: Client::new() + client: Client::new(), } } diff --git a/src/lib.rs b/src/lib.rs index d63fd4a..a73cd0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,13 +3,12 @@ extern crate core; mod chain_info; mod http; mod verify; -mod scheme; use crate::chain_info::ChainInfo; +use crate::http::{new_http_transport, HttpTransport}; +use crate::verify::{verify_beacon, Beacon}; use crate::DrandClientError::{InvalidChainInfo, InvalidRound}; use thiserror::Error; -use crate::http::{HttpTransport, new_http_transport}; -use crate::scheme::{Beacon, verify_beacon}; pub struct DrandClient<'a, T: Transport> { transport: T, @@ -37,14 +36,11 @@ pub fn fetch_chain_info( ) -> Result { let url = format!("{}/info", base_url); match transport.fetch(&url) { - Err(_) => - Err(DrandClientError::NotResponding), - Ok(body) => - serde_json::from_str(&body) - .map_err(|e| { - println!("{}", e); - InvalidChainInfo - }) + Err(_) => Err(DrandClientError::NotResponding), + Ok(body) => serde_json::from_str(&body).map_err(|e| { + println!("{}", e); + InvalidChainInfo + }), } } @@ -68,8 +64,12 @@ impl<'a, T: Transport> DrandClient<'a, T> { Ok(body) => match serde_json::from_str::(&body) { Ok(beacon) => { - verify_beacon(&self.chain_info.scheme_id, &self.chain_info.public_key, &beacon) - .map_err(|_| DrandClientError::FailedVerification)?; + verify_beacon( + &self.chain_info.scheme_id, + &self.chain_info.public_key, + &beacon, + ) + .map_err(|_| DrandClientError::FailedVerification)?; Ok(beacon) } Err(_) => Err(DrandClientError::InvalidBeacon), @@ -100,11 +100,10 @@ pub enum TransportError { Unexpected, } - #[cfg(test)] mod test { use crate::DrandClientError::InvalidRound; - use crate::{DrandClientError, new_http_client}; + use crate::{new_http_client, DrandClientError}; #[test] fn request_chained_randomness_success() -> Result<(), DrandClientError> { @@ -136,7 +135,8 @@ mod test { #[test] fn request_g1g2swapped_beacon_succeeds() -> Result<(), DrandClientError> { - let unchained_url = "https://api.drand.sh/dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493"; + let unchained_url = + "https://api.drand.sh/dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493"; let client = new_http_client(unchained_url)?; client.randomness(1)?; Ok(()) @@ -144,9 +144,19 @@ mod test { #[test] fn request_g1g2swapped_rfc_beacon_succeeds() -> Result<(), DrandClientError> { - let unchained_url = "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"; + let unchained_url = + "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"; let client = new_http_client(unchained_url)?; client.randomness(1)?; Ok(()) } + + #[test] + fn request_g1g2swapped_rfc_latest_succeeds() -> Result<(), DrandClientError> { + let unchained_url = + "https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971"; + let client = new_http_client(unchained_url)?; + client.latest_randomness()?; + Ok(()) + } } diff --git a/src/scheme.rs b/src/scheme.rs deleted file mode 100644 index 65e2528..0000000 --- a/src/scheme.rs +++ /dev/null @@ -1,141 +0,0 @@ -use sha2::{Digest, Sha256}; -use serde::{Deserialize, Deserializer}; -use crate::verify::{verify_on_g1, verify_on_g2}; - -#[derive(Deserialize, Debug, PartialEq, Clone)] -pub struct Beacon { - #[serde(alias = "round")] - pub round_number: u64, - #[serde(with = "hex")] - pub randomness: Vec, - #[serde(with = "hex")] - pub signature: Vec, - #[serde(default, with = "hex")] - pub previous_signature: Vec, -} - -const DST_G1: &str = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; -const DST_G2: &str = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; - -#[derive(Debug, PartialEq, Clone)] -pub enum SchemeID { - PedersenBlsChained, - PedersenBlsUnchained, - UnchainedOnG1, - UnchainedOnG1RFC9380, -} - -impl<'de> Deserialize<'de> for SchemeID { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = Deserialize::deserialize(deserializer)?; - match s { - "pedersen-bls-chained" => Ok(SchemeID::PedersenBlsChained), - "pedersen-bls-unchained" => Ok(SchemeID::PedersenBlsUnchained), - "bls-unchained-on-g1" => Ok(SchemeID::UnchainedOnG1), - "bls-unchained-g1-rfc9380" => Ok(SchemeID::UnchainedOnG1RFC9380), - _ => Err(serde::de::Error::unknown_variant(&s, &["pedersen-bls-chained", "pedersen-bls-unchained", "bls-unchained-on-g1", "bls-unchained-g1-rfc9380"])) - } - } -} - -pub fn verify_beacon<'a>(scheme_id: &SchemeID, public_key: &[u8], beacon: &'a Beacon) -> Result<(), &'a str> { - match scheme_id { - SchemeID::PedersenBlsChained => - verify_on_g2(public_key, &chained_beacon_message(beacon)?, &beacon.signature, DST_G2), - SchemeID::PedersenBlsUnchained => - verify_on_g2(public_key, &unchained_beacon_message(beacon)?, &beacon.signature, DST_G2), - SchemeID::UnchainedOnG1 => - verify_on_g1(public_key, &unchained_beacon_message(beacon)?, &beacon.signature, DST_G2), - SchemeID::UnchainedOnG1RFC9380 => - verify_on_g1(public_key, &unchained_beacon_message(beacon)?, &beacon.signature, DST_G1), - } -} - -fn unchained_beacon_message<'a>(beacon: &Beacon) -> Result, &'a str> { - if beacon.previous_signature.len() > 0 { - return Err("unchained schemes cannot contain a `previous_signature`"); - } - let round_bytes = beacon.round_number.to_be_bytes(); - - Ok(Sha256::digest(&round_bytes).to_vec()) -} - -fn chained_beacon_message<'a>(beacon: &Beacon) -> Result, &'a str> { - if beacon.previous_signature.len() == 0 { - Err("chained beacons must have a `previous_signature`") - } else { - // surely there's a better way to concat two slices - let mut message = Vec::new(); - message.extend_from_slice(&beacon.previous_signature.as_slice()); - message.extend_from_slice(&beacon.round_number.to_be_bytes()); - Ok(Sha256::digest(message.as_slice()).to_vec()) - } -} - -#[cfg(test)] -mod test { - use crate::scheme::{Beacon, SchemeID, verify_beacon}; - - #[test] - fn default_beacon_verifies() { - let public_key = hexify("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb"); - let prev_sig = hexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce"); - - let beacon = Beacon { - round_number: 397089, - randomness: hexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"), - signature: hexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"), - previous_signature: prev_sig, - }; - - assert!(matches!(verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon), Ok(()))); - } - - #[test] - fn testnet_unchained_beacon_verifies() { - let public_key = hexify("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65"); - let beacon = Beacon { - round_number: 397092, - randomness: hexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"), - signature: hexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"), - previous_signature: Vec::new(), - }; - - assert!(matches!(verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon), Ok(_))); - } - - #[test] - fn g1g2_swap_non_rfc_beacon_verifies() { - let public_key = hexify("a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e"); - let beacon = Beacon { - round_number: 3, - randomness: hexify("a4eb0ed6c4132da066843c3bfdce732ce5013eda86e74c136ab8ccc387b798dd"), - signature: hexify("8176555f90d71aa49ceb37739683749491c2bab15a46094b255289ed25cf8f01cdfb1fe8bd9cd5a19eb09448a3e53186"), - previous_signature: Vec::new(), - }; - - assert!(matches!(verify_beacon(&SchemeID::UnchainedOnG1, &public_key, &beacon), Ok(_))); - } - - #[test] - fn g1g2_swap_rfc_beacon_verifies() { - let public_key = hexify("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a"); - let beacon = Beacon { - round_number: 1000, - randomness: hexify("fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"), - signature: hexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"), - previous_signature: Vec::new(), - }; - - assert!(matches!(verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon), Ok(_))); - } - - fn hexify(s: &str) -> Vec { - return hex::decode(s) - .unwrap() - .to_vec(); - } -} diff --git a/src/verify.rs b/src/verify.rs index 41e8b58..f54c20c 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,98 +1,234 @@ -use std::ops::{Neg}; -use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, multi_miller_loop}; use bls12_381::hash_to_curve::{ExpandMsgXmd, HashToCurve}; +use bls12_381::{ + multi_miller_loop, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt, +}; +use serde::{Deserialize, Deserializer}; +use sha2::{Digest, Sha256}; +use std::ops::Neg; +use thiserror::Error; + +#[derive(Deserialize, Debug, PartialEq, Clone)] +pub struct Beacon { + #[serde(alias = "round")] + pub round_number: u64, + #[serde(with = "hex")] + pub randomness: Vec, + #[serde(with = "hex")] + pub signature: Vec, + #[serde(default, with = "hex")] + pub previous_signature: Vec, +} + +const DST_G1: &str = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; +const DST_G2: &str = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; + +#[derive(Debug, PartialEq, Clone)] +pub enum SchemeID { + PedersenBlsChained, + PedersenBlsUnchained, + UnchainedOnG1, + UnchainedOnG1RFC9380, +} + +impl<'de> Deserialize<'de> for SchemeID { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = Deserialize::deserialize(deserializer)?; + match s { + "pedersen-bls-chained" => Ok(SchemeID::PedersenBlsChained), + "pedersen-bls-unchained" => Ok(SchemeID::PedersenBlsUnchained), + "bls-unchained-on-g1" => Ok(SchemeID::UnchainedOnG1), + "bls-unchained-g1-rfc9380" => Ok(SchemeID::UnchainedOnG1RFC9380), + _ => Err(serde::de::Error::unknown_variant( + &s, + &[ + "pedersen-bls-chained", + "pedersen-bls-unchained", + "bls-unchained-on-g1", + "bls-unchained-g1-rfc9380", + ], + )), + } + } +} + +#[derive(Error, Debug, PartialEq)] +pub enum VerificationError { + #[error("unchained schemes cannot contain a `previous_signature`")] + UnchainedHasPreviousSignature, + #[error("chained beacons must have a `previous_signature`")] + ChainedBeaconNeedsPreviousSignature, + #[error("invalid signature length")] + InvalidSignatureLength, + #[error("invalid public key")] + InvalidPublicKey, + #[error("message can't be empty")] + EmptyMessage, + #[error("signature verification failed")] + SignatureFailedVerification, + #[error("the randomness for the beacon did not match the signature")] + InvalidRandomness, +} + +pub fn verify_beacon<'a>( + scheme_id: &SchemeID, + public_key: &[u8], + beacon: &'a Beacon, +) -> Result<(), VerificationError> { + if Sha256::digest(&beacon.signature).to_vec() != beacon.randomness { + return Err(VerificationError::InvalidRandomness); + } + match scheme_id { + SchemeID::PedersenBlsChained => verify_on_g2( + public_key, + &chained_beacon_message(beacon)?, + &beacon.signature, + DST_G2, + ), + SchemeID::PedersenBlsUnchained => verify_on_g2( + public_key, + &unchained_beacon_message(beacon)?, + &beacon.signature, + DST_G2, + ), + SchemeID::UnchainedOnG1 => verify_on_g1( + public_key, + &unchained_beacon_message(beacon)?, + &beacon.signature, + DST_G2, + ), + SchemeID::UnchainedOnG1RFC9380 => verify_on_g1( + public_key, + &unchained_beacon_message(beacon)?, + &beacon.signature, + DST_G1, + ), + } +} + +fn unchained_beacon_message<'a>(beacon: &Beacon) -> Result, VerificationError> { + if beacon.previous_signature.len() > 0 { + return Err(VerificationError::UnchainedHasPreviousSignature); + } + let round_bytes = beacon.round_number.to_be_bytes(); -pub fn verify_on_g2<'a>(public_key: &[u8], message: &[u8], signature: &[u8], domain_separation_tag: &str) -> Result<(), &'a str> { + Ok(Sha256::digest(&round_bytes).to_vec()) +} + +fn chained_beacon_message<'a>(beacon: &Beacon) -> Result, VerificationError> { + if beacon.previous_signature.len() == 0 { + Err(VerificationError::ChainedBeaconNeedsPreviousSignature) + } else { + // surely there's a better way to concat two slices + let mut message = Vec::new(); + message.extend_from_slice(&beacon.previous_signature.as_slice()); + message.extend_from_slice(&beacon.round_number.to_be_bytes()); + Ok(Sha256::digest(message.as_slice()).to_vec()) + } +} + +pub fn verify_on_g2<'a>( + public_key: &[u8], + message: &[u8], + signature: &[u8], + domain_separation_tag: &str, +) -> Result<(), VerificationError> { let pub_key_bytes: &[u8; 48] = public_key .try_into() - .map_err(|_| "public key wrong length")?; + .map_err(|_| VerificationError::InvalidPublicKey)?; let sig_bytes: &[u8; 96] = signature .try_into() - .map_err(|_| "signature wrong length")?; + .map_err(|_| VerificationError::InvalidSignatureLength)?; + + let p = G1Affine::from_compressed(pub_key_bytes).unwrap_or(G1Affine::identity()); - let p = G1Affine::from_compressed(pub_key_bytes).unwrap(); - let q = G2Affine::from_compressed(sig_bytes).unwrap(); + let q = G2Affine::from_compressed(sig_bytes).unwrap_or(G2Affine::identity()); if p.is_on_curve().unwrap_u8() != 1 { - return Err("not on curve"); + return Err(VerificationError::InvalidPublicKey); } if p.is_identity().unwrap_u8() == 1 { - return Err("cannot use point at infinity"); + return Err(VerificationError::InvalidPublicKey); } if message.len() == 0 { - return Err("message can't be empty"); + return Err(VerificationError::EmptyMessage); } if signature.len() == 0 { - return Err("signature can't be empty"); + return Err(VerificationError::InvalidSignatureLength); } - let m = >>::hash_to_curve( - &message, domain_separation_tag.as_bytes(), + let m = >>::hash_to_curve( + &message, + domain_separation_tag.as_bytes(), ); let m_prepared = G2Prepared::from(G2Affine::from(m)); let q_prepared = G2Prepared::from(q); - let exp = multi_miller_loop( - &[ - (&p.neg(), &m_prepared), - (&G1Affine::generator(), &q_prepared) - ] - ); + let exp = multi_miller_loop(&[ + (&p.neg(), &m_prepared), + (&G1Affine::generator(), &q_prepared), + ]); if exp.final_exponentiation() != Gt::identity() { - Err("verification failed") + Err(VerificationError::SignatureFailedVerification) } else { Ok(()) } } -pub fn verify_on_g1<'a>(public_key: &[u8], message: &[u8], signature: &[u8], domain_separation_tag: &str) -> Result<(), &'a str> { +pub fn verify_on_g1<'a>( + public_key: &[u8], + message: &[u8], + signature: &[u8], + domain_separation_tag: &str, +) -> Result<(), VerificationError> { let pub_key_bytes: &[u8; 96] = public_key .try_into() - .map_err(|_| "public key wrong length")?; + .map_err(|_| VerificationError::InvalidPublicKey)?; let sig_bytes: &[u8; 48] = signature .try_into() - .map_err(|_| "signature wrong length")?; + .map_err(|_| VerificationError::InvalidSignatureLength)?; - let signature_point = G1Affine::from_compressed(sig_bytes).unwrap(); - let pubkey_point = G2Affine::from_compressed(pub_key_bytes).unwrap(); + let signature_point = G1Affine::from_compressed(sig_bytes).unwrap_or(G1Affine::identity()); + let pubkey_point = G2Affine::from_compressed(pub_key_bytes).unwrap_or(G2Affine::identity()); if pubkey_point.is_on_curve().unwrap_u8() != 1 { - return Err("not on curve"); + return Err(VerificationError::InvalidPublicKey); } if pubkey_point.is_identity().unwrap_u8() == 1 { - return Err("cannot use point at infinity"); + return Err(VerificationError::InvalidPublicKey); } if message.len() == 0 { - return Err("message can't be empty"); + return Err(VerificationError::EmptyMessage); } if signature.len() == 0 { - return Err("signature can't be empty"); + return Err(VerificationError::InvalidSignatureLength); } - let m = >>::hash_to_curve( - &message, domain_separation_tag.as_bytes(), + let m = >>::hash_to_curve( + &message, + domain_separation_tag.as_bytes(), ); let pubkey_prepared = G2Prepared::from(pubkey_point.neg()); let g2_base = G2Prepared::from(G2Affine::generator()); - let exp = multi_miller_loop( - &[ - (&G1Affine::from(m), &pubkey_prepared), - (&signature_point, &g2_base) - ] - ); + let exp = multi_miller_loop(&[ + (&G1Affine::from(m), &pubkey_prepared), + (&signature_point, &g2_base), + ]); if exp.final_exponentiation() != Gt::identity() { - Err("verification failed") + Err(VerificationError::SignatureFailedVerification) } else { Ok(()) } @@ -100,62 +236,377 @@ pub fn verify_on_g1<'a>(public_key: &[u8], message: &[u8], signature: &[u8], dom #[cfg(test)] mod test { - use sha2::Digest; - use crate::verify::{verify_on_g2, verify_on_g1}; + use crate::verify::{verify_beacon, Beacon, SchemeID, VerificationError}; + use bls12_381::{G1Affine, G2Affine}; #[test] - fn g1_verifies_a_beacon() -> Result<(), ()> { - let public_key = hex::decode("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb").unwrap(); - let round: u64 = 397089; - let sig = hex::decode("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a").unwrap(); - let prev_sig = hex::decode("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce").unwrap(); + fn default_beacon_verifies() { + let public_key = dehexify("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb"); + let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce"); + + let beacon = Beacon { + round_number: 397089, + randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"), + signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"), + previous_signature: prev_sig, + }; + + assert!(matches!( + verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon), + Ok(()), + )); + } - let mut message = Vec::new(); - message.extend(prev_sig); - message.extend(round.to_be_bytes()); + #[test] + fn default_wrong_round_fails() { + let public_key = dehexify("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb"); + let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce"); + + let beacon = Beacon { + round_number: 1, // wrong round for randomness + randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"), + signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"), + previous_signature: prev_sig, + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon), + VerificationError::SignatureFailedVerification, + ); + } - let m = sha2::Sha256::digest(message.as_slice()); + #[test] + fn default_with_invalid_randomness_fails() { + let public_key = dehexify("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb"); + let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce"); + + let beacon = Beacon { + round_number: 397089, + // updated the randomness hex to be wrong + randomness: dehexify("bd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"), + signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"), + previous_signature: prev_sig, + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon), + VerificationError::InvalidRandomness, + ); + } - let _ = verify_on_g2(&public_key, &m.to_vec(), &sig, "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_").unwrap(); - Ok(()) + #[test] + fn default_beacon_missing_previous_sig_fails() { + let public_key = dehexify("88a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb"); + + let beacon = Beacon { + round_number: 397089, + randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"), + signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon), + VerificationError::ChainedBeaconNeedsPreviousSignature, + ); } #[test] - fn g1_unchained_beacon_verifies() -> Result<(), ()> { - let public_key = hex::decode("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65").unwrap(); - let round: u64 = 397092; - let sig = hex::decode("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539").unwrap(); + fn default_beacon_invalid_public_key_fails() { + // public key is not correct + let public_key = dehexify("78a8227b75dba145599d894d33eebde3b36fef900d456ae2cc4388867adb4769c40359f783750a41b4d17e40f578bfdb"); + let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce"); + + let beacon = Beacon { + round_number: 397089, + randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"), + signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"), + previous_signature: prev_sig, + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon), + VerificationError::InvalidPublicKey, + ); + } - let mut message = Vec::new(); - message.extend(round.to_be_bytes()); + #[test] + fn default_beacon_empty_public_key_fails() { + let public_key = Vec::new(); + let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce"); + + let beacon = Beacon { + round_number: 397089, + randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"), + signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"), + previous_signature: prev_sig, + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon), + VerificationError::InvalidPublicKey, + ); + } - let m = sha2::Sha256::digest(message.as_slice()); + #[test] + fn default_beacon_infinity_public_key_fails() { + let public_key = G1Affine::identity().to_compressed(); + let prev_sig = dehexify("a2237ee39a1a6569cb8e02c6e979c07efe1f30be0ac501436bd325015f1cd6129dc56fd60efcdf9158d74ebfa34bfcbd17803dbca6d2ae8bc3a968e4dc582f8710c69de80b2e649663fef5742d22fff7d1619b75d5f222e8c9b8840bc2044bce"); + + let beacon = Beacon { + round_number: 397089, + randomness: dehexify("cd435675735e459fb4d9c68a9d9f7b719e59e0a9f5f86fe6bd86b730d01fba42"), + signature: dehexify("88ccd9a91946bc0bbef2c6c60a09bbf4a247b1d2059522449aa1a35758feddfad85efe818bbde3e1e4ab0c852d96e65f0b1f97f239bf3fc918860ea846cbb500fcf7c9d0dd3d851320374460b5fc596b8cfd629f4c07c7507c259bf9beca850a"), + previous_signature: prev_sig, + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsChained, &public_key, &beacon), + VerificationError::InvalidPublicKey, + ); + } - let _ = verify_on_g2(&public_key, &m.to_vec(), &sig, "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_").unwrap(); - Ok(()) + #[test] + fn testnet_unchained_beacon_verifies() { + let public_key = dehexify("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65"); + let beacon = Beacon { + round_number: 397092, + randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"), + signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"), + previous_signature: Vec::new(), + }; + + assert!(matches!( + verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon), + Ok(_), + )); } #[test] - fn g2_non_rfc_verifies_a_beacon() -> Result<(), ()> { - let public_key = hex::decode("a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e").unwrap(); - let round: u64 = 3; - let sig = hex::decode("8176555f90d71aa49ceb37739683749491c2bab15a46094b255289ed25cf8f01cdfb1fe8bd9cd5a19eb09448a3e53186").unwrap(); + fn testnet_unchained_beacon_wrong_round_fails() { + let public_key = dehexify("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65"); + let beacon = Beacon { + round_number: 1, // wrong round + randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"), + signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon), + VerificationError::SignatureFailedVerification, + ); + } - let m = sha2::Sha256::digest(round.to_be_bytes().as_slice()); + #[test] + fn testnet_unchained_beacon_randomness_not_matching_signature_fails() { + let public_key = dehexify("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65"); + let beacon = Beacon { + round_number: 397092, + // mismatching randomness + randomness: dehexify("a731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"), + signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon), + VerificationError::InvalidRandomness, + ); + } - let _ = verify_on_g1(&public_key, &m.to_vec(), &sig, "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_").unwrap(); - Ok(()) + #[test] + fn testnet_unchained_beacon_containing_previous_sig_fails() { + let public_key = dehexify("8d91ae0f4e3cd277cfc46aba26680232b0d5bb4444602cdb23442d62e17f43cdffb1104909e535430c10a6a1ce680a65"); + let beacon = Beacon { + round_number: 397092, + randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"), + signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"), + previous_signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"), + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon), + VerificationError::UnchainedHasPreviousSignature, + ); } #[test] - fn g2_rfc_verifies_a_beacon() -> Result<(), ()> { - let public_key = hex::decode("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a").unwrap(); - let round: u64 = 1000; - let sig = hex::decode("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39").unwrap(); + fn testnet_unchained_invalid_public_key_fails() { + // valid public key, but for wrong scheme + let public_key = dehexify("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31"); + let beacon = Beacon { + round_number: 397092, + randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"), + signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon), + VerificationError::SignatureFailedVerification, + ); + } - let m = sha2::Sha256::digest(round.to_be_bytes().as_slice()); + #[test] + fn testnet_unchained_beacon_empty_public_key_fails() { + let public_key = Vec::new(); + let beacon = Beacon { + round_number: 397092, + randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"), + signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon), + VerificationError::InvalidPublicKey, + ); + } - let _ = verify_on_g1(&public_key, &m.to_vec(), &sig, "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_").unwrap(); - Ok(()) + #[test] + fn testnet_unchained_beacon_infinity_public_key_fails() { + let public_key = G2Affine::identity().to_uncompressed(); + let beacon = Beacon { + round_number: 397092, + randomness: dehexify("7731783ab8118d7484d0e8e237f3023a4c7ef4532f35016f2e56e89a7570c796"), + signature: dehexify("94da96b5b985a22a3d99fa3051a42feb4da9218763f6c836fca3770292dbf4b01f5d378859a113960548d167eaa144250a2c8e34c51c5270152ac2bc7a52632236f746545e0fae52f69068c017745204240d19dae2b4d038cef3c6047fcd6539"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::PedersenBlsUnchained, &public_key, &beacon), + VerificationError::InvalidPublicKey, + ); + } + + #[test] + fn g1g2_swap_non_rfc_beacon_verifies() { + let public_key = dehexify("a0b862a7527fee3a731bcb59280ab6abd62d5c0b6ea03dc4ddf6612fdfc9d01f01c31542541771903475eb1ec6615f8d0df0b8b6dce385811d6dcf8cbefb8759e5e616a3dfd054c928940766d9a5b9db91e3b697e5d70a975181e007f87fca5e"); + let beacon = Beacon { + round_number: 3, + randomness: dehexify("a4eb0ed6c4132da066843c3bfdce732ce5013eda86e74c136ab8ccc387b798dd"), + signature: dehexify("8176555f90d71aa49ceb37739683749491c2bab15a46094b255289ed25cf8f01cdfb1fe8bd9cd5a19eb09448a3e53186"), + previous_signature: Vec::new(), + }; + + assert!(matches!( + verify_beacon(&SchemeID::UnchainedOnG1, &public_key, &beacon), + Ok(_) + )); + } + + #[test] + fn g1g2_swap_rfc_beacon_verifies() { + let public_key = dehexify("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a"); + let beacon = Beacon { + round_number: 1000, + randomness: dehexify("fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"), + signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"), + previous_signature: Vec::new(), + }; + + assert!(matches!( + verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon), + Ok(_) + )); + } + + #[test] + fn g1g2_swap_empty_public_key_fails() { + let public_key = Vec::new(); + let beacon = Beacon { + round_number: 1000, + randomness: dehexify("fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"), + signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon), + VerificationError::InvalidPublicKey, + ); + } + + #[test] + fn g1g2_swap_infinity_public_key_fails() { + let public_key = G2Affine::identity().to_uncompressed(); + let beacon = Beacon { + round_number: 1000, + randomness: dehexify("fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"), + signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon), + VerificationError::InvalidPublicKey, + ); } -} \ No newline at end of file + + #[test] + fn g1g2_swap_wrong_round_fails() { + let public_key = dehexify("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a"); + let beacon = Beacon { + round_number: 1, + randomness: dehexify("fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"), + signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon), + VerificationError::SignatureFailedVerification, + ); + } + + #[test] + fn g1g2_swap_invalid_randomness_fails() { + let public_key = dehexify("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a"); + let beacon = Beacon { + round_number: 1000, + // incorrect hash for the signature + randomness: dehexify("aa290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd"), + signature: dehexify("b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon), + VerificationError::InvalidRandomness, + ); + } + + #[test] + fn g1g2_swap_invalid_signature_fails() { + let public_key = dehexify("83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a"); + let beacon = Beacon { + round_number: 1000, + // this is not a valid signature + signature: dehexify("a44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39"), + // but the hash matches it + randomness: dehexify("5993706587c56d4e7079d175bfa5d52295694896e68c691b93765242096c9fa7"), + previous_signature: Vec::new(), + }; + + assert_error( + verify_beacon(&SchemeID::UnchainedOnG1RFC9380, &public_key, &beacon), + VerificationError::SignatureFailedVerification, + ); + } + + fn dehexify(s: &str) -> Vec { + return hex::decode(s).unwrap().to_vec(); + } + + fn assert_error(actual: Result<(), VerificationError>, expected: VerificationError) { + match actual { + Ok(_) => panic!("expected error but got success"), + Err(e) => { + if e != expected { + panic!("expected {:?} but got {:?}", expected, e); + } + } + } + } +}