From 7db81b167f992c8fd96b4f02c4de774940ef7282 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 6 Jul 2018 17:54:07 +1000 Subject: [PATCH] Initial commit --- .gitignore | 3 + .gitmodules | 3 + Cargo.toml | 17 +++ README.md | 39 +++++++ parity | 1 + src/lib.rs | 2 + src/state/active_state.rs | 69 +++++++++++ src/state/aggregate_vote.rs | 37 ++++++ src/state/block.rs | 160 ++++++++++++++++++++++++++ src/state/crosslink_record.rs | 29 +++++ src/state/mod.rs | 11 ++ src/state/partial_crosslink_record.rs | 33 ++++++ src/state/recent_proposer_record.rs | 19 +++ src/state/validator_record.rs | 65 +++++++++++ src/utils/bls.rs | 13 +++ src/utils/mod.rs | 4 + src/utils/types.rs | 7 ++ tests/aggregate_vote.rs | 7 ++ 18 files changed, 519 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Cargo.toml create mode 100644 README.md create mode 160000 parity create mode 100644 src/lib.rs create mode 100644 src/state/active_state.rs create mode 100644 src/state/aggregate_vote.rs create mode 100644 src/state/block.rs create mode 100644 src/state/crosslink_record.rs create mode 100644 src/state/mod.rs create mode 100644 src/state/partial_crosslink_record.rs create mode 100644 src/state/recent_proposer_record.rs create mode 100644 src/state/validator_record.rs create mode 100644 src/utils/bls.rs create mode 100644 src/utils/mod.rs create mode 100644 src/utils/types.rs create mode 100644 tests/aggregate_vote.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..693699042 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..1761c1474 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "parity"] + path = parity + url = git@github.com:paritytech/parity.git diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..de3915531 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rust_beacon" +version = "0.1.0" +authors = ["Paul Hauner "] + +[dependencies] +ethereum-types = "" +rand = "" +bls = { git = "https://github.com/sigp/bls" } +rlp = { path = "parity/util/rlp" } + +[dependencies.pairing] +git = "https://github.com/mmaker/pairing" +branch = "feature/hashing" + +[patch.crates-io] +ring = { git = "https://github.com/paritytech/ring" } diff --git a/README.md b/README.md new file mode 100644 index 000000000..00a5e251f --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# rust_beacon_chain + +A *work-in-progress* implementation of the Ethereum beacon_chain in Rust. + +It is an implementation of [this +spec](https://notes.ethereum.org/SCIg8AH5SA-O4C1G1LYZHQ?view) and is also +largely based upon the +[ethereum/beacon_chain](https://github.com/ethereum/beacon_chain) repo. + +## Usage + +Presently this is just a bunch of data structures and some tests. + +``` +$ git clone --recurse-submodules +$ cd rust_beacon_chain +$ cargo test +``` + +_Note: don't forget to clone/pull with respect to submodules. Parity is +included as a submodule so we can use their handy RLP module without compiling +all the things._ + +## Contact + +This repo is presently authored by Paul Hauner (@paulhauner) as a Sigma Prime +project. + +Best place for discussion is probably the [ethereum/sharding +gitter](https://gitter.im/ethereum/sharding). + +## TODO: + +- [] Implement crystallized state. +- [] Implement state transition. +- [] Implement integration tests (some unit tests are implemented now). +- [] Implement RLP serialization across-the-board. +- [] Ensure bls library is legit (i.e., functioning and secure). +- [] Implement the things, optimise them & scale to 1000000000 nodes. diff --git a/parity b/parity new file mode 160000 index 000000000..aa67bd5d0 --- /dev/null +++ b/parity @@ -0,0 +1 @@ +Subproject commit aa67bd5d00e48bac71ab81a384ac2902757eebed diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..63c76f0c8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod state; +pub mod utils; diff --git a/src/state/active_state.rs b/src/state/active_state.rs new file mode 100644 index 000000000..81d2c0ac1 --- /dev/null +++ b/src/state/active_state.rs @@ -0,0 +1,69 @@ +use super::partial_crosslink_record::PartialCrosslinkRecord; +use super::recent_proposer_record::RecentPropserRecord; +use super::utils::types::*; + +pub struct ActiveState { + pub height: u64, + pub randao: Sha256Digest, + pub ffg_voter_bitfield: Bitfield, + pub recent_attesters: Vec, // TODO: should be u24 + pub partial_crosslinks: Vec, + pub total_skip_count: u64, + pub recent_proposers: Vec +} + +impl ActiveState { + pub fn new_for_height(height: u64) -> ActiveState { + ActiveState { + height: height, + randao: Sha256Digest::random(), + ffg_voter_bitfield: Vec::new(), + recent_attesters: Vec::new(), + partial_crosslinks: Vec::new(), + total_skip_count: 0, + recent_proposers: Vec::new() + } + } + + pub fn num_recent_proposers(&self) -> usize { + self.recent_proposers.len() + } + + pub fn num_recent_attesters(&self) -> usize { + self.recent_attesters.len() + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_for_height() { + let h = 1; + let a = ActiveState::new_for_height(h); + assert_eq!(a.height, h); + } + + #[test] + fn test_num_recent_proposers() { + let mut a = ActiveState::new_for_height(1); + for _ in 1..5 { + a.recent_proposers.push(RecentPropserRecord::new( + 1, + Sha256Digest::random(), + 2)); + } + assert_eq!(a.num_recent_proposers(), 4) + } + + #[test] + fn test_num_recent_attesters() { + let mut a = ActiveState::new_for_height(1); + for _ in 1..5 { + a.recent_attesters.push(1); + } + assert_eq!(a.num_recent_attesters(), 4) + } +} diff --git a/src/state/aggregate_vote.rs b/src/state/aggregate_vote.rs new file mode 100644 index 000000000..3c550e625 --- /dev/null +++ b/src/state/aggregate_vote.rs @@ -0,0 +1,37 @@ +use super::utils::types::*; +use super::utils::bls::AggregateSignature; + +pub struct AggregateVote { + pub shard_id: u16, + pub shard_block_hash: Sha256Digest, + pub notary_bitfield: Bitfield, + pub aggregate_sig: AggregateSignature, +} + +impl AggregateVote { + pub fn new_for_shard(shard_id: u16, + shard_block_hash: Sha256Digest) + -> AggregateVote { + AggregateVote { + shard_id: shard_id, + shard_block_hash: shard_block_hash, + notary_bitfield: Vec::new(), + aggregate_sig: AggregateSignature::new() + } + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_for_shard() { + let id = 1; + let hash = Sha256Digest::random(); + let v = AggregateVote::new_for_shard(id, hash); + assert_eq!(v.shard_id, id); + assert_eq!(v.shard_block_hash, hash); + } +} diff --git a/src/state/block.rs b/src/state/block.rs new file mode 100644 index 000000000..758a48262 --- /dev/null +++ b/src/state/block.rs @@ -0,0 +1,160 @@ +use super::utils::types::{ Sha256Digest, Bitfield }; +use super::utils::bls::{ Signature, AggregateSignature, Keypair, PublicKey }; +use super::aggregate_vote::AggregateVote; +use super::rlp::{ RlpStream, Encodable } ; + +pub struct Block { + pub parent_hash: Sha256Digest, + pub skip_count: u64, + pub randao_reveal: Sha256Digest, + pub attestation_bitfield: Bitfield, + pub attestation_aggregate_sig: AggregateSignature, + pub shard_aggregate_votes: Vec, + pub main_chain_ref: Sha256Digest, + pub state_hash: Sha256Digest, + pub sig: Option +} + +impl Block { + pub fn new(parent_hash: Sha256Digest, + randao_reveal: Sha256Digest, + main_chain_ref: Sha256Digest, + state_hash: Sha256Digest) -> Block { + Block { + parent_hash: parent_hash, + skip_count: 0, + randao_reveal: randao_reveal, + attestation_bitfield: Vec::new(), + attestation_aggregate_sig: AggregateSignature::new(), + shard_aggregate_votes: Vec::new(), + main_chain_ref: main_chain_ref, + state_hash: state_hash, + sig: None + } + } + + /* + * Take a Block and covert it into an array of u8 for BLS signing + * or verfication. The `sig` field is purposefully omitted. + */ + pub fn encode_to_signable_message(&self) -> [u8; 9140] { + // Using biggest avg. block size from v2 spec + let mut message: [u8; 9140] = [0; 9140]; + + // Create the RLP vector + let mut s = RlpStream::new(); + s.append(&self.parent_hash); + s.append(&self.skip_count); + s.append(&self.randao_reveal); + s.append(&self.attestation_bitfield); + // TODO: represent attestation_aggregate_sig + // TODO: represent shard_aggregate_votes + s.append(&self.main_chain_ref); + s.append(&self.state_hash); + let rlp_vec = s.out(); + + // Parse the RLP vector into an array compatible with the BLS signer + let len = rlp_vec.len(); + message[..len].copy_from_slice(&rlp_vec[..len]); + message + } + + /* + * Sign the block with the given keypair. + */ + pub fn sig_sign(&mut self, keypair: &Keypair) { + let message = self.encode_to_signable_message(); + self.sig = Some(keypair.sign(&message)); + } + + /* + * Verify a block signature given some keypair. + */ + pub fn sig_verify(&self, pub_key: &PublicKey) -> bool { + let message = self.encode_to_signable_message(); + match &self.sig { + None => false, + Some(sig) => { + pub_key.verify(&message, &sig) + }, + } + } +} + +impl Encodable for Block { + fn rlp_append(&self, s: &mut RlpStream) { + s.append(&self.parent_hash); + s.append(&self.skip_count); + s.append(&self.randao_reveal); + s.append(&self.attestation_bitfield); + // TODO: represent attestation_aggregate_sig + // TODO: represent shard_aggregate_votes + s.append(&self.main_chain_ref); + s.append(&self.state_hash); + // TODO: represent sig + } +} + + +#[cfg(test)] +mod tests { + extern crate rand; + + use super::*; + use self::rand::{ SeedableRng, XorShiftRng }; + + #[test] + fn test_new_for_parent_hash() { + let parent_hash = Sha256Digest::random(); + let randao_reveal = Sha256Digest::random(); + let main_chain_ref = Sha256Digest::random(); + let state_hash = Sha256Digest::random(); + let b = Block::new(parent_hash, + randao_reveal, + main_chain_ref, + state_hash); + assert_eq!(b.parent_hash, parent_hash); + assert_eq!(b.randao_reveal, randao_reveal); + assert_eq!(b.main_chain_ref, main_chain_ref); + assert_eq!(b.state_hash, state_hash); + } + + #[test] + fn test_signable_message_encoding() { + let parent_hash = Sha256Digest::from([0; 32]); + let randao_reveal = Sha256Digest::from([1; 32]); + let main_chain_ref = Sha256Digest::from([2; 32]); + let state_hash = Sha256Digest::from([3; 32]); + let mut b = Block::new(parent_hash, + randao_reveal, + main_chain_ref, + state_hash); + b.skip_count = 2; + let output = b.encode_to_signable_message(); + // TODO: test this better + assert_eq!(output[0], 160); + assert_eq!(output[1..21], [0; 20]); + } + + #[test] + fn test_sign_and_verify() { + let mut rng = XorShiftRng::from_seed([0xbc4f6d44, 0xd62f276c, 0xb963afd0, 0x5455863d]); + let alice_keypair = Keypair::generate(&mut rng); + let bob_keypair = Keypair::generate(&mut rng); + let mut b = Block::new(Sha256Digest::random(), + Sha256Digest::random(), + Sha256Digest::random(), + Sha256Digest::random()); + + // Both signatures fail before signing + assert_eq!(b.sig_verify(&alice_keypair.public), false); + assert_eq!(b.sig_verify(&bob_keypair.public), false); + + // Sign as Alice + b.sig_sign(&alice_keypair); + + // Alice signature passes, bobs fails + assert_eq!(b.sig_verify(&alice_keypair.public), true); + assert_eq!(b.sig_verify(&bob_keypair.public), false); + } +} diff --git a/src/state/crosslink_record.rs b/src/state/crosslink_record.rs new file mode 100644 index 000000000..6b19081ca --- /dev/null +++ b/src/state/crosslink_record.rs @@ -0,0 +1,29 @@ +use super::utils::types::Sha256Digest; + +pub struct CrosslinkRecord { + pub epoch: u64, + pub hash: Sha256Digest +} + +impl CrosslinkRecord { + pub fn new(epoch: u64, hash: Sha256Digest) -> CrosslinkRecord { + CrosslinkRecord { + epoch: epoch, + hash: hash + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let epoch = 1; + let hash = Sha256Digest::random(); + let c = CrosslinkRecord::new(epoch, hash); + assert_eq!(c.epoch, epoch); + assert_eq!(c.hash, hash); + } +} diff --git a/src/state/mod.rs b/src/state/mod.rs new file mode 100644 index 000000000..f8c3aafdd --- /dev/null +++ b/src/state/mod.rs @@ -0,0 +1,11 @@ +extern crate rlp; + +use super::utils; + +pub mod active_state; +pub mod aggregate_vote; +pub mod block; +pub mod crosslink_record; +pub mod partial_crosslink_record; +pub mod recent_proposer_record; +pub mod validator_record; diff --git a/src/state/partial_crosslink_record.rs b/src/state/partial_crosslink_record.rs new file mode 100644 index 000000000..966ca6a64 --- /dev/null +++ b/src/state/partial_crosslink_record.rs @@ -0,0 +1,33 @@ +use super::utils::types::{ Sha256Digest, Bitfield }; + +pub struct PartialCrosslinkRecord { + pub shard_id: u16, + pub shard_block_hash: Sha256Digest, + pub voter_bitfield: Bitfield +} + +impl PartialCrosslinkRecord { + pub fn new_for_shard(shard_id: u16, + shard_block_hash: Sha256Digest) -> PartialCrosslinkRecord { + PartialCrosslinkRecord { + shard_id: shard_id, + shard_block_hash: shard_block_hash, + voter_bitfield: Vec::new() + } + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_for_shard() { + let id = 1; + let hash = Sha256Digest::random(); + let p = PartialCrosslinkRecord::new_for_shard(id, hash); + assert_eq!(p.shard_id, id); + assert_eq!(p.shard_block_hash, hash); + } +} diff --git a/src/state/recent_proposer_record.rs b/src/state/recent_proposer_record.rs new file mode 100644 index 000000000..9f5077a1e --- /dev/null +++ b/src/state/recent_proposer_record.rs @@ -0,0 +1,19 @@ +use super::utils::types::*; + +pub struct RecentPropserRecord { + pub index: u32, // TODO: make u24 + pub randao_commitment: Sha256Digest, + pub balance_delta: u32, // TODO: make u24 +} + +impl RecentPropserRecord { + pub fn new(index: u32, + randao_commitment: Sha256Digest, + balance_delta: u32) -> RecentPropserRecord { + RecentPropserRecord { + index: index, + randao_commitment: randao_commitment, + balance_delta: balance_delta + } + } +} diff --git a/src/state/validator_record.rs b/src/state/validator_record.rs new file mode 100644 index 000000000..3b23127ed --- /dev/null +++ b/src/state/validator_record.rs @@ -0,0 +1,65 @@ +use super::utils::types::{ Sha256Digest, Address }; +use super::utils::bls::PublicKey; + +pub struct ValidatorRecord { + pub pubkey: PublicKey, + pub withdrawal_shard: u16, + pub withdrawal_address: Address, + pub randao_commitment: Sha256Digest, + pub balance: u64, + pub switch_dynasty: u64 +} + +impl ValidatorRecord { + pub fn new(pubkey: PublicKey, + withdrawal_shard: u16, + withdrawal_address: Address, + randao_commitment: Sha256Digest, + balance: u64, + switch_dynasty: u64) -> ValidatorRecord { + ValidatorRecord { + pubkey: pubkey, + withdrawal_shard: withdrawal_shard, + withdrawal_address: withdrawal_address, + randao_commitment: randao_commitment, + balance: balance, + switch_dynasty: switch_dynasty + } + } +} + + +#[cfg(test)] +mod tests { + extern crate rand; + + use super::*; + use super::super::utils::bls::Keypair; + use self::rand::{ SeedableRng, XorShiftRng }; + + #[test] + fn test_new() { + let mut rng = XorShiftRng::from_seed([0xbc4f6d44, 0xd62f276c, 0xb963afd0, 0x5455863d]); + let keypair = Keypair::generate(&mut rng); + let withdrawal_shard = 1; + let withdrawal_address = Address::random(); + let randao_commitment = Sha256Digest::random(); + let balance = 100; + let switch_dynasty = 10; + + let v = ValidatorRecord::new( + keypair.public, + withdrawal_shard, + withdrawal_address, + randao_commitment, + balance, + switch_dynasty); + // TODO: figure out how to compare keys + // assert_eq!(v.pubkey, keypair.public); + assert_eq!(v.withdrawal_shard, withdrawal_shard); + assert_eq!(v.withdrawal_address, withdrawal_address); + assert_eq!(v.randao_commitment, randao_commitment); + assert_eq!(v.balance, balance); + assert_eq!(v.switch_dynasty, switch_dynasty); + } +} diff --git a/src/utils/bls.rs b/src/utils/bls.rs new file mode 100644 index 000000000..c6b5dae56 --- /dev/null +++ b/src/utils/bls.rs @@ -0,0 +1,13 @@ +extern crate bls; +extern crate pairing; + +use self::bls::AggregateSignature as GenericAggregateSignature; +use self::bls::Signature as GenericSignature; +use self::bls::Keypair as GenericKeypair; +use self::bls::PublicKey as GenericPublicKey; +use self::pairing::bls12_381::Bls12; + +pub type AggregateSignature = GenericAggregateSignature; +pub type Signature = GenericSignature; +pub type Keypair = GenericKeypair; +pub type PublicKey = GenericPublicKey; diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 000000000..28e55d869 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,4 @@ +extern crate ethereum_types; + +pub mod types; +pub mod bls; diff --git a/src/utils/types.rs b/src/utils/types.rs new file mode 100644 index 000000000..894e56db6 --- /dev/null +++ b/src/utils/types.rs @@ -0,0 +1,7 @@ +use super::ethereum_types::{ H256, H160 }; + +pub type Sha256Digest = H256; + +pub type Address = H160; + +pub type Bitfield = Vec; diff --git a/tests/aggregate_vote.rs b/tests/aggregate_vote.rs new file mode 100644 index 000000000..31e1bb209 --- /dev/null +++ b/tests/aggregate_vote.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +}