Skip to content

Commit

Permalink
feat: support drand quicket
Browse files Browse the repository at this point in the history
  • Loading branch information
hanabi1224 committed Jan 15, 2024
1 parent 13ba087 commit c1d9ed6
Show file tree
Hide file tree
Showing 18 changed files with 636 additions and 143 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ bls-signatures = { version = "0.15", default-features = false, features = [
"multicore",
"blst-portable",
] } # prevent SIGINT on CI runners by using portable assembly
blst = { version = "0.3", features = ["portable"] }
blstrs = { version = "0.7", features = ["portable"] }
boa_engine = "0.17.0"
boa_interner = "0.17.0"
boa_parser = "0.17.0"
Expand Down Expand Up @@ -79,6 +81,7 @@ fvm_shared3 = { package = "fvm_shared", version = "~3.6", features = ["testing",
fvm_shared4 = { package = "fvm_shared", version = "~4.0.0", features = ["testing", "proofs"] }
gethostname = "0.4"
git-version = "0.3"
group = "0.13"
hex = { version = "0.4", features = ["serde"] }
http = "1.0"
http0 = { package = "http", version = "0.2" }
Expand Down
19 changes: 11 additions & 8 deletions src/beacon/beacon_entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,32 @@ use serde_tuple::{self, Deserialize_tuple, Serialize_tuple};
/// This beacon entry is stored on chain in the block header.
#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
#[derive(
Clone, Debug, Default, Eq, PartialEq, Deserialize_tuple, Serialize_tuple, Hash, Ord, PartialOrd,
Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize_tuple, Deserialize_tuple,
)]
pub struct BeaconEntry {
round: u64,
#[serde(with = "serde_byte_array")]
data: Vec<u8>,
signature: Vec<u8>,
}

