Skip to content

Commit

Permalink
support for unchained scheme
Browse files Browse the repository at this point in the history
- fixed a loooot of deserialisation errors for data model
- add actual verification to the chained scheme
- added some more tests and made the existing ones actually check things >.>
- extracted some BLS logic into its own file
  • Loading branch information
CluEleSsUK committed Dec 31, 2022
1 parent 62b9741 commit 078ca08
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 180 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
hex = "0.4.3"
hex = { version = "0.4.3", features = ["serde"] }
url = "2.3.1"
reqwest = { version = "0.11", features = ["blocking"] }
json = "0.12.4"
bls-signatures = "0.13.0"
thiserror = "1.0.38"
thiserror = "1.0.38"
sha256 = "1.1.1"
serde = { version = "1.0.151", features = ["derive"] }
serde_json = "1.0.91"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# 🦀 drand-client-rs
# 🎲 🦀 drand-client-rs

A simple drand client implementation written in rust

## Roadmap
- [x] http transport
- [x] json parser
- [x] chained scheme
- [ ] unchained scheme
- [x] unchained scheme
- [ ] unchained scheme with G1 and G2 swapped
- [ ] protobuf parser
- [ ] libp2p transport
Expand Down
30 changes: 30 additions & 0 deletions src/bls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use bls_signatures::{hash, PublicKey, Serialize, Signature, verify};
use crate::chain_info::ChainInfo;
use crate::SchemeError;

pub trait BlsVerifiable {
fn signature(&self) -> &Vec<u8>;
fn to_message(&self) -> Result<Vec<u8>, SchemeError>;
}

pub(crate) fn bls_verify<B: BlsVerifiable>(info: &ChainInfo, beacon: B) -> Result<B, SchemeError> {
let public_key = PublicKey::from_bytes(info.public_key.as_slice())
.map_err(|_| SchemeError::InvalidChainInfo)?;

let signature = Signature::from_bytes(&beacon.signature().as_slice())
.map_err(|_| SchemeError::InvalidBeacon)?;

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)
)?;


let point_on_curve = hash(bls_message_bytes.as_slice());
if !verify(&signature, &[point_on_curve], &[public_key]) {
return Err(SchemeError::InvalidBeacon);
}

return Ok(beacon);
}
23 changes: 23 additions & 0 deletions src/chain_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use serde::Deserialize;

#[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct ChainInfo {
#[serde(alias = "schemeID")]
pub scheme_id: String,
#[serde(with = "hex")]
pub public_key: Vec<u8>,
#[serde(with = "hex", alias = "hash")]
pub chain_hash: Vec<u8>,
#[serde(with = "hex", alias = "groupHash")]
pub group_hash: Vec<u8>,
pub genesis_time: u64,
#[serde(alias = "period")]
pub period_seconds: usize,
pub metadata: ChainInfoMetadata,
}

#[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct ChainInfoMetadata {
#[serde(alias = "beaconID")]
pub beacon_id: String,
}
119 changes: 29 additions & 90 deletions src/chained.rs
Original file line number Diff line number Diff line change
@@ -1,113 +1,52 @@
use bls_signatures::{PublicKey, Serialize, Signature, verify};
use json::JsonValue;
use crate::{ChainInfo, Scheme, SchemeError};
use thiserror::Error;

pub(crate) struct ChainedBeacon {
use std::io::{Write};
use crate::{bls, Scheme, SchemeError};
use crate::chain_info::ChainInfo;
use serde::Deserialize;
use crate::bls::BlsVerifiable;

#[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct ChainedBeacon {
#[serde(alias = "round")]
pub round_number: u64,
#[serde(with = "hex")]
pub randomness: Vec<u8>,
#[serde(with = "hex")]
pub signature: Vec<u8>,
#[serde(with = "hex")]
pub previous_signature: Vec<u8>,
}


#[derive(Error, Debug)]
pub enum JsonParseError {
#[error("unknown error")]
UnknownError,
#[error("empty payload")]
EmptyPayload,
#[error("could not parse")]
CouldNotParse,
#[error("invalid json")]
InvalidJson,
#[error("invalid type")]
InvalidType { key: String },
#[error("could not parse value")]
CouldNotParseValue { key: String },
#[error("unknown key")]
UnknownKey { key: String },
}

