diff --git a/aqua-examples/drand/services/drand/Cargo.toml b/aqua-examples/drand/services/drand/Cargo.toml index e6a99b9..decc86a 100644 --- a/aqua-examples/drand/services/drand/Cargo.toml +++ b/aqua-examples/drand/services/drand/Cargo.toml @@ -13,7 +13,8 @@ path = "src/main.rs" [dependencies] marine-rs-sdk = { version = "0.7.1", features = ["logger"] } # log = "0.4.14" -# drand-verify = "0.3.0" +# drand-verify = { version = "0.3.0", js = false, optional = false } +drand-verify = { version = "0.3.0", js = false, optional = false } hex = "0.4.3" serde = "1.0.148" serde_json = "1.0.89" diff --git a/aqua-examples/drand/services/drand/src/main.rs b/aqua-examples/drand/services/drand/src/main.rs index 99e1bbd..232c5da 100644 --- a/aqua-examples/drand/services/drand/src/main.rs +++ b/aqua-examples/drand/services/drand/src/main.rs @@ -15,6 +15,7 @@ */ // use drand_verify::{derive_randomness, g1_from_fixed, verify}; +use drand_verify::{derive_randomness, g1_from_fixed, verify}; use hex; use marine_rs_sdk::module_manifest; use marine_rs_sdk::{marine, MountedBinaryResult}; @@ -22,14 +23,6 @@ use serde::{Deserialize, Serialize}; use std::convert::TryInto; -mod points; -mod randomness; -mod verify; - -use points::g1_from_fixed; -use randomness::derive_randomness; -use verify::verify; - module_manifest!(); pub fn main() {} diff --git a/aqua-examples/drand/services/drand/src/points.rs b/aqua-examples/drand/services/drand/src/points.rs deleted file mode 100644 index 80d695c..0000000 --- a/aqua-examples/drand/services/drand/src/points.rs +++ /dev/null @@ -1,233 +0,0 @@ -// https://raw.githubusercontent.com/noislabs/drand-verify/main/src/points.rs - -use std::fmt; - -use groupy::{EncodedPoint, GroupDecodingError}; -use paired::bls12_381::{G1Affine, G1Compressed, G2Affine, G2Compressed}; - -#[derive(Debug)] -pub enum InvalidPoint { - InvalidLength { expected: usize, actual: usize }, - DecodingError { msg: String }, -} - -impl From for InvalidPoint { - fn from(source: GroupDecodingError) -> Self { - InvalidPoint::DecodingError { - msg: format!("{}", source), - } - } -} - -impl fmt::Display for InvalidPoint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - InvalidPoint::InvalidLength { expected, actual } => { - write!(f, "Invalid input length for point (must be in compressed format): Expected {}, actual: {}", expected, actual) - } - InvalidPoint::DecodingError { msg } => { - write!(f, "Invalid point: {}", msg) - } - } - } -} - -pub fn g1_from_variable(data: &[u8]) -> Result { - if data.len() != G1Compressed::size() { - return Err(InvalidPoint::InvalidLength { - expected: G1Compressed::size(), - actual: data.len(), - }); - } - - let mut buf = [0u8; 48]; - buf[..].clone_from_slice(data); - g1_from_fixed(buf) -} - -/// Like [`g1_from_variable`] without guaranteeing that the encoding represents a valid element. -/// Only use this when you know for sure the encoding is correct. -pub fn g1_from_variable_unchecked(data: &[u8]) -> Result { - if data.len() != G1Compressed::size() { - return Err(InvalidPoint::InvalidLength { - expected: G1Compressed::size(), - actual: data.len(), - }); - } - - let mut buf = [0u8; 48]; - buf[..].clone_from_slice(data); - g1_from_fixed_unchecked(buf) -} - -pub fn g2_from_variable(data: &[u8]) -> Result { - if data.len() != G2Compressed::size() { - return Err(InvalidPoint::InvalidLength { - expected: G2Compressed::size(), - actual: data.len(), - }); - } - - let mut buf = [0u8; 96]; - buf[..].clone_from_slice(data); - g2_from_fixed(buf) -} - -/// Like [`g2_from_variable`] without guaranteeing that the encoding represents a valid element. -/// Only use this when you know for sure the encoding is correct. -pub fn g2_from_variable_unchecked(data: &[u8]) -> Result { - if data.len() != G2Compressed::size() { - return Err(InvalidPoint::InvalidLength { - expected: G2Compressed::size(), - actual: data.len(), - }); - } - - let mut buf = [0u8; 96]; - buf[..].clone_from_slice(data); - g2_from_fixed_unchecked(buf) -} - -pub fn g1_from_fixed(data: [u8; 48]) -> Result { - // Workaround for https://github.com/filecoin-project/paired/pull/23 - let mut compressed = G1Compressed::empty(); - compressed.as_mut().copy_from_slice(&data); - Ok(compressed.into_affine()?) -} - -/// Like [`g1_from_fixed`] without guaranteeing that the encoding represents a valid element. -/// Only use this when you know for sure the encoding is correct. -pub fn g1_from_fixed_unchecked(data: [u8; 48]) -> Result { - // Workaround for https://github.com/filecoin-project/paired/pull/23 - let mut compressed = G1Compressed::empty(); - compressed.as_mut().copy_from_slice(&data); - Ok(compressed.into_affine_unchecked()?) -} - -pub fn g2_from_fixed(data: [u8; 96]) -> Result { - // Workaround for https://github.com/filecoin-project/paired/pull/23 - let mut compressed = G2Compressed::empty(); - compressed.as_mut().copy_from_slice(&data); - Ok(compressed.into_affine()?) -} - -/// Like [`g2_from_fixed`] without guaranteeing that the encoding represents a valid element. -/// Only use this when you know for sure the encoding is correct. -pub fn g2_from_fixed_unchecked(data: [u8; 96]) -> Result { - // Workaround for https://github.com/filecoin-project/paired/pull/23 - let mut compressed = G2Compressed::empty(); - compressed.as_mut().copy_from_slice(&data); - Ok(compressed.into_affine_unchecked()?) -} - -#[cfg(test)] -mod tests { - use super::*; - use hex_literal::hex; - - #[test] - fn g1_from_variable_works() { - let result = g1_from_variable(&hex::decode("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31").unwrap()); - assert!(result.is_ok()); - - let result = g1_from_variable(&hex::decode("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af").unwrap()); - match result.unwrap_err() { - InvalidPoint::InvalidLength { expected, actual } => { - assert_eq!(expected, 48); - assert_eq!(actual, 47); - } - err => panic!("Unexpected error: {:?}", err), - } - } - - #[test] - fn g1_from_variable_unchecked_works() { - let data = hex::decode("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31").unwrap(); - let a = g1_from_variable_unchecked(&data).unwrap(); - let b = g1_from_variable(&data).unwrap(); - assert_eq!(a, b); - } - - #[test] - fn g2_from_variable_works() { - let result = g2_from_variable(&hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap()); - assert!(result.is_ok()); - - let result = g2_from_variable(&hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e").unwrap()); - match result.unwrap_err() { - InvalidPoint::InvalidLength { expected, actual } => { - assert_eq!(expected, 96); - assert_eq!(actual, 95); - } - err => panic!("Unexpected error: {:?}", err), - } - } - - #[test] - fn g2_from_variable_unchecked_works() { - let data = hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap(); - let a = g2_from_variable_unchecked(&data).unwrap(); - let b = g2_from_variable(&data).unwrap(); - assert_eq!(a, b); - } - - #[test] - fn g1_from_fixed_works() { - let result = g1_from_fixed(hex_literal::hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31")); - assert!(result.is_ok()); - - let result = g1_from_fixed(hex_literal::hex!("118f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31")); - match result.unwrap_err() { - InvalidPoint::DecodingError { msg } => { - assert_eq!(msg, "encoding has unexpected compression mode"); - } - err => panic!("Unexpected error: {:?}", err), - } - - let result = g1_from_fixed(hex_literal::hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af22")); - match result.unwrap_err() { - InvalidPoint::DecodingError { msg } => { - assert_eq!(msg, "coordinate(s) do not lie on the curve"); - } - err => panic!("Unexpected error: {:?}", err), - } - } - - #[test] - fn g1_from_fixed_unchecked_works() { - let data = hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31"); - let a = g1_from_fixed_unchecked(data).unwrap(); - let b = g1_from_fixed(data).unwrap(); - assert_eq!(a, b); - } - - #[test] - fn g2_from_fixed_works() { - let result = g2_from_fixed(hex_literal::hex!("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42")); - assert!(result.is_ok()); - - let result = g2_from_fixed(hex_literal::hex!("11f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42")); - match result.unwrap_err() { - InvalidPoint::DecodingError { msg } => { - assert_eq!(msg, "encoding has unexpected compression mode"); - } - err => panic!("Unexpected error: {:?}", err), - } - - let result = g2_from_fixed(hex_literal::hex!("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e44")); - match result.unwrap_err() { - InvalidPoint::DecodingError { msg } => { - assert_eq!(msg, "coordinate(s) do not lie on the curve"); - } - err => panic!("Unexpected error: {:?}", err), - } - } - - #[test] - fn g2_from_fixed_unchecked_works() { - let data = hex!("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42"); - let a = g2_from_fixed_unchecked(data).unwrap(); - let b = g2_from_fixed(data).unwrap(); - assert_eq!(a, b); - } -} diff --git a/aqua-examples/drand/services/drand/src/randomness.rs b/aqua-examples/drand/services/drand/src/randomness.rs deleted file mode 100644 index 59a6e13..0000000 --- a/aqua-examples/drand/services/drand/src/randomness.rs +++ /dev/null @@ -1,29 +0,0 @@ -use sha2::{Digest, Sha256}; - -/// Derives a 32 byte randomness from the beacon's signature -pub fn derive_randomness(signature: &[u8]) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(signature); - hasher.finalize().into() -} - -#[cfg(test)] -mod tests { - use super::*; - use hex_literal::hex; - - #[test] - fn derives_randomness_correctly() { - // curl -sS https://drand.cloudflare.com/public/72785 - let signature = hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap(); - let expected_randomness = - hex!("8b676484b5fb1f37f9ec5c413d7d29883504e5b669f604a1ce68b3388e9ae3d9"); - assert_eq!(derive_randomness(&signature), expected_randomness); - - // curl -sS https://drand.cloudflare.com/public/1337 - let signature = hex::decode("945b08dcb30e24da281ccf14a646f0630ceec515af5c5895e18cc1b19edd65d156b71c776a369af3487f1bc6af1062500b059e01095cc0eedce91713977d7735cac675554edfa0d0481bb991ed93d333d08286192c05bf6b65d20f23a37fc7bb").unwrap(); - let expected_randomness = - hex!("2660664f8d4bc401194d80d81da20a1e79480f65b8e2d205aecbd143b5bfb0d3"); - assert_eq!(derive_randomness(&signature), expected_randomness); - } -} diff --git a/aqua-examples/drand/services/drand/src/verify.rs b/aqua-examples/drand/services/drand/src/verify.rs deleted file mode 100644 index 045190d..0000000 --- a/aqua-examples/drand/services/drand/src/verify.rs +++ /dev/null @@ -1,155 +0,0 @@ -// from https://raw.githubusercontent.com/noislabs/drand-verify/main/src/verify.rs -use fff::Field; -use groupy::{CurveAffine, CurveProjective}; -use paired::bls12_381::{Bls12, Fq12, G1Affine, G2Affine, G2}; -use paired::{Engine, ExpandMsgXmd, HashToCurve, PairingCurveAffine}; -use sha2::{Digest, Sha256}; -use std::error::Error; -use std::fmt; - -const DOMAIN: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; - -use super::points::g2_from_variable; - -#[derive(Debug)] -pub enum VerificationError { - InvalidPoint { field: String, msg: String }, -} - -impl fmt::Display for VerificationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - VerificationError::InvalidPoint { field, msg } => { - write!(f, "Invalid point for field {}: {}", field, msg) - } - } - } -} - -impl Error for VerificationError {} - -// Verify checks beacon components to see if they are valid. -pub fn verify( - pk: &G1Affine, - round: u64, - previous_signature: &[u8], - signature: &[u8], -) -> Result { - let msg_on_g2 = verify_step1(round, previous_signature); - verify_step2(pk, signature, &msg_on_g2) -} - -/// First step of the verification. -/// Should not be used directly in most cases. Use [`verify`] instead. -/// -/// This API is not stable. -#[doc(hidden)] -pub fn verify_step1(round: u64, previous_signature: &[u8]) -> G2Affine { - let msg = message(round, previous_signature); - msg_to_curve(&msg) -} - -/// Second step of the verification. -/// Should not be used directly in most cases. Use [`verify`] instead. -/// -/// This API is not stable. -#[doc(hidden)] -pub fn verify_step2( - pk: &G1Affine, - signature: &[u8], - msg_on_g2: &G2Affine, -) -> Result { - let g1 = G1Affine::one(); - let sigma = match g2_from_variable(signature) { - Ok(sigma) => sigma, - Err(err) => { - return Err(VerificationError::InvalidPoint { - field: "signature".into(), - msg: err.to_string(), - }) - } - }; - Ok(fast_pairing_equality(&g1, &sigma, pk, msg_on_g2)) -} - -/// Checks if e(p, q) == e(r, s) -/// -/// See https://hackmd.io/@benjaminion/bls12-381#Final-exponentiation. -/// -/// Optimized by this trick: -/// Instead of doing e(a,b) (in G2) multiplied by e(-c,d) (in G2) -/// (which is costly is to multiply in G2 because these are very big numbers) -/// we can do FinalExponentiation(MillerLoop( [a,b], [-c,d] )) which is the same -/// in an optimized way. -fn fast_pairing_equality(p: &G1Affine, q: &G2Affine, r: &G1Affine, s: &G2Affine) -> bool { - let minus_p = { - let mut out = *p; - out.negate(); - out - }; - // "some number of (G1, G2) pairs" are the inputs of the miller loop - let pair1 = (&minus_p.prepare(), &q.prepare()); - let pair2 = (&r.prepare(), &s.prepare()); - let looped = Bls12::miller_loop([&pair1, &pair2]); - match Bls12::final_exponentiation(&looped) { - Some(value) => value == Fq12::one(), - None => false, - } -} - -fn message(current_round: u64, prev_sig: &[u8]) -> Vec { - let mut hasher = Sha256::default(); - hasher.update(prev_sig); - hasher.update(round_to_bytes(current_round)); - hasher.finalize().to_vec() -} - -/// https://github.com/drand/drand-client/blob/master/wasm/chain/verify.go#L28-L33 -#[inline] -fn round_to_bytes(round: u64) -> [u8; 8] { - round.to_be_bytes() -} - -fn msg_to_curve(msg: &[u8]) -> G2Affine { - let g = >>::hash_to_curve(msg, DOMAIN); - g.into_affine() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::points::g1_from_fixed; - use hex_literal::hex; - - /// Public key League of Entropy Mainnet (curl -sS https://drand.cloudflare.com/info) - const PK_LEO_MAINNET: [u8; 48] = hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31"); - - #[test] - fn verify_works() { - let pk = g1_from_fixed(PK_LEO_MAINNET).unwrap(); - - // curl -sS https://drand.cloudflare.com/public/72785 - let previous_signature = hex::decode("a609e19a03c2fcc559e8dae14900aaefe517cb55c840f6e69bc8e4f66c8d18e8a609685d9917efbfb0c37f058c2de88f13d297c7e19e0ab24813079efe57a182554ff054c7638153f9b26a60e7111f71a0ff63d9571704905d3ca6df0b031747").unwrap(); - let signature = hex::decode("82f5d3d2de4db19d40a6980e8aa37842a0e55d1df06bd68bddc8d60002e8e959eb9cfa368b3c1b77d18f02a54fe047b80f0989315f83b12a74fd8679c4f12aae86eaf6ab5690b34f1fddd50ee3cc6f6cdf59e95526d5a5d82aaa84fa6f181e42").unwrap(); - let round: u64 = 72785; - - // good - let result = verify(&pk, round, &previous_signature, &signature).unwrap(); - assert!(result); - - // wrong round - let result = verify(&pk, 321, &previous_signature, &signature).unwrap(); - assert!(!result); - - // wrong previous signature - let previous_signature_corrupted = hex::decode("6a09e19a03c2fcc559e8dae14900aaefe517cb55c840f6e69bc8e4f66c8d18e8a609685d9917efbfb0c37f058c2de88f13d297c7e19e0ab24813079efe57a182554ff054c7638153f9b26a60e7111f71a0ff63d9571704905d3ca6df0b031747").unwrap(); - let result = verify(&pk, round, &previous_signature_corrupted, &signature).unwrap(); - assert!(!result); - - // wrong signature - // (use signature from https://drand.cloudflare.com/public/1 to get a valid curve point) - let wrong_signature = hex::decode("8d61d9100567de44682506aea1a7a6fa6e5491cd27a0a0ed349ef6910ac5ac20ff7bc3e09d7c046566c9f7f3c6f3b10104990e7cb424998203d8f7de586fb7fa5f60045417a432684f85093b06ca91c769f0e7ca19268375e659c2a2352b4655").unwrap(); - let result = verify(&pk, round, &previous_signature, &wrong_signature).unwrap(); - assert!(!result); - } -}