diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..38757ae --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,20 @@ +name: build and test +on: + pull_request: + branches: [ master ] + push: + branches: [ master ] + +jobs: + build_and_test: + name: Rust project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - run: cargo fmt -- --check + - run: cargo clippy -- -Dwarnings + - run: cargo test -- --nocapture + - run: cargo build --release --all-features \ No newline at end of file diff --git a/README.md b/README.md index afb7539..6015f66 100644 --- a/README.md +++ b/README.md @@ -13,5 +13,5 @@ A simple drand client implementation written in rust - [ ] nicer error messages - [ ] comprehensive testing - [ ] rustdoc -- [ ] github actions for testing +- [x] github actions for testing - [ ] examples diff --git a/src/bls.rs b/src/bls.rs index 6bcfe78..49ef78f 100644 --- a/src/bls.rs +++ b/src/bls.rs @@ -1,6 +1,6 @@ -use bls_signatures::{hash, PublicKey, Serialize, Signature, verify}; use crate::chain_info::ChainInfo; use crate::SchemeError; +use bls_signatures::{hash, verify, PublicKey, Serialize, Signature}; pub trait BlsVerifiable { fn signature(&self) -> &Vec; @@ -11,20 +11,18 @@ pub(crate) fn bls_verify(info: &ChainInfo, beacon: B) -> Resul let public_key = PublicKey::from_bytes(info.public_key.as_slice()) .map_err(|_| SchemeError::InvalidChainInfo)?; - let signature = Signature::from_bytes(&beacon.signature().as_slice()) + let signature = Signature::from_bytes(beacon.signature().as_slice()) .map_err(|_| SchemeError::InvalidBeacon)?; - let bls_message_bytes = beacon.to_message() + let bls_message_bytes = beacon + .to_message() .map(|bytes| sha256::digest(bytes.as_slice())) - .and_then(|hex_str| hex::decode(hex_str) - .map_err(|_| SchemeError::InvalidBeacon) - )?; - + .and_then(|hex_str| hex::decode(hex_str).map_err(|_| SchemeError::InvalidBeacon))?; let point_on_curve = hash(bls_message_bytes.as_slice()); if !verify(&signature, &[point_on_curve], &[public_key]) { - return Err(SchemeError::InvalidBeacon); + Err(SchemeError::InvalidBeacon) + } else { + Ok(beacon) } - - return Ok(beacon); } diff --git a/src/chain_info.rs b/src/chain_info.rs index 4a85c33..4b4e572 100644 --- a/src/chain_info.rs +++ b/src/chain_info.rs @@ -20,4 +20,4 @@ pub struct ChainInfo { pub struct ChainInfoMetadata { #[serde(alias = "beaconID")] pub beacon_id: String, -} \ No newline at end of file +} diff --git a/src/chained.rs b/src/chained.rs index ff47335..30266b5 100644 --- a/src/chained.rs +++ b/src/chained.rs @@ -1,8 +1,8 @@ -use std::io::{Write}; -use crate::{bls, Scheme, SchemeError}; +use crate::bls::BlsVerifiable; use crate::chain_info::ChainInfo; +use crate::{bls, Scheme, SchemeError}; use serde::Deserialize; -use crate::bls::BlsVerifiable; +use std::io::Write; #[derive(Deserialize, Debug, PartialEq, Clone)] pub struct ChainedBeacon { @@ -20,15 +20,19 @@ pub struct ChainedScheme {} impl Scheme for ChainedScheme { fn supports(&self, scheme_id: &str) -> bool { - return scheme_id.eq_ignore_ascii_case("pedersen-bls-chained"); + scheme_id.eq_ignore_ascii_case("pedersen-bls-chained") } - fn verify(&self, info: &ChainInfo, beacon: ChainedBeacon) -> Result { + fn verify( + &self, + info: &ChainInfo, + beacon: ChainedBeacon, + ) -> Result { if !self.supports(&info.scheme_id) { - return Err(SchemeError::InvalidScheme); + Err(SchemeError::InvalidScheme) + } else { + bls::bls_verify(info, beacon) } - - return bls::bls_verify(info, beacon); } } @@ -44,9 +48,9 @@ impl BlsVerifiable for ChainedBeacon { return Err(SchemeError::InvalidBeacon); } if bytes.write_all(&self.round_number.to_be_bytes()).is_err() { - return Err(SchemeError::InvalidBeacon); - }; - - return Ok(bytes); + Err(SchemeError::InvalidBeacon) + } else { + Ok(bytes) + } } } diff --git a/src/http.rs b/src/http.rs index d1f5e0d..f58e57e 100644 --- a/src/http.rs +++ b/src/http.rs @@ -15,19 +15,19 @@ pub struct HttpTransport { } impl HttpTransport { - pub fn fetch<'a>(&self, url: &str) -> Result { - let res = self.client.get(url) + pub fn fetch(&self, url: &str) -> Result { + let res = self + .client + .get(url) .send() .map_err(|_| HttpError::Unexpected)?; - return match res.status() { - StatusCode::OK => res.text() - .map_err(|_| HttpError::Unexpected), + match res.status() { + StatusCode::OK => res.text().map_err(|_| HttpError::Unexpected), - StatusCode::NOT_FOUND => - Err(HttpError::NotFound), + StatusCode::NOT_FOUND => Err(HttpError::NotFound), _ => Err(HttpError::Unexpected), - }; + } } } diff --git a/src/lib.rs b/src/lib.rs index 6581e1b..4dcdbf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,36 +1,41 @@ extern crate core; +mod bls; +mod chain_info; mod chained; -mod unchained; mod http; -mod chain_info; -mod bls; +mod unchained; -use reqwest::blocking::Client; -use serde::de::DeserializeOwned; -use thiserror::Error; use crate::chain_info::ChainInfo; use crate::chained::{ChainedBeacon, ChainedScheme}; -use crate::DrandClientError::{InvalidChainInfo, InvalidRound}; use crate::http::HttpTransport; use crate::unchained::{UnchainedBeacon, UnchainedScheme}; +use crate::DrandClientError::{InvalidChainInfo, InvalidRound}; +use reqwest::blocking::Client; +use serde::de::DeserializeOwned; +use thiserror::Error; -struct DrandClient<'a, B> { +pub struct DrandClient<'a, B> { scheme: &'a dyn Scheme, transport: HttpTransport, base_url: &'a str, chain_info: ChainInfo, } -fn new_chained_client(base_url: &str) -> Result, DrandClientError> { +pub fn new_chained_client(base_url: &str) -> Result, DrandClientError> { return new_client(&ChainedScheme {}, base_url); } -fn new_unchained_client(base_url: &str) -> Result, DrandClientError> { +pub fn new_unchained_client( + base_url: &str, +) -> Result, DrandClientError> { return new_client(&UnchainedScheme {}, base_url); } -fn new_client<'a, S: Scheme, B>(scheme: &'a S, base_url: &'a str) -> Result, DrandClientError> { +pub fn new_client<'a, S: Scheme, B>( + scheme: &'a S, + base_url: &'a str, +) -> Result, DrandClientError> { let http_transport = HttpTransport { client: Client::new(), }; @@ -41,11 +46,12 @@ fn new_client<'a, S: Scheme, B>(scheme: &'a S, base_url: &'a str) -> Result Result { +pub fn fetch_chain_info( + transport: &HttpTransport, + base_url: &str, +) -> Result { let url = format!("{}/info", base_url); - return match transport.fetch(&url) { + match transport.fetch(&url) { Err(_) => Err(DrandClientError::NotResponding), - Ok(body) => serde_json::from_str(&body) - .map_err(|_| InvalidChainInfo) - }; + Ok(body) => serde_json::from_str(&body).map_err(|_| InvalidChainInfo), + } } -impl<'a, B> DrandClient<'a, B> where B: DeserializeOwned { - fn latest_randomness(&self) -> Result { - return self.fetch_beacon_tag("latest"); +impl<'a, B> DrandClient<'a, B> +where + B: DeserializeOwned, +{ + pub fn latest_randomness(&self) -> Result { + self.fetch_beacon_tag("latest") } - fn randomness(&self, round_number: u64) -> Result { + pub fn randomness(&self, round_number: u64) -> Result { if round_number == 0 { return Err(InvalidRound); } - return self.fetch_beacon_tag(&format!("{}", round_number)); + self.fetch_beacon_tag(&format!("{}", round_number)) } fn fetch_beacon_tag(&self, tag: &str) -> Result { let url = format!("{}/public/{}", self.base_url, tag); - return match self.transport.fetch(&url) { - Err(_) => - Err(DrandClientError::NotResponding), + match self.transport.fetch(&url) { + Err(_) => Err(DrandClientError::NotResponding), Ok(body) => match serde_json::from_str(&body) { - Ok(json) => self.scheme.verify(&self.chain_info, json) + Ok(json) => self + .scheme + .verify(&self.chain_info, json) .map_err(|_| DrandClientError::InvalidBeacon), - Err(e) => { - println!("{:?}", e); - Err(DrandClientError::InvalidBeacon) - } - } - }; + Err(_) => Err(DrandClientError::InvalidBeacon), + }, + } } } @@ -105,15 +114,15 @@ pub enum SchemeError { InvalidChainInfo, } -trait Scheme { +pub trait Scheme { fn supports(&self, scheme_id: &str) -> bool; fn verify(&self, info: &ChainInfo, beacon: B) -> Result; } #[cfg(test)] mod test { - use crate::{DrandClientError, new_chained_client, new_unchained_client}; use crate::DrandClientError::InvalidRound; + use crate::{new_chained_client, new_unchained_client, DrandClientError}; #[test] fn request_chained_randomness_success() -> Result<(), DrandClientError> { @@ -160,4 +169,4 @@ mod test { assert_eq!(result.unwrap_err(), InvalidRound); return Ok(()); } -} \ No newline at end of file +} diff --git a/src/unchained.rs b/src/unchained.rs index 873650c..3fd5207 100644 --- a/src/unchained.rs +++ b/src/unchained.rs @@ -1,8 +1,8 @@ -use std::io::Write; -use crate::{bls, Scheme, SchemeError}; +use crate::bls::BlsVerifiable; use crate::chain_info::ChainInfo; +use crate::{bls, Scheme, SchemeError}; use serde::Deserialize; -use crate::bls::BlsVerifiable; +use std::io::Write; #[derive(Deserialize, Debug, PartialEq, Clone)] pub struct UnchainedBeacon { @@ -18,15 +18,19 @@ pub struct UnchainedScheme {} impl Scheme for UnchainedScheme { fn supports(&self, scheme_id: &str) -> bool { - return scheme_id.eq_ignore_ascii_case("pedersen-bls-unchained"); + scheme_id.eq_ignore_ascii_case("pedersen-bls-unchained") } - fn verify(&self, info: &ChainInfo, beacon: UnchainedBeacon) -> Result { + fn verify( + &self, + info: &ChainInfo, + beacon: UnchainedBeacon, + ) -> Result { if !self.supports(&info.scheme_id) { - return Err(SchemeError::InvalidScheme); + Err(SchemeError::InvalidScheme) + } else { + bls::bls_verify(info, beacon) } - - return bls::bls_verify(info, beacon); } } @@ -39,9 +43,9 @@ impl BlsVerifiable for UnchainedBeacon { let mut bytes: Vec = vec![]; if bytes.write_all(&self.round_number.to_be_bytes()).is_err() { - return Err(SchemeError::InvalidBeacon); - }; - - return Ok(bytes); + Err(SchemeError::InvalidBeacon) + } else { + Ok(bytes) + } } -} \ No newline at end of file +}