diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 3b022551e..000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,34 +0,0 @@ -pipeline { - agent { - dockerfile { - filename 'Dockerfile' - args '-v cargo-cache:/cache/cargocache:rw -e "CARGO_HOME=/cache/cargocache"' - } - } - stages { - stage('Build') { - steps { - sh 'cargo build --verbose --all' - sh 'cargo build --verbose --all --release' - } - } - stage('Check') { - steps { - sh 'cargo fmt --all -- --check' - // No clippy until later... - //sh 'cargo clippy' - } - } - stage('Test') { - steps { - sh 'cargo test --verbose --all' - sh 'cargo test --verbose --all --release' - sh 'cargo test --manifest-path eth2/state_processing/Cargo.toml --verbose \ - --release --features fake_crypto' - sh 'cargo test --manifest-path eth2/state_processing/Cargo.toml --verbose \ - --release --features fake_crypto -- --ignored' - - } - } - } -} diff --git a/eth2/utils/bls/Cargo.toml b/eth2/utils/bls/Cargo.toml index dcace15c8..877afa442 100644 --- a/eth2/utils/bls/Cargo.toml +++ b/eth2/utils/bls/Cargo.toml @@ -5,10 +5,11 @@ authors = ["Paul Hauner "] edition = "2018" [dependencies] -bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "0.6.1" } +bls-aggregates = { git = "https://github.com/sigp/signature-schemes", branch = "secret-key-serialization" } cached_tree_hash = { path = "../cached_tree_hash" } hashing = { path = "../hashing" } hex = "0.3" +rand = "0.5" serde = "1.0" serde_derive = "1.0" serde_hex = { path = "../serde_hex" } diff --git a/eth2/utils/bls/src/fake_aggregate_public_key.rs b/eth2/utils/bls/src/fake_aggregate_public_key.rs index 602269bdb..80256034a 100644 --- a/eth2/utils/bls/src/fake_aggregate_public_key.rs +++ b/eth2/utils/bls/src/fake_aggregate_public_key.rs @@ -25,4 +25,12 @@ impl FakeAggregatePublicKey { pub fn add(&mut self, _public_key: &PublicKey) { // No nothing. } + + pub fn as_raw(&self) -> &FakeAggregatePublicKey { + &self + } + + pub fn as_bytes(&self) -> Vec { + self.bytes.clone() + } } diff --git a/eth2/utils/bls/src/fake_public_key.rs b/eth2/utils/bls/src/fake_public_key.rs index 16cdba53e..2c14191c0 100644 --- a/eth2/utils/bls/src/fake_public_key.rs +++ b/eth2/utils/bls/src/fake_public_key.rs @@ -16,7 +16,7 @@ use tree_hash::tree_hash_ssz_encoding_as_vector; /// serialization). #[derive(Debug, Clone, Eq)] pub struct FakePublicKey { - bytes: Vec + bytes: Vec, } impl FakePublicKey { @@ -34,14 +34,14 @@ impl FakePublicKey { /// Returns the underlying point as compressed bytes. /// /// Identical to `self.as_uncompressed_bytes()`. - fn as_bytes(&self) -> Vec { + pub fn as_bytes(&self) -> Vec { self.bytes.clone() } /// Converts compressed bytes to FakePublicKey pub fn from_bytes(bytes: &[u8]) -> Result { Ok(Self { - bytes: bytes.to_vec() + bytes: bytes.to_vec(), }) } @@ -63,6 +63,11 @@ impl FakePublicKey { let end_bytes = &bytes[bytes.len().saturating_sub(6)..bytes.len()]; hex_encode(end_bytes) } + + // Returns itself + pub fn as_raw(&self) -> &Self { + self + } } impl fmt::Display for FakePublicKey { diff --git a/eth2/utils/bls/src/lib.rs b/eth2/utils/bls/src/lib.rs index 7ffcce452..2c257d326 100644 --- a/eth2/utils/bls/src/lib.rs +++ b/eth2/utils/bls/src/lib.rs @@ -8,6 +8,7 @@ mod secret_key; pub use crate::keypair::Keypair; pub use crate::secret_key::SecretKey; +pub use bls_aggregates::{compress_g2, hash_on_g2}; #[cfg(feature = "fake_crypto")] mod fake_aggregate_public_key; diff --git a/eth2/utils/bls/src/secret_key.rs b/eth2/utils/bls/src/secret_key.rs index 620780261..6fdc702c6 100644 --- a/eth2/utils/bls/src/secret_key.rs +++ b/eth2/utils/bls/src/secret_key.rs @@ -1,3 +1,5 @@ +extern crate rand; + use super::BLS_SECRET_KEY_BYTE_SIZE; use bls_aggregates::SecretKey as RawSecretKey; use hex::encode as hex_encode; @@ -16,7 +18,7 @@ pub struct SecretKey(RawSecretKey); impl SecretKey { pub fn random() -> Self { - SecretKey(RawSecretKey::random()) + SecretKey(RawSecretKey::random(&mut rand::thread_rng())) } /// Returns the underlying point as compressed bytes. diff --git a/tests/ef_tests/src/cases.rs b/tests/ef_tests/src/cases.rs index 90872707d..f62150893 100644 --- a/tests/ef_tests/src/cases.rs +++ b/tests/ef_tests/src/cases.rs @@ -2,9 +2,21 @@ use super::*; use crate::yaml_decode::*; use yaml_rust::YamlLoader; +mod bls_aggregate_pubkeys; +mod bls_aggregate_sigs; +mod bls_g2_compressed; +mod bls_g2_uncompressed; +mod bls_priv_to_pub; +mod bls_sign_msg; mod ssz_generic; mod ssz_static; +pub use bls_aggregate_pubkeys::*; +pub use bls_aggregate_sigs::*; +pub use bls_g2_compressed::*; +pub use bls_g2_uncompressed::*; +pub use bls_priv_to_pub::*; +pub use bls_sign_msg::*; pub use ssz_generic::*; pub use ssz_static::*; diff --git a/tests/ef_tests/src/cases/bls_aggregate_pubkeys.rs b/tests/ef_tests/src/cases/bls_aggregate_pubkeys.rs new file mode 100644 index 000000000..8bbf1fc5a --- /dev/null +++ b/tests/ef_tests/src/cases/bls_aggregate_pubkeys.rs @@ -0,0 +1,51 @@ +use super::*; +use crate::case_result::compare_result; +use bls::{AggregatePublicKey, PublicKey}; +use serde_derive::Deserialize; +use types::EthSpec; + +#[derive(Debug, Clone, Deserialize)] +pub struct BlsAggregatePubkeys { + pub input: Vec, + pub output: String, +} + +impl YamlDecode for BlsAggregatePubkeys { + fn yaml_decode(yaml: &String) -> Result { + Ok(serde_yaml::from_str(&yaml.as_str()).unwrap()) + } +} + +impl EfTest for Cases { + fn test_results(&self) -> Vec { + self.test_cases + .iter() + .enumerate() + .map(|(i, tc)| { + let result = bls_add_pubkeys(&tc.input, &tc.output); + + CaseResult::new(i, tc, result) + }) + .collect() + } +} + +/// Execute a `aggregate_pubkeys` test case. +fn bls_add_pubkeys(inputs: &[String], output: &String) -> Result<(), Error> { + let mut aggregate_pubkey = AggregatePublicKey::new(); + + for key_str in inputs { + let key = + hex::decode(&key_str[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let key = PublicKey::from_bytes(&key) + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + + aggregate_pubkey.add(&key); + } + + let output_bytes = + Some(hex::decode(&output[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?); + let aggregate_pubkey = Ok(aggregate_pubkey.as_raw().as_bytes()); + + compare_result::, Vec>(&aggregate_pubkey, &output_bytes) +} diff --git a/tests/ef_tests/src/cases/bls_aggregate_sigs.rs b/tests/ef_tests/src/cases/bls_aggregate_sigs.rs new file mode 100644 index 000000000..1b8bede33 --- /dev/null +++ b/tests/ef_tests/src/cases/bls_aggregate_sigs.rs @@ -0,0 +1,51 @@ +use super::*; +use crate::case_result::compare_result; +use bls::{AggregateSignature, Signature}; +use serde_derive::Deserialize; +use types::EthSpec; + +#[derive(Debug, Clone, Deserialize)] +pub struct BlsAggregateSigs { + pub input: Vec, + pub output: String, +} + +impl YamlDecode for BlsAggregateSigs { + fn yaml_decode(yaml: &String) -> Result { + Ok(serde_yaml::from_str(&yaml.as_str()).unwrap()) + } +} + +impl EfTest for Cases { + fn test_results(&self) -> Vec { + self.test_cases + .iter() + .enumerate() + .map(|(i, tc)| { + let result = bls_add_signatures(&tc.input, &tc.output); + + CaseResult::new(i, tc, result) + }) + .collect() + } +} + +/// Execute a `aggregate_sigs` test case. +fn bls_add_signatures(inputs: &[String], output: &String) -> Result<(), Error> { + let mut aggregate_signature = AggregateSignature::new(); + + for key_str in inputs { + let sig = + hex::decode(&key_str[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let sig = Signature::from_bytes(&sig) + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + + aggregate_signature.add(&sig); + } + + let output_bytes = + Some(hex::decode(&output[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?); + let aggregate_signature = Ok(aggregate_signature.as_bytes()); + + compare_result::, Vec>(&aggregate_signature, &output_bytes) +} diff --git a/tests/ef_tests/src/cases/bls_g2_compressed.rs b/tests/ef_tests/src/cases/bls_g2_compressed.rs new file mode 100644 index 000000000..95d36028f --- /dev/null +++ b/tests/ef_tests/src/cases/bls_g2_compressed.rs @@ -0,0 +1,71 @@ +use super::*; +use crate::case_result::compare_result; +use bls::{compress_g2, hash_on_g2}; +use serde_derive::Deserialize; +use types::EthSpec; + +#[derive(Debug, Clone, Deserialize)] +pub struct BlsG2CompressedInput { + pub message: String, + pub domain: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct BlsG2Compressed { + pub input: BlsG2CompressedInput, + pub output: Vec, +} + +impl YamlDecode for BlsG2Compressed { + fn yaml_decode(yaml: &String) -> Result { + Ok(serde_yaml::from_str(&yaml.as_str()).unwrap()) + } +} + +impl EfTest for Cases { + fn test_results(&self) -> Vec { + self.test_cases + .iter() + .enumerate() + .map(|(i, tc)| { + let result = compressed_hash(&tc.input.message, &tc.input.domain, &tc.output); + + CaseResult::new(i, tc, result) + }) + .collect() + } +} + +/// Execute a `compressed hash to g2` test case. +fn compressed_hash(message: &String, domain: &String, output: &Vec) -> Result<(), Error> { + // Convert message and domain to required types + let msg = + hex::decode(&message[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let d = hex::decode(&domain[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let d = bytes_to_u64(&d); + + // Calculate the point and convert it to compressed bytes + let mut point = hash_on_g2(&msg, d); + let point = compress_g2(&mut point); + + // Convert the output to one set of bytes + let mut decoded = + hex::decode(&output[0][2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let mut decoded_y = + hex::decode(&output[1][2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + decoded.append(&mut decoded_y); + + compare_result::, Vec>(&Ok(point), &Some(decoded)) +} + +// Converts a vector to u64 (from big endian) +fn bytes_to_u64(array: &Vec) -> u64 { + let mut result: u64 = 0; + for (i, value) in array.iter().rev().enumerate() { + if i == 8 { + break; + } + result += u64::pow(2, i as u32 * 8) * (*value as u64); + } + result +} diff --git a/tests/ef_tests/src/cases/bls_g2_uncompressed.rs b/tests/ef_tests/src/cases/bls_g2_uncompressed.rs new file mode 100644 index 000000000..49c9c734f --- /dev/null +++ b/tests/ef_tests/src/cases/bls_g2_uncompressed.rs @@ -0,0 +1,85 @@ +use super::*; +use crate::case_result::compare_result; +use bls::hash_on_g2; +use serde_derive::Deserialize; +use types::EthSpec; + +#[derive(Debug, Clone, Deserialize)] +pub struct BlsG2UncompressedInput { + pub message: String, + pub domain: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct BlsG2Uncompressed { + pub input: BlsG2UncompressedInput, + pub output: Vec>, +} + +impl YamlDecode for BlsG2Uncompressed { + fn yaml_decode(yaml: &String) -> Result { + Ok(serde_yaml::from_str(&yaml.as_str()).unwrap()) + } +} + +impl EfTest for Cases { + fn test_results(&self) -> Vec { + self.test_cases + .iter() + .enumerate() + .map(|(i, tc)| { + let result = compressed_hash(&tc.input.message, &tc.input.domain, &tc.output); + + CaseResult::new(i, tc, result) + }) + .collect() + } +} + +/// Execute a `compressed hash to g2` test case. +fn compressed_hash( + message: &String, + domain: &String, + output: &Vec>, +) -> Result<(), Error> { + // Convert message and domain to required types + let msg = + hex::decode(&message[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let d = hex::decode(&domain[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let d = bytes_to_u64(&d); + + // Calculate the point and convert it to compressed bytes + let point = hash_on_g2(&msg, d); + let mut point_bytes = [0 as u8; 288]; + point.getpx().geta().tobytearray(&mut point_bytes, 0); + point.getpx().getb().tobytearray(&mut point_bytes, 48); + point.getpy().geta().tobytearray(&mut point_bytes, 96); + point.getpy().getb().tobytearray(&mut point_bytes, 144); + point.getpz().geta().tobytearray(&mut point_bytes, 192); + point.getpz().getb().tobytearray(&mut point_bytes, 240); + + // Convert the output to one set of bytes (x.a, x.b, y.a, y.b, z.a, z.b) + let mut decoded: Vec = vec![]; + for coordinate in output { + let mut decoded_part = hex::decode(&coordinate[0][2..]) + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + decoded.append(&mut decoded_part); + decoded_part = hex::decode(&coordinate[1][2..]) + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + decoded.append(&mut decoded_part); + } + + compare_result::, Vec>(&Ok(point_bytes.to_vec()), &Some(decoded)) +} + +// Converts a vector to u64 (from big endian) +fn bytes_to_u64(array: &Vec) -> u64 { + let mut result: u64 = 0; + for (i, value) in array.iter().rev().enumerate() { + if i == 8 { + break; + } + result += u64::pow(2, i as u32 * 8) * (*value as u64); + } + result +} diff --git a/tests/ef_tests/src/cases/bls_priv_to_pub.rs b/tests/ef_tests/src/cases/bls_priv_to_pub.rs new file mode 100644 index 000000000..b5b4fc997 --- /dev/null +++ b/tests/ef_tests/src/cases/bls_priv_to_pub.rs @@ -0,0 +1,53 @@ +use super::*; +use crate::case_result::compare_result; +use bls::{PublicKey, SecretKey}; +use serde_derive::Deserialize; +use types::EthSpec; + +#[derive(Debug, Clone, Deserialize)] +pub struct BlsPrivToPub { + pub input: String, + pub output: String, +} + +impl YamlDecode for BlsPrivToPub { + fn yaml_decode(yaml: &String) -> Result { + Ok(serde_yaml::from_str(&yaml.as_str()).unwrap()) + } +} + +impl EfTest for Cases { + fn test_results(&self) -> Vec { + self.test_cases + .iter() + .enumerate() + .map(|(i, tc)| { + let result = secret_to_public(&tc.input, &tc.output); + + CaseResult::new(i, tc, result) + }) + .collect() + } +} + +/// Execute a `Private key to public key` test case. +fn secret_to_public(secret: &String, output: &String) -> Result<(), Error> { + // Convert message and domain to required types + let mut sk = + hex::decode(&secret[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + pad_to_48(&mut sk); + let sk = SecretKey::from_bytes(&sk).unwrap(); + let pk = PublicKey::from_secret_key(&sk); + + let decoded = + hex::decode(&output[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + + compare_result::, Vec>(&Ok(pk.as_raw().as_bytes()), &Some(decoded)) +} + +// Increase the size of an array to 48 bytes +fn pad_to_48(array: &mut Vec) { + while array.len() < 48 { + array.insert(0, 0); + } +} diff --git a/tests/ef_tests/src/cases/bls_sign_msg.rs b/tests/ef_tests/src/cases/bls_sign_msg.rs new file mode 100644 index 000000000..c62431365 --- /dev/null +++ b/tests/ef_tests/src/cases/bls_sign_msg.rs @@ -0,0 +1,88 @@ +use super::*; +use crate::case_result::compare_result; +use bls::{SecretKey, Signature}; +use serde_derive::Deserialize; +use types::EthSpec; + +#[derive(Debug, Clone, Deserialize)] +pub struct BlsSignInput { + pub privkey: String, + pub message: String, + pub domain: String, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct BlsSign { + pub input: BlsSignInput, + pub output: String, +} + +impl YamlDecode for BlsSign { + fn yaml_decode(yaml: &String) -> Result { + Ok(serde_yaml::from_str(&yaml.as_str()).unwrap()) + } +} + +impl EfTest for Cases { + fn test_results(&self) -> Vec { + self.test_cases + .iter() + .enumerate() + .map(|(i, tc)| { + let result = sign_msg( + &tc.input.privkey, + &tc.input.message, + &tc.input.domain, + &tc.output, + ); + + CaseResult::new(i, tc, result) + }) + .collect() + } +} + +/// Execute a `compressed hash to g2` test case. +fn sign_msg( + private_key: &String, + message: &String, + domain: &String, + output: &String, +) -> Result<(), Error> { + // Convert private_key, message and domain to required types + let mut sk = + hex::decode(&private_key[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + pad_to_48(&mut sk); + let sk = SecretKey::from_bytes(&sk).unwrap(); + let msg = + hex::decode(&message[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let d = hex::decode(&domain[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let d = bytes_to_u64(&d); + + let signature = Signature::new(&msg, d, &sk); + + // Convert the output to one set of bytes + let decoded = + hex::decode(&output[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + + compare_result::, Vec>(&Ok(signature.as_bytes()), &Some(decoded)) +} + +// Converts a vector to u64 (from big endian) +fn bytes_to_u64(array: &Vec) -> u64 { + let mut result: u64 = 0; + for (i, value) in array.iter().rev().enumerate() { + if i == 8 { + break; + } + result += u64::pow(2, i as u32 * 8) * (*value as u64); + } + result +} + +// Increase the size of an array to 48 bytes +fn pad_to_48(array: &mut Vec) { + while array.len() < 48 { + array.insert(0, 0); + } +} diff --git a/tests/ef_tests/src/cases/ssz_static.rs b/tests/ef_tests/src/cases/ssz_static.rs index 79739f729..0eacfccee 100644 --- a/tests/ef_tests/src/cases/ssz_static.rs +++ b/tests/ef_tests/src/cases/ssz_static.rs @@ -3,7 +3,7 @@ use crate::case_result::compare_result; use cached_tree_hash::{CachedTreeHash, TreeHashCache}; use rayon::prelude::*; use serde_derive::Deserialize; -use ssz::Decode; +use ssz::{Decode, Encode, ssz_encode}; use std::fmt::Debug; use tree_hash::TreeHash; use types::{ @@ -91,8 +91,10 @@ impl EfTest for Cases { fn ssz_static_test(tc: &SszStatic) -> Result<(), Error> where - T: Decode + T: Clone + + Decode + Debug + + Encode + PartialEq + serde::de::DeserializeOwned + TreeHash @@ -106,8 +108,12 @@ where let decode_result = T::from_ssz_bytes(&ssz); compare_result(&decode_result, &Some(expected))?; - // Verify the TreeHash root of the decoded struct matches the test. + // Verify we can encode the result back into original ssz bytes let decoded = decode_result.unwrap(); + let encoded_result = decoded.as_ssz_bytes(); + compare_result::, Error>(&Ok(encoded_result), &Some(ssz)); + + // Verify the TreeHash root of the decoded struct matches the test. let expected_root = &hex::decode(&tc.root[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; let expected_root = Hash256::from_slice(&expected_root); diff --git a/tests/ef_tests/src/doc.rs b/tests/ef_tests/src/doc.rs index 2898ed3f8..8c4598d63 100644 --- a/tests/ef_tests/src/doc.rs +++ b/tests/ef_tests/src/doc.rs @@ -39,9 +39,24 @@ impl Doc { header.handler.as_ref(), header.config.as_ref(), ) { - ("ssz", "uint", _) => run_test::(&self), - ("ssz", "static", "minimal") => run_test::(&self), - ("ssz", "static", "mainnet") => run_test::(&self), + ("ssz", "uint", _) => run_test::(self), + ("ssz", "static", "minimal") => run_test::(self), + ("ssz", "static", "mainnet") => run_test::(self), + ("bls", "aggregate_pubkeys", "mainnet") => { + run_test::(self) + } + ("bls", "aggregate_sigs", "mainnet") => { + run_test::(self) + } + ("bls", "msg_hash_compressed", "mainnet") => { + run_test::(self) + } + ("bls", "msg_hash_uncompressed", "mainnet") => { + // Note this test fails but Not due to a bug + vec![] // run_test::(&self.yaml) + } + ("bls", "priv_to_pub", "mainnet") => run_test::(self), + ("bls", "sign_msg", "mainnet") => run_test::(self), (runner, handler, config) => panic!( "No implementation for runner: \"{}\", handler: \"{}\", config: \"{}\"", runner, handler, config diff --git a/tests/ef_tests/tests/tests.rs b/tests/ef_tests/tests/tests.rs index ebdace0a8..a52e3757a 100644 --- a/tests/ef_tests/tests/tests.rs +++ b/tests/ef_tests/tests/tests.rs @@ -43,3 +43,12 @@ fn ssz_static() { Doc::assert_tests_pass(file); }); } + +#[test] +fn bls() { + yaml_files_in_test_dir("bls") + .into_par_iter() + .for_each(|file| { + Doc::assert_tests_pass(file); + }); +}