mirror of
https://github.com/fluencelabs/examples
synced 2025-04-25 10:42:16 +00:00
add drand + verify
This commit is contained in:
parent
f3dac4c1df
commit
ce9793ee63
32
aqua-examples/drand/services/drand/Cargo.toml
Normal file
32
aqua-examples/drand/services/drand/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
[package]
|
||||||
|
name = "drand"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["boneyard93501 <4523011+boneyard93501@users.noreply.github.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "drand, a Marine wasi module"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "drand"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
marine-rs-sdk = { version = "0.7.1", features = ["logger"] }
|
||||||
|
# log = "0.4.14"
|
||||||
|
# drand-verify = "0.3.0"
|
||||||
|
hex = "0.4.3"
|
||||||
|
serde = "1.0.148"
|
||||||
|
serde_json = "1.0.89"
|
||||||
|
fff = "0.3.1"
|
||||||
|
groupy = "0.4.1"
|
||||||
|
wasm-bindgen = "0.2.83"
|
||||||
|
paired = "0.22.0"
|
||||||
|
sha2 = "0.9.9"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
marine-rs-sdk-test = "0.8.1"
|
||||||
|
hex-literal = "0.3.4"
|
||||||
|
|
||||||
|
[dev]
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "s"
|
335
aqua-examples/drand/services/drand/src/main.rs
Normal file
335
aqua-examples/drand/services/drand/src/main.rs
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 Fluence Labs Limited
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// use drand_verify::{derive_randomness, g1_from_fixed, verify};
|
||||||
|
use hex;
|
||||||
|
use marine_rs_sdk::module_manifest;
|
||||||
|
use marine_rs_sdk::{marine, MountedBinaryResult};
|
||||||
|
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() {}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct Chain {
|
||||||
|
pub hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct Chains {
|
||||||
|
pub hashes: Vec<Chains>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
pub struct DResult {
|
||||||
|
pub stderr: String,
|
||||||
|
pub stdout: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct DInfo {
|
||||||
|
pub public_key: String,
|
||||||
|
pub period: u64,
|
||||||
|
pub genesis_time: u64,
|
||||||
|
pub hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct Randomness {
|
||||||
|
pub round: u64,
|
||||||
|
pub randomness: String,
|
||||||
|
pub signature: String,
|
||||||
|
pub previous_signature: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
pub fn chains(url: String, hash_chain: bool) -> DResult {
|
||||||
|
let url = format!("{}/chains", url);
|
||||||
|
let curl_cmd = vec![
|
||||||
|
"-X".to_string(),
|
||||||
|
"GET".to_string(),
|
||||||
|
"-H".to_string(),
|
||||||
|
"Accept: application/json".to_string(),
|
||||||
|
url,
|
||||||
|
];
|
||||||
|
|
||||||
|
let response = curl_request(curl_cmd);
|
||||||
|
if response.error.len() > 0 {
|
||||||
|
return DResult {
|
||||||
|
stderr: response.error.to_string(),
|
||||||
|
stdout: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match String::from_utf8(response.clone().stdout) {
|
||||||
|
Ok(r) => {
|
||||||
|
let obj: Vec<String> = serde_json::from_str(&r).unwrap();
|
||||||
|
if !hash_chain {
|
||||||
|
DResult {
|
||||||
|
stdout: r,
|
||||||
|
stderr: "".to_owned(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DResult {
|
||||||
|
stdout: format!("{}", obj[0]),
|
||||||
|
stderr: "".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => DResult {
|
||||||
|
stdout: "".to_owned(),
|
||||||
|
stderr: e.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
pub fn info(url: String, chain_hash: String, pub_key: bool) -> DResult {
|
||||||
|
let url = format!("{}/{}/info", url, chain_hash);
|
||||||
|
let curl_cmd = vec![
|
||||||
|
"-X".to_string(),
|
||||||
|
"GET".to_string(),
|
||||||
|
"-H".to_string(),
|
||||||
|
"Accept: application/json".to_string(),
|
||||||
|
url,
|
||||||
|
];
|
||||||
|
|
||||||
|
let response = curl_request(curl_cmd);
|
||||||
|
if response.error.len() > 0 {
|
||||||
|
return DResult {
|
||||||
|
stderr: response.error.to_string(),
|
||||||
|
stdout: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match String::from_utf8(response.clone().stdout) {
|
||||||
|
Ok(r) => {
|
||||||
|
let obj: DInfo = serde_json::from_str(&r).unwrap();
|
||||||
|
if pub_key {
|
||||||
|
DResult {
|
||||||
|
stdout: obj.public_key,
|
||||||
|
stderr: "".to_owned(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DResult {
|
||||||
|
stdout: r,
|
||||||
|
stderr: "".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => DResult {
|
||||||
|
stdout: "".to_owned(),
|
||||||
|
stderr: e.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
pub fn randomness(url: String, chain_hash: String, round: String) -> DResult {
|
||||||
|
let mut uri: String;
|
||||||
|
if &round.to_lowercase() == "latest" {
|
||||||
|
uri = format!("{}/{}/public/latest", url, chain_hash);
|
||||||
|
} else {
|
||||||
|
let round = &round.parse::<u64>().unwrap();
|
||||||
|
uri = format!("{}/{}/public/{}", url, chain_hash, round);
|
||||||
|
}
|
||||||
|
|
||||||
|
let curl_cmd = vec![
|
||||||
|
"-X".to_string(),
|
||||||
|
"GET".to_string(),
|
||||||
|
"-H".to_string(),
|
||||||
|
"Accept: application/json".to_string(),
|
||||||
|
uri,
|
||||||
|
];
|
||||||
|
|
||||||
|
let response = curl_request(curl_cmd);
|
||||||
|
if response.error.len() > 0 {
|
||||||
|
return DResult {
|
||||||
|
stderr: response.error.to_string(),
|
||||||
|
stdout: "".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match String::from_utf8(response.clone().stdout) {
|
||||||
|
Ok(r) => DResult {
|
||||||
|
stdout: r,
|
||||||
|
stderr: "".to_owned(),
|
||||||
|
},
|
||||||
|
Err(e) => DResult {
|
||||||
|
stdout: "".to_owned(),
|
||||||
|
stderr: e.to_string(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
pub fn verify_bls(pk: String, round: u64, prev_signature: String, signature: String) -> DResult {
|
||||||
|
let hex_pk: [u8; 48] = hex::decode(&pk).unwrap().as_slice().try_into().unwrap();
|
||||||
|
let pk = g1_from_fixed(hex_pk).unwrap();
|
||||||
|
|
||||||
|
println!("about to match verify");
|
||||||
|
|
||||||
|
let hex_sig = hex::decode(signature).unwrap();
|
||||||
|
let hex_psig = hex::decode(prev_signature).unwrap();
|
||||||
|
|
||||||
|
match verify(&pk, round, &hex_psig, &hex_sig) {
|
||||||
|
Err(err) => DResult {
|
||||||
|
stderr: format!("Error during verification: {}", err),
|
||||||
|
stdout: "".to_string(),
|
||||||
|
},
|
||||||
|
Ok(valid) => {
|
||||||
|
println!("ok verify");
|
||||||
|
if valid {
|
||||||
|
println!("Verification succeeded");
|
||||||
|
let randomness = derive_randomness(&hex_sig);
|
||||||
|
println!("Randomness: {}", hex::encode(&randomness));
|
||||||
|
DResult {
|
||||||
|
stdout: hex::encode(&randomness),
|
||||||
|
stderr: "".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DResult {
|
||||||
|
stdout: "".to_string(),
|
||||||
|
stderr: format!("Verification failed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine]
|
||||||
|
#[link(wasm_import_module = "curl_adapter")]
|
||||||
|
extern "C" {
|
||||||
|
pub fn curl_request(cmd: Vec<String>) -> MountedBinaryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
// use super::*;
|
||||||
|
use super::Randomness;
|
||||||
|
use marine_rs_sdk_test::marine_test;
|
||||||
|
|
||||||
|
const URL: &'static str = "https://api.drand.sh";
|
||||||
|
|
||||||
|
#[marine_test(
|
||||||
|
config_path = "/Users/bebo/localdev/examples/aqua-examples/drand/services/configs/Config.toml",
|
||||||
|
modules_dir = "/Users/bebo/localdev/examples/aqua-examples/drand/services/artifacts/"
|
||||||
|
)]
|
||||||
|
fn test_chain(drand: marine_test_env::drand::ModuleInterface) {
|
||||||
|
let res = drand.chains(URL.to_string(), false);
|
||||||
|
assert_eq!(res.stderr.len(), 0);
|
||||||
|
|
||||||
|
let res = drand.chains(URL.to_string(), true);
|
||||||
|
assert_eq!(res.stderr.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine_test(
|
||||||
|
config_path = "/Users/bebo/localdev/examples/aqua-examples/drand/services/configs/Config.toml",
|
||||||
|
modules_dir = "/Users/bebo/localdev/examples/aqua-examples/drand/services/artifacts/"
|
||||||
|
)]
|
||||||
|
fn test_info(drand: marine_test_env::drand::ModuleInterface) {
|
||||||
|
let chain_hash =
|
||||||
|
"8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce".to_string();
|
||||||
|
let res = drand.info(URL.to_string(), chain_hash.clone(), false);
|
||||||
|
assert_eq!(res.stderr.len(), 0);
|
||||||
|
assert!(res.stdout.len() > 0);
|
||||||
|
|
||||||
|
let res = drand.info(URL.to_string(), chain_hash, true);
|
||||||
|
assert_eq!(res.stderr.len(), 0);
|
||||||
|
assert!(res.stdout.len() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine_test(
|
||||||
|
config_path = "/Users/bebo/localdev/examples/aqua-examples/drand/services/configs/Config.toml",
|
||||||
|
modules_dir = "/Users/bebo/localdev/examples/aqua-examples/drand/services/artifacts/"
|
||||||
|
)]
|
||||||
|
fn test_randomness(drand: marine_test_env::drand::ModuleInterface) {
|
||||||
|
let chain_hash =
|
||||||
|
"8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce".to_string();
|
||||||
|
let res = drand.randomness(URL.to_string(), chain_hash.clone(), "latest".to_owned());
|
||||||
|
assert_eq!(res.stderr.len(), 0);
|
||||||
|
assert!(res.stdout.len() > 0);
|
||||||
|
|
||||||
|
let rand_obj: Randomness = serde_json::from_str(&res.stdout).unwrap();
|
||||||
|
let prev_round = rand_obj.round - 1;
|
||||||
|
let res = drand.randomness(
|
||||||
|
URL.to_string(),
|
||||||
|
chain_hash.clone(),
|
||||||
|
format!("{}", prev_round),
|
||||||
|
);
|
||||||
|
assert_eq!(res.stderr.len(), 0);
|
||||||
|
assert!(res.stdout.len() > 0);
|
||||||
|
|
||||||
|
let prev_rand_obj: Randomness = serde_json::from_str(&res.stdout).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(rand_obj.previous_signature, prev_rand_obj.signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[marine_test(
|
||||||
|
config_path = "/Users/bebo/localdev/examples/aqua-examples/drand/services/configs/Config.toml",
|
||||||
|
modules_dir = "/Users/bebo/localdev/examples/aqua-examples/drand/services/artifacts/"
|
||||||
|
)]
|
||||||
|
fn test_verify(drand: marine_test_env::drand::ModuleInterface) {
|
||||||
|
// get chain hash
|
||||||
|
let chain_hash = drand.chains(URL.to_string(), true).stdout;
|
||||||
|
println!("verify-chain hash: {:?}", chain_hash);
|
||||||
|
|
||||||
|
// get public key for chain
|
||||||
|
let pk = drand.info(URL.to_string(), chain_hash.clone(), true).stdout;
|
||||||
|
println!("verify-pk: {:?}", chain_hash);
|
||||||
|
|
||||||
|
// get latest randomness
|
||||||
|
let res = drand
|
||||||
|
.randomness(URL.to_string(), chain_hash.clone(), "latest".to_owned())
|
||||||
|
.stdout;
|
||||||
|
println!("verify randomness: {:?}", res);
|
||||||
|
|
||||||
|
let randomness: Randomness = serde_json::from_str(&res).unwrap();
|
||||||
|
println!("verify randomness: {:?}", randomness);
|
||||||
|
|
||||||
|
// verify randomness
|
||||||
|
let res = drand.verify_bls(
|
||||||
|
pk,
|
||||||
|
randomness.round,
|
||||||
|
randomness.previous_signature,
|
||||||
|
randomness.signature,
|
||||||
|
);
|
||||||
|
println!("verify: {:?}", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn doodle() {
|
||||||
|
use hex_literal::hex;
|
||||||
|
const PK_LEO_MAINNET: [u8; 48] = hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31");
|
||||||
|
let pk = "868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31";
|
||||||
|
println!("hex : {:?}", hex::decode(pk).unwrap());
|
||||||
|
|
||||||
|
// let h: [u8; 48] = hex!("868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31");
|
||||||
|
println!("hex!: {:?}", PK_LEO_MAINNET);
|
||||||
|
}
|
||||||
|
}
|
233
aqua-examples/drand/services/drand/src/points.rs
Normal file
233
aqua-examples/drand/services/drand/src/points.rs
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
// 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<GroupDecodingError> 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<G1Affine, InvalidPoint> {
|
||||||
|
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<G1Affine, InvalidPoint> {
|
||||||
|
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<G2Affine, InvalidPoint> {
|
||||||
|
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<G2Affine, InvalidPoint> {
|
||||||
|
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<G1Affine, InvalidPoint> {
|
||||||
|
// 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<G1Affine, InvalidPoint> {
|
||||||
|
// 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<G2Affine, InvalidPoint> {
|
||||||
|
// 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<G2Affine, InvalidPoint> {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
29
aqua-examples/drand/services/drand/src/randomness.rs
Normal file
29
aqua-examples/drand/services/drand/src/randomness.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
155
aqua-examples/drand/services/drand/src/verify.rs
Normal file
155
aqua-examples/drand/services/drand/src/verify.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// 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<bool, VerificationError> {
|
||||||
|
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<bool, VerificationError> {
|
||||||
|
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<u8> {
|
||||||
|
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 = <G2 as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user