impl BeaconEntry {
pub fn new(round: u64, data: Vec<u8>) -> Self {
Self { round, data }
Self {
round,
signature: data,
}
}
/// Returns the current round number.
pub fn round(&self) -> u64 {
self.round
}
/// The signature of message `H(prev_round, prev_round.data, round)`.
pub fn data(&self) -> &[u8] {
&self.data
/// The signature of message `H(prev_round, prev_round.signature, round)`.
pub fn signature(&self) -> &[u8] {
&self.signature
}

pub fn into_parts(self) -> (u64, Vec<u8>) {
let Self { round, data } = self;
(round, data)
let Self { round, signature } = self;
(round, signature)
}
}
179 changes: 97 additions & 82 deletions src/beacon/drand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@
use std::borrow::Cow;
use std::time::Duration;

use super::beacon_entries::BeaconEntry;
use super::{
beacon_entries::BeaconEntry,
signatures::{
verify_messages_mainnet, verify_messages_quicknet, PublicKeyOnG1, PublicKeyOnG2,
SignatureOnG1, SignatureOnG2,
},
};
use crate::shim::clock::ChainEpoch;
use crate::shim::version::NetworkVersion;
use crate::utils::net::global_http_client;
use ahash::HashMap;
use anyhow::Context as _;
use async_trait::async_trait;
use bls_signatures::{PublicKey, Serialize, Signature};
use bls_signatures::Serialize as _;
use byteorder::{BigEndian, ByteOrder};
use itertools::Itertools;
use parking_lot::RwLock;
use serde::{Deserialize as SerdeDeserialize, Serialize as SerdeSerialize};
use sha2::Digest;
Expand All @@ -21,29 +28,21 @@ use sha2::Digest;
/// `LOTUS_IGNORE_DRAND`
pub const IGNORE_DRAND_VAR: &str = "IGNORE_DRAND";

/// Coefficients of the publicly available `Drand` keys.
/// This is shared by all participants on the `Drand` network.
#[derive(Clone, Debug, SerdeSerialize, SerdeDeserialize)]
pub struct DrandPublic {
/// Public key used to verify beacon entries.
pub coefficient: Vec<u8>,
}

impl DrandPublic {
/// Returns the public key for the `Drand` beacon.
pub fn key(&self) -> Result<PublicKey, bls_signatures::Error> {
PublicKey::from_bytes(&self.coefficient)
}
}

/// Type of the `drand` network. In general only `mainnet` and its chain
/// information should be considered stable.
#[derive(PartialEq, Eq, Clone)]
/// Type of the `drand` network. `mainnet` is chained and `quicknet` is unchained.
/// For the details, see <https://github.com/filecoin-project/FIPs/blob/1bd887028ac1b50b6f2f94913e07ede73583da5b/FIPS/fip-0063.md#specification>
#[derive(PartialEq, Eq, Copy, Clone)]
pub enum DrandNetwork {
Mainnet,
Quicknet,
Incentinet,
}

impl DrandNetwork {
pub fn is_unchained(&self) -> bool {
matches!(self, Self::Quicknet)
}
}

#[derive(Clone)]
/// Configuration used when initializing a `Drand` beacon.
pub struct DrandConfig<'a> {
Expand Down Expand Up @@ -137,12 +136,12 @@ pub trait Beacon
where
Self: Send + Sync + 'static,
{
/// Verify a new beacon entry against the most recent one before it.
fn verify_entry(&self, curr: &BeaconEntry, prev: &BeaconEntry) -> Result<bool, anyhow::Error>;
/// Verify beacon entries that are sorted by round.
fn verify_entries(&self, entries: &[BeaconEntry]) -> Result<bool, anyhow::Error>;

/// Returns a `BeaconEntry` given a round. It fetches the `BeaconEntry` from a `Drand` node over [`gRPC`](https://grpc.io/)
/// In the future, we will cache values, and support streaming.
async fn entry(&self, round: u64) -> Result<BeaconEntry, anyhow::Error>;
async fn entry(&self, round: u64) -> anyhow::Result<BeaconEntry>;

/// Returns the most recent beacon round for the given Filecoin chain epoch.
fn max_beacon_round_for_epoch(
Expand All @@ -154,8 +153,8 @@ where

#[async_trait]
impl Beacon for Box<dyn Beacon> {
fn verify_entry(&self, curr: &BeaconEntry, prev: &BeaconEntry) -> Result<bool, anyhow::Error> {
self.as_ref().verify_entry(curr, prev)
fn verify_entries(&self, entries: &[BeaconEntry]) -> Result<bool, anyhow::Error> {
self.as_ref().verify_entries(entries)
}

async fn entry(&self, round: u64) -> Result<BeaconEntry, anyhow::Error> {
Expand Down Expand Up @@ -192,16 +191,17 @@ pub struct BeaconEntryJson {
round: u64,
randomness: String,
signature: String,
previous_signature: String,
previous_signature: Option<String>,
}

/// `Drand` randomness beacon that can be used to generate randomness for the
/// Filecoin chain. Primary use is to satisfy the [Beacon] trait.
pub struct DrandBeacon {
server: &'static str,
hash: String,
network: DrandNetwork,

pub_key: DrandPublic,
public_key: Vec<u8>,
/// Interval between beacons, in seconds.
interval: u64,
drand_gen_time: u64,
Expand All @@ -216,41 +216,14 @@ impl DrandBeacon {
/// Construct a new `DrandBeacon`.
pub fn new(genesis_ts: u64, interval: u64, config: &DrandConfig<'_>) -> Self {
assert_ne!(genesis_ts, 0, "Genesis timestamp cannot be 0");

let chain_info = &config.chain_info;

if cfg!(debug_assertions) && config.network_type == DrandNetwork::Mainnet {
let info_url = format!("{}/{}/info", config.server, config.chain_info.hash);
let remote_chain_info = std::thread::spawn(move || {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(async {
let client = global_http_client();
let remote_chain_info: ChainInfo = client
.get(info_url)
.send()
.await?
.error_for_status()?
.json()
.await
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok::<ChainInfo, anyhow::Error>(remote_chain_info)
})
})
.join()
.expect("thread panicked")
.expect("failed to fetch remote drand chain info");
debug_assert!(&remote_chain_info == chain_info);
}

Self {
server: config.server,
hash: config.chain_info.hash.to_string(),
pub_key: DrandPublic {
coefficient: hex::decode(chain_info.public_key.as_ref())
.expect("invalid static encoding of drand hex public key"),
},
interval: chain_info.period as u64,
drand_gen_time: chain_info.genesis_time as u64,
network: config.network_type,
public_key: hex::decode(config.chain_info.public_key.as_ref())
.expect("invalid static encoding of drand hex public key"),
interval: config.chain_info.period as u64,
drand_gen_time: config.chain_info.genesis_time as u64,
fil_round_time: interval,
fil_gen_time: genesis_ts,
local_cache: Default::default(),
Expand All @@ -260,31 +233,68 @@ impl DrandBeacon {

#[async_trait]
impl Beacon for DrandBeacon {
fn verify_entry(&self, curr: &BeaconEntry, prev: &BeaconEntry) -> Result<bool, anyhow::Error> {
// TODO(forest): https://github.com/ChainSafe/forest/issues/3572
if prev.round() == 0 {
return Ok(true);
}
fn verify_entries(&self, entries: &[BeaconEntry]) -> Result<bool, anyhow::Error> {
let mut it = entries.iter();
if self.network.is_unchained() {
let mut messages = vec![];
let mut signatures = vec![];
let pk = PublicKeyOnG2::from_bytes(&self.public_key)?;
for curr in it {
// Hash the messages (H(curr_round))
let message = {
let mut round_bytes = [0; std::mem::size_of::<u64>()];
BigEndian::write_u64(&mut round_bytes, curr.round());
sha2::Sha256::digest(round_bytes)
};
messages.push(message);
signatures.push(SignatureOnG1::from_bytes(curr.signature())?);
}

// Hash the messages (H(prev sig | curr_round))
let digest = {
let mut round_bytes = [0; std::mem::size_of::<u64>()];
BigEndian::write_u64(&mut round_bytes, curr.round());
let mut hasher = sha2::Sha256::default();
hasher.update(prev.data());
hasher.update(round_bytes);
hasher.finalize()
};
// Signature
let sig = Signature::from_bytes(curr.data())?;
let sig_match = bls_signatures::verify_messages(&sig, &[&digest], &[self.pub_key.key()?]);

// Cache the result
let contains_curr = self.local_cache.read().contains_key(&curr.round());
if sig_match && !contains_curr {
self.local_cache.write().insert(curr.round(), curr.clone());
Ok(verify_messages_quicknet(
&pk,
messages
.iter()
.map(|a| a.as_slice())
.collect_vec()
.as_slice(),
signatures.iter().collect_vec().as_slice(),
))
} else {
let mut messages = vec![];
let mut signatures = vec![];
if let Some(mut prev) = it.next() {
let pk = PublicKeyOnG1::from_bytes(&self.public_key)?;
for curr in it {
if prev.round() > 0 {
// Hash the messages (H(prev sig | curr_round))
let message = {
let mut round_bytes = [0; std::mem::size_of::<u64>()];
BigEndian::write_u64(&mut round_bytes, curr.round());
let mut hasher = sha2::Sha256::default();
hasher.update(prev.signature());
hasher.update(round_bytes);
hasher.finalize()
};
messages.push(message);
signatures.push(SignatureOnG2::from_bytes(curr.signature())?);
}

prev = curr;
}

Ok(verify_messages_mainnet(
&pk,
messages
.iter()
.map(|a| a.as_slice())
.collect_vec()
.as_slice(),
&signatures,
))
} else {
Ok(false)
}
}
Ok(sig_match)
}

async fn entry(&self, round: u64) -> anyhow::Result<BeaconEntry> {
Expand Down Expand Up @@ -330,6 +340,11 @@ impl Beacon for DrandBeacon {
if latest_ts < self.drand_gen_time {
return 1;
}

// (hanabi1224) Should it be like:
// let from_genesis = latest_ts - self.drand_gen_time + self.fil_round_time;
// from_genesis / self.interval

let from_genesis = latest_ts - self.drand_gen_time;
// we take the time from genesis divided by the periods in seconds, that
// gives us the number of periods since genesis. We also add +1 because
Expand Down
17 changes: 14 additions & 3 deletions src/beacon/mock_beacon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,20 @@ impl MockBeacon {

#[async_trait]
impl Beacon for MockBeacon {
fn verify_entry(&self, curr: &BeaconEntry, prev: &BeaconEntry) -> Result<bool, anyhow::Error> {
let oe = Self::entry_for_index(prev.round());
Ok(oe.data() == curr.data())
fn verify_entries(&self, entries: &[BeaconEntry]) -> Result<bool, anyhow::Error> {
let mut it = entries.iter();
if let Some(mut prev) = it.next() {
for curr in it {
let oe = Self::entry_for_index(prev.round());
if oe.signature() != curr.signature() {
return Ok(false);
}

prev = curr;
}
}

Ok(true)
}

async fn entry(&self, round: u64) -> Result<BeaconEntry, anyhow::Error> {
Expand Down
7 changes: 4 additions & 3 deletions src/beacon/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

pub mod beacon_entries;
mod drand;
#[cfg(test)]
pub mod mock_beacon;

pub mod signatures;
pub use beacon_entries::*;
pub use drand::*;

#[cfg(test)]
pub mod mock_beacon;
#[cfg(test)]
mod tests {
mod drand;
Expand Down
Loading

0 comments on commit c1d9ed6

Please sign in to comment.