impl TryFrom<JsonValue> for ChainedBeacon {
type Error = JsonParseError;

fn try_from(value: JsonValue) -> Result<Self, Self::Error> {
if !value.is_object() {
return Err(JsonParseError::CouldNotParse);
}

let mut out = ChainedBeacon {
round_number: 0,
randomness: vec![],
signature: vec![],
previous_signature: vec![],
};

for (key, value) in value.entries() {
match key {
"round" => {
if !value.is_number() {
return Err(JsonParseError::CouldNotParseValue { key: key.to_string() });
}
if let Some(round_number) = value.as_u64() {
out.round_number = round_number
} else {
return Err(JsonParseError::CouldNotParseValue { key: key.to_string() });
}
}
"randomness" => {
out.randomness = parse_bytes(key, value)?
}
"signature" => {
out.signature = parse_bytes(key, value)?
}
"previous_signature" => {
out.previous_signature = parse_bytes(key, value)?
}
_ => return Err(JsonParseError::UnknownKey { key: key.to_string() })
}
}

return Ok(out);
}
}

fn parse_bytes<'a>(key: &str, value: &JsonValue) -> Result<Vec<u8>, JsonParseError> {
if !value.is_string() {
return Err(JsonParseError::InvalidType { key: key.to_string() });
}
return match value.as_str() {
None =>
Err(JsonParseError::CouldNotParseValue { key: key.to_string() }),
Some(hex_str) =>
hex::decode(hex_str)
.map_err(|_| JsonParseError::CouldNotParseValue { key: key.to_string() })
};
}

pub struct ChainedScheme {}

impl Scheme<ChainedBeacon> for ChainedScheme {
fn supports(&self, scheme_id: &str) -> bool {
return scheme_id.eq_ignore_ascii_case("bls-pedersen-chained");
return scheme_id.eq_ignore_ascii_case("pedersen-bls-chained");
}

fn verify(&self, info: &ChainInfo, beacon: ChainedBeacon) -> Result<ChainedBeacon, SchemeError> {
if !self.supports(&info.scheme_id) {
return Err(SchemeError::InvalidScheme);
}

let public_key = PublicKey::from_bytes(info.public_key.as_slice())
.map_err(|_| SchemeError::InvalidChainInfo)?;
return bls::bls_verify(info, beacon);
}
}

impl BlsVerifiable for ChainedBeacon {
fn signature(&self) -> &Vec<u8> {
&self.signature
}

let signature = Signature::from_bytes(&beacon.signature.as_slice())
.map_err(|_| SchemeError::InvalidBeacon)?;
fn to_message(&self) -> Result<Vec<u8>, SchemeError> {
let mut bytes: Vec<u8> = vec![];

if !verify(&signature, &[], &[public_key]) {
if bytes.write_all(self.previous_signature.as_slice()).is_err() {
return Err(SchemeError::InvalidBeacon);
}
if bytes.write_all(&self.round_number.to_be_bytes()).is_err() {
return Err(SchemeError::InvalidBeacon);
};

return Ok(beacon);
return Ok(bytes);
}
}
}
33 changes: 33 additions & 0 deletions src/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use reqwest::blocking::Client;
use reqwest::StatusCode;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum HttpError {
#[error("not found")]
NotFound,
#[error("unexpected")]
Unexpected,
}

pub struct HttpTransport {
pub client: Client,
}

impl HttpTransport {
pub fn fetch<'a>(&self, url: &str) -> Result<String, HttpError> {
let res = self.client.get(url)
.send()
.map_err(|_| HttpError::Unexpected)?;

return match res.status() {
StatusCode::OK => res.text()
.map_err(|_| HttpError::Unexpected),

StatusCode::NOT_FOUND =>
Err(HttpError::NotFound),

_ => Err(HttpError::Unexpected),
};
}
}
Loading

0 comments on commit 078ca08

Please sign in to comment.