diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 988c7aa29f..35767aadde 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -77,7 +77,7 @@ jobs: - uses: actions/checkout@v1 - name: Get latest version of stable Rust run: rustup update stable - - name: Run eth2.0-spec-tests with and without fake_crypto + - name: Run eth2.0-spec-tests with blst, milagro and fake_crypto run: make test-ef dockerfile-ubuntu: name: dockerfile-ubuntu diff --git a/Cargo.lock b/Cargo.lock index f450d3315e..8b93e4f92e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,7 +186,7 @@ dependencies = [ [[package]] name = "amcl" version = "0.2.0" -source = "git+https://github.com/sigp/milagro_bls?tag=v1.1.0#32c9f9382fc73f8976a00aca9773e6a322bb2c9e" +source = "git+https://github.com/sigp/milagro_bls?branch=paulh#7662690845f3f2594e46cfe71b6b0bb91d5e1f82" dependencies = [ "hex 0.3.2", "lazy_static", @@ -360,6 +360,7 @@ dependencies = [ "fork_choice", "futures 0.3.5", "genesis", + "int_to_bytes", "integer-sqrt", "itertools 0.9.0", "lazy_static", @@ -533,9 +534,11 @@ name = "bls" version = "0.2.0" dependencies = [ "arbitrary", + "blst", "eth2_hashing", "eth2_ssz", - "hex 0.4.2", + "ethereum-types", + "hex 0.3.2", "milagro_bls", "rand 0.7.3", "serde", @@ -545,6 +548,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "blst" +version = "0.1.1" +source = "git+https://github.com/sigp/blst.git?rev=968c846a2dc46e836e407bbdbac1a38a597ebc46#968c846a2dc46e836e407bbdbac1a38a597ebc46" +dependencies = [ + "cc", + "glob", + "threadpool", +] + [[package]] name = "boot_node" version = "0.1.0" @@ -1456,10 +1469,10 @@ name = "eth2_interop_keypairs" version = "0.2.0" dependencies = [ "base64 0.12.3", + "bls", "eth2_hashing", "hex 0.4.2", "lazy_static", - "milagro_bls", "num-bigint", "serde", "serde_derive", @@ -3093,7 +3106,7 @@ dependencies = [ [[package]] name = "milagro_bls" version = "1.1.0" -source = "git+https://github.com/sigp/milagro_bls?tag=v1.1.0#32c9f9382fc73f8976a00aca9773e6a322bb2c9e" +source = "git+https://github.com/sigp/milagro_bls?branch=paulh#7662690845f3f2594e46cfe71b6b0bb91d5e1f82" dependencies = [ "amcl", "hex 0.4.2", @@ -5256,6 +5269,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.1.43" diff --git a/Makefile b/Makefile index dd408e3ae5..a3c3af18e4 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ check-benches: run-ef-tests: cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests" cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests,fake_crypto" + cargo test --release --manifest-path=$(EF_TESTS)/Cargo.toml --features "ef_tests,milagro" # Runs only the tests/state_transition_vectors tests. run-state-transition-tests: diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index f890ff4db6..07dc309a57 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -26,7 +26,7 @@ clap_utils = { path = "../common/clap_utils" } eth2_wallet = { path = "../crypto/eth2_wallet" } eth2_wallet_manager = { path = "../common/eth2_wallet_manager" } rand = "0.7.2" -validator_dir = { path = "../common/validator_dir", features = ["unencrypted_keys"] } +validator_dir = { path = "../common/validator_dir" } tokio = { version = "0.2.21", features = ["full"] } eth2_keystore = { path = "../crypto/eth2_keystore" } account_utils = { path = "../common/account_utils" } diff --git a/account_manager/src/lib.rs b/account_manager/src/lib.rs index 65d1f23e70..5300693dcf 100644 --- a/account_manager/src/lib.rs +++ b/account_manager/src/lib.rs @@ -1,5 +1,4 @@ mod common; -pub mod upgrade_legacy_keypairs; pub mod validator; pub mod wallet; @@ -19,7 +18,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .about("Utilities for generating and managing Ethereum 2.0 accounts.") .subcommand(wallet::cli_app()) .subcommand(validator::cli_app()) - .subcommand(upgrade_legacy_keypairs::cli_app()) } /// Run the account manager, returning an error if the operation did not succeed. @@ -27,7 +25,6 @@ pub fn run(matches: &ArgMatches<'_>, env: Environment) -> Result< match matches.subcommand() { (wallet::CMD, Some(matches)) => wallet::cli_run(matches)?, (validator::CMD, Some(matches)) => validator::cli_run(matches, env)?, - (upgrade_legacy_keypairs::CMD, Some(matches)) => upgrade_legacy_keypairs::cli_run(matches)?, (unknown, _) => { return Err(format!( "{} is not a valid {} command. See --help.", diff --git a/account_manager/src/upgrade_legacy_keypairs.rs b/account_manager/src/upgrade_legacy_keypairs.rs deleted file mode 100644 index 3d91bce048..0000000000 --- a/account_manager/src/upgrade_legacy_keypairs.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! This command allows migrating from the old method of storing keys (unencrypted SSZ) to the -//! current method of using encrypted EIP-2335 keystores. -//! -//! This command should be completely removed once the `unencrypted_keys` feature is removed from -//! the `validator_dir` command. This should hopefully be in mid-June 2020. -//! -//! ## Example -//! -//! This command will upgrade all keypairs in the `--validators-dir`, storing the newly-generated -//! passwords in `--secrets-dir`. -//! -//! ```ignore -//! lighthouse am upgrade-legacy-keypairs \ -//! --validators-dir ~/.lighthouse/validators \ -//! --secrets-dir ~/.lighthouse/secrets -//! ``` - -use crate::{SECRETS_DIR_FLAG, VALIDATOR_DIR_FLAG}; -use clap::{App, Arg, ArgMatches}; -use clap_utils::parse_required; -use eth2_keystore::KeystoreBuilder; -use rand::{distributions::Alphanumeric, Rng}; -use std::fs::{create_dir_all, read_dir, write, File}; -use std::path::{Path, PathBuf}; -use types::Keypair; -use validator_dir::{ - unencrypted_keys::load_unencrypted_keypair, VOTING_KEYSTORE_FILE, WITHDRAWAL_KEYSTORE_FILE, -}; - -pub const CMD: &str = "upgrade-legacy-keypairs"; -pub const VOTING_KEYPAIR_FILE: &str = "voting_keypair"; -pub const WITHDRAWAL_KEYPAIR_FILE: &str = "withdrawal_keypair"; - -pub fn cli_app<'a, 'b>() -> App<'a, 'b> { - App::new(CMD) - .about( - "Converts legacy unencrypted SSZ keypairs into encrypted keystores.", - ) - .arg( - Arg::with_name(VALIDATOR_DIR_FLAG) - .long(VALIDATOR_DIR_FLAG) - .value_name("VALIDATORS_DIRECTORY") - .takes_value(true) - .required(true) - .help("The directory containing legacy validators. Generally ~/.lighthouse/validators"), - ) - .arg( - Arg::with_name(SECRETS_DIR_FLAG) - .long(SECRETS_DIR_FLAG) - .value_name("SECRETS_DIRECTORY") - .takes_value(true) - .required(true) - .help("The directory where keystore passwords will be stored. Generally ~/.lighthouse/secrets"), - ) -} - -pub fn cli_run(matches: &ArgMatches) -> Result<(), String> { - let validators_dir: PathBuf = parse_required(matches, VALIDATOR_DIR_FLAG)?; - let secrets_dir: PathBuf = parse_required(matches, SECRETS_DIR_FLAG)?; - - if !secrets_dir.exists() { - create_dir_all(&secrets_dir) - .map_err(|e| format!("Failed to create secrets dir {:?}: {:?}", secrets_dir, e))?; - } - - read_dir(&validators_dir) - .map_err(|e| { - format!( - "Failed to read validators directory {:?}: {:?}", - validators_dir, e - ) - })? - .try_for_each(|dir| { - let path = dir - .map_err(|e| format!("Unable to read dir: {}", e))? - .path(); - - if path.is_dir() { - if let Err(e) = upgrade_keypair( - &path, - &secrets_dir, - VOTING_KEYPAIR_FILE, - VOTING_KEYSTORE_FILE, - ) { - println!("Validator {:?}: {:?}", path, e); - } else { - println!("Validator {:?} voting keys: success", path); - } - - if let Err(e) = upgrade_keypair( - &path, - &secrets_dir, - WITHDRAWAL_KEYPAIR_FILE, - WITHDRAWAL_KEYSTORE_FILE, - ) { - println!("Validator {:?}: {:?}", path, e); - } else { - println!("Validator {:?} withdrawal keys: success", path); - } - } - - Ok(()) - }) -} - -fn upgrade_keypair>( - validator_dir: P, - secrets_dir: P, - input_filename: &str, - output_filename: &str, -) -> Result<(), String> { - let validator_dir = validator_dir.as_ref(); - let secrets_dir = secrets_dir.as_ref(); - - let keypair: Keypair = load_unencrypted_keypair(validator_dir.join(input_filename))?; - - let password = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(48) - .collect::() - .into_bytes(); - - let keystore = KeystoreBuilder::new(&keypair, &password, "".into()) - .map_err(|e| format!("Unable to create keystore builder: {:?}", e))? - .build() - .map_err(|e| format!("Unable to build keystore: {:?}", e))?; - - let keystore_path = validator_dir.join(output_filename); - - if keystore_path.exists() { - return Err(format!("{:?} already exists", keystore_path)); - } - - let mut file = File::create(&keystore_path).map_err(|e| format!("Cannot create: {:?}", e))?; - keystore - .to_json_writer(&mut file) - .map_err(|e| format!("Cannot write keystore to {:?}: {:?}", keystore_path, e))?; - - let password_path = secrets_dir.join(keypair.pk.as_hex_string()); - - if password_path.exists() { - return Err(format!("{:?} already exists", password_path)); - } - - write(&password_path, &password) - .map_err(|e| format!("Unable to write password to {:?}: {:?}", password_path, e))?; - - Ok(()) -} diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 306151ba64..154da4f1e1 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -9,6 +9,9 @@ default = ["participation_metrics"] write_ssz_files = [] # Writes debugging .ssz files to /tmp during block processing. participation_metrics = [] # Exposes validator participation metrics to Prometheus. +[dev-dependencies] +int_to_bytes = { path = "../../consensus/int_to_bytes" } + [dependencies] eth2_config = { path = "../../common/eth2_config" } merkle_proof = { path = "../../consensus/merkle_proof" } diff --git a/beacon_node/beacon_chain/src/attestation_verification.rs b/beacon_node/beacon_chain/src/attestation_verification.rs index a62e9fc6e6..e4db790b8c 100644 --- a/beacon_node/beacon_chain/src/attestation_verification.rs +++ b/beacon_node/beacon_chain/src/attestation_verification.rs @@ -521,7 +521,7 @@ pub fn verify_attestation_signature( let _signature_verification_timer = metrics::start_timer(&metrics::ATTESTATION_PROCESSING_SIGNATURE_TIMES); - if signature_set.is_valid() { + if signature_set.verify() { Ok(()) } else { Err(Error::InvalidSignature) @@ -589,7 +589,7 @@ pub fn verify_signed_aggregate_signatures( .map_err(BeaconChainError::SignatureSetError)?, ]; - Ok(verify_signature_sets(signature_sets)) + Ok(verify_signature_sets(signature_sets.iter())) } /// Assists in readability. diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index d912015985..4e6329ee73 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -862,7 +862,7 @@ impl BeaconChain { root: target_root, }, }, - signature: AggregateSignature::empty_signature(), + signature: AggregateSignature::empty(), }) } @@ -1654,7 +1654,7 @@ impl BeaconChain { }, }, // The block is not signed here, that is the task of a validator client. - signature: Signature::empty_signature(), + signature: Signature::empty(), }; per_block_processing( diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 800f5e7cb6..b0c467920d 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -666,7 +666,7 @@ fn genesis_block( message: BeaconBlock::empty(&spec), // Empty signature, which should NEVER be read. This isn't to-spec, but makes the genesis // block consistent with every other block. - signature: Signature::empty_signature(), + signature: Signature::empty(), }; genesis_block.message.state_root = genesis_state .update_tree_hash_cache() diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index ed0bfe8d52..5c017a3e8e 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -6,7 +6,6 @@ use crate::observed_attesters::Error as ObservedAttestersError; use crate::observed_block_producers::Error as ObservedBlockProducersError; use operation_pool::OpPoolError; use safe_arith::ArithError; -use ssz::DecodeError; use ssz_types::Error as SszTypesError; use state_processing::{ block_signature_verifier::Error as BlockSignatureVerifierError, @@ -69,7 +68,7 @@ pub enum BeaconChainError { AttestationCacheLockTimeout, ValidatorPubkeyCacheLockTimeout, IncorrectStateForAttestation(RelativeEpochError), - InvalidValidatorPubkeyBytes(DecodeError), + InvalidValidatorPubkeyBytes(bls::Error), ValidatorPubkeyCacheIncomplete(usize), SignatureSetError(SignatureSetError), BlockSignatureVerifierError(state_processing::block_signature_verifier::Error), diff --git a/beacon_node/beacon_chain/src/snapshot_cache.rs b/beacon_node/beacon_chain/src/snapshot_cache.rs index fffe715f9f..e1b26e8b09 100644 --- a/beacon_node/beacon_chain/src/snapshot_cache.rs +++ b/beacon_node/beacon_chain/src/snapshot_cache.rs @@ -99,7 +99,7 @@ mod test { use super::*; use types::{ test_utils::{generate_deterministic_keypair, TestingBeaconStateBuilder}, - BeaconBlock, Epoch, MainnetEthSpec, Signature, SignedBeaconBlock, Slot, + BeaconBlock, Epoch, MainnetEthSpec, SignedBeaconBlock, Slot, }; const CACHE_SIZE: usize = 4; @@ -115,7 +115,9 @@ mod test { beacon_state_root: Hash256::from_low_u64_be(i), beacon_block: SignedBeaconBlock { message: BeaconBlock::empty(&spec), - signature: Signature::new(&[42], &generate_deterministic_keypair(0).sk), + signature: generate_deterministic_keypair(0) + .sk + .sign(Hash256::from_low_u64_be(42)), }, beacon_block_root: Hash256::from_low_u64_be(i), } diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 744712eb4b..76993f87a1 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -23,8 +23,8 @@ use tempfile::{tempdir, TempDir}; use tree_hash::TreeHash; use types::{ AggregateSignature, Attestation, BeaconState, BeaconStateHash, ChainSpec, Domain, EthSpec, - Hash256, Keypair, SecretKey, SelectionProof, Signature, SignedAggregateAndProof, - SignedBeaconBlock, SignedBeaconBlockHash, SignedRoot, Slot, SubnetId, + Hash256, Keypair, SecretKey, SelectionProof, SignedAggregateAndProof, SignedBeaconBlock, + SignedBeaconBlockHash, SignedRoot, Slot, SubnetId, }; pub use types::test_utils::generate_deterministic_keypairs; @@ -515,7 +515,7 @@ where self.spec .get_domain(epoch, Domain::Randao, fork, state.genesis_validators_root); let message = epoch.signing_root(domain); - Signature::new(message.as_bytes(), sk) + sk.sign(message) }; let (block, state) = self @@ -586,12 +586,9 @@ where let message = attestation.data.signing_root(domain); - let mut agg_sig = AggregateSignature::new(); + let mut agg_sig = AggregateSignature::infinity(); - agg_sig.add(&Signature::new( - message.as_bytes(), - self.get_sk(*validator_index), - )); + agg_sig.add_assign(&self.get_sk(*validator_index).sign(message)); agg_sig }; diff --git a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs index 4d588d023c..02d35acaa8 100644 --- a/beacon_node/beacon_chain/src/validator_pubkey_cache.rs +++ b/beacon_node/beacon_chain/src/validator_pubkey_cache.rs @@ -140,6 +140,7 @@ struct ValidatorPubkeyCacheFile(File); enum Error { Io(io::Error), Ssz(DecodeError), + PubkeyDecode(bls::Error), /// The file read from disk does not have a contiguous list of validator public keys. The file /// has become corrupted. InconsistentIndex { @@ -200,7 +201,7 @@ impl ValidatorPubkeyCacheFile { let expected = last.map(|n| n + 1); if expected.map_or(true, |expected| index == expected) { last = Some(index); - pubkeys.push((&pubkey).try_into().map_err(Error::Ssz)?); + pubkeys.push((&pubkey).try_into().map_err(Error::PubkeyDecode)?); indices.insert(pubkey, index); } else { return Err(Error::InconsistentIndex { diff --git a/beacon_node/beacon_chain/tests/attestation_production.rs b/beacon_node/beacon_chain/tests/attestation_production.rs index af5a0ce479..407bf2ee7d 100644 --- a/beacon_node/beacon_chain/tests/attestation_production.rs +++ b/beacon_node/beacon_chain/tests/attestation_production.rs @@ -111,7 +111,7 @@ fn produces_attestations() { ); assert_eq!( attestation.signature, - AggregateSignature::empty_signature(), + AggregateSignature::empty(), "bad signature" ); assert_eq!(data.index, index, "bad index"); diff --git a/beacon_node/beacon_chain/tests/attestation_verification.rs b/beacon_node/beacon_chain/tests/attestation_verification.rs index 6288b7534d..27db261cbc 100644 --- a/beacon_node/beacon_chain/tests/attestation_verification.rs +++ b/beacon_node/beacon_chain/tests/attestation_verification.rs @@ -8,13 +8,14 @@ use beacon_chain::{ test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, HarnessType}, BeaconChain, BeaconChainTypes, }; +use int_to_bytes::int_to_bytes32; use state_processing::per_slot_processing; use store::config::StoreConfig; use tree_hash::TreeHash; use types::{ test_utils::generate_deterministic_keypair, AggregateSignature, Attestation, EthSpec, Hash256, - Keypair, MainnetEthSpec, SecretKey, SelectionProof, Signature, SignedAggregateAndProof, - SignedBeaconBlock, SubnetId, Unsigned, + Keypair, MainnetEthSpec, SecretKey, SelectionProof, SignedAggregateAndProof, SignedBeaconBlock, + SubnetId, Unsigned, }; pub type E = MainnetEthSpec; @@ -311,7 +312,7 @@ fn aggregated_gossip_verification() { let aggregation_bits = &mut a.message.aggregate.aggregation_bits; aggregation_bits.difference_inplace(&aggregation_bits.clone()); assert!(aggregation_bits.is_zero()); - a.message.aggregate.signature = AggregateSignature::new(); + a.message.aggregate.signature = AggregateSignature::infinity(); a }, AttnError::EmptyAggregationBitfield @@ -330,7 +331,7 @@ fn aggregated_gossip_verification() { { let mut a = valid_aggregate.clone(); - a.signature = Signature::new(&[42, 42], &validator_sk); + a.signature = validator_sk.sign(Hash256::from_low_u64_be(42)); a }, @@ -370,7 +371,9 @@ fn aggregated_gossip_verification() { let mut i: u64 = 0; a.message.selection_proof = loop { i += 1; - let proof: SelectionProof = Signature::new(&i.to_le_bytes(), &validator_sk).into(); + let proof: SelectionProof = validator_sk + .sign(Hash256::from_slice(&int_to_bytes32(i))) + .into(); if proof .is_aggregator(committee_len, &harness.chain.spec) .unwrap() @@ -397,8 +400,8 @@ fn aggregated_gossip_verification() { { let mut a = valid_aggregate.clone(); - let mut agg_sig = AggregateSignature::new(); - agg_sig.add(&Signature::new(&[42, 42], &aggregator_sk)); + let mut agg_sig = AggregateSignature::infinity(); + agg_sig.add_assign(&aggregator_sk.sign(Hash256::from_low_u64_be(42))); a.message.aggregate.signature = agg_sig; a @@ -727,8 +730,8 @@ fn unaggregated_gossip_verification() { { let mut a = valid_attestation.clone(); - let mut agg_sig = AggregateSignature::new(); - agg_sig.add(&Signature::new(&[42, 42], &validator_sk)); + let mut agg_sig = AggregateSignature::infinity(); + agg_sig.add_assign(&validator_sk.sign(Hash256::from_low_u64_be(42))); a.signature = agg_sig; a @@ -737,13 +740,10 @@ fn unaggregated_gossip_verification() { AttnError::InvalidSignature ); - assert!( - harness - .chain - .verify_unaggregated_attestation_for_gossip(valid_attestation.clone(), subnet_id) - .is_ok(), - "valid attestation should be verified" - ); + harness + .chain + .verify_unaggregated_attestation_for_gossip(valid_attestation.clone(), subnet_id) + .expect("valid attestation should be verified"); /* * The following test ensures that: diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 101443d93f..30c7c5603f 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -68,13 +68,13 @@ fn chain_segment_blocks() -> Vec> { fn junk_signature() -> Signature { let kp = generate_deterministic_keypair(VALIDATOR_COUNT); - let message = &[42, 42]; - Signature::new(message, &kp.sk) + let message = Hash256::from_slice(&[42; 32]); + kp.sk.sign(message) } fn junk_aggregate_signature() -> AggregateSignature { - let mut agg_sig = AggregateSignature::new(); - agg_sig.add(&junk_signature()); + let mut agg_sig = AggregateSignature::empty(); + agg_sig.add_assign(&junk_signature()); agg_sig } diff --git a/beacon_node/beacon_chain/tests/op_verification.rs b/beacon_node/beacon_chain/tests/op_verification.rs index f1230417d3..4bb2f07485 100644 --- a/beacon_node/beacon_chain/tests/op_verification.rs +++ b/beacon_node/beacon_chain/tests/op_verification.rs @@ -201,7 +201,11 @@ fn attester_slashing() { // Last half of the validators let second_half = (VALIDATOR_COUNT as u64 / 2..VALIDATOR_COUNT as u64).collect::>(); - let signer = |idx: u64, message: &[u8]| Signature::new(message, &KEYPAIRS[idx as usize].sk); + let signer = |idx: u64, message: &[u8]| { + KEYPAIRS[idx as usize] + .sk + .sign(Hash256::from_slice(&message)) + }; let make_slashing = |validators| { TestingAttesterSlashingBuilder::double_vote::<_, E>( diff --git a/beacon_node/eth1/src/deposit_log.rs b/beacon_node/eth1/src/deposit_log.rs index 44a437e2e3..ab5a85a02c 100644 --- a/beacon_node/eth1/src/deposit_log.rs +++ b/beacon_node/eth1/src/deposit_log.rs @@ -64,7 +64,7 @@ impl DepositLog { }; let signature_is_valid = deposit_pubkey_signature_message(&deposit_data, spec) - .map_or(false, |msg| deposit_signature_set(&msg).is_valid()); + .map_or(false, |msg| deposit_signature_set(&msg).verify()); Ok(DepositLog { deposit_data, diff --git a/beacon_node/eth1/tests/test.rs b/beacon_node/eth1/tests/test.rs index 1b75ebbac6..fa11aca46d 100644 --- a/beacon_node/eth1/tests/test.rs +++ b/beacon_node/eth1/tests/test.rs @@ -44,7 +44,7 @@ fn random_deposit_data() -> DepositData { pubkey: keypair.pk.into(), withdrawal_credentials: Hash256::zero(), amount: 32_000_000_000, - signature: Signature::empty_signature().into(), + signature: Signature::empty().into(), }; deposit.signature = deposit.create_signature(&keypair.sk, &MainnetEthSpec::default_spec()); diff --git a/beacon_node/eth2_libp2p/src/rpc/protocol.rs b/beacon_node/eth2_libp2p/src/rpc/protocol.rs index da284f9ef5..8125964959 100644 --- a/beacon_node/eth2_libp2p/src/rpc/protocol.rs +++ b/beacon_node/eth2_libp2p/src/rpc/protocol.rs @@ -33,13 +33,13 @@ lazy_static! { // same across different `EthSpec` implementations. pub static ref SIGNED_BEACON_BLOCK_MIN: usize = SignedBeaconBlock:: { message: BeaconBlock::empty(&MainnetEthSpec::default_spec()), - signature: Signature::empty_signature(), + signature: Signature::empty(), } .as_ssz_bytes() .len(); pub static ref SIGNED_BEACON_BLOCK_MAX: usize = SignedBeaconBlock:: { message: BeaconBlock::full(&MainnetEthSpec::default_spec()), - signature: Signature::empty_signature(), + signature: Signature::empty(), } .as_ssz_bytes() .len(); diff --git a/beacon_node/eth2_libp2p/tests/rpc_tests.rs b/beacon_node/eth2_libp2p/tests/rpc_tests.rs index 774137a3a3..cef3e1b2bd 100644 --- a/beacon_node/eth2_libp2p/tests/rpc_tests.rs +++ b/beacon_node/eth2_libp2p/tests/rpc_tests.rs @@ -127,7 +127,7 @@ async fn test_blocks_by_range_chunked_rpc() { let empty_block = BeaconBlock::empty(&spec); let empty_signed = SignedBeaconBlock { message: empty_block, - signature: Signature::empty_signature(), + signature: Signature::empty(), }; let rpc_response = Response::BlocksByRange(Some(Box::new(empty_signed))); @@ -238,7 +238,7 @@ async fn test_blocks_by_range_chunked_rpc_terminates_correctly() { let empty_block = BeaconBlock::empty(&spec); let empty_signed = SignedBeaconBlock { message: empty_block, - signature: Signature::empty_signature(), + signature: Signature::empty(), }; let rpc_response = Response::BlocksByRange(Some(Box::new(empty_signed))); @@ -365,7 +365,7 @@ async fn test_blocks_by_range_single_empty_rpc() { let empty_block = BeaconBlock::empty(&spec); let empty_signed = SignedBeaconBlock { message: empty_block, - signature: Signature::empty_signature(), + signature: Signature::empty(), }; let rpc_response = Response::BlocksByRange(Some(Box::new(empty_signed))); @@ -479,7 +479,7 @@ async fn test_blocks_by_root_chunked_rpc() { let full_block = BeaconBlock::full(&spec); let signed_full_block = SignedBeaconBlock { message: full_block, - signature: Signature::empty_signature(), + signature: Signature::empty(), }; let rpc_response = Response::BlocksByRoot(Some(Box::new(signed_full_block))); @@ -598,7 +598,7 @@ async fn test_blocks_by_root_chunked_rpc_terminates_correctly() { let full_block = BeaconBlock::full(&spec); let signed_full_block = SignedBeaconBlock { message: full_block, - signature: Signature::empty_signature(), + signature: Signature::empty(), }; let rpc_response = Response::BlocksByRoot(Some(Box::new(signed_full_block))); diff --git a/beacon_node/genesis/src/interop.rs b/beacon_node/genesis/src/interop.rs index c70ba4b29b..fcead9fbed 100644 --- a/beacon_node/genesis/src/interop.rs +++ b/beacon_node/genesis/src/interop.rs @@ -31,7 +31,7 @@ pub fn interop_genesis_state( withdrawal_credentials: withdrawal_credentials(&keypair.pk), pubkey: keypair.pk.clone().into(), amount, - signature: Signature::empty_signature().into(), + signature: Signature::empty().into(), }; data.signature = data.create_signature(&keypair.sk, spec); diff --git a/beacon_node/operation_pool/src/lib.rs b/beacon_node/operation_pool/src/lib.rs index 1ffd119f1e..5b664c8772 100644 --- a/beacon_node/operation_pool/src/lib.rs +++ b/beacon_node/operation_pool/src/lib.rs @@ -901,8 +901,11 @@ mod release_tests { } fn attester_slashing(&self, slashed_indices: &[u64]) -> AttesterSlashing { - let signer = - |idx: u64, message: &[u8]| Signature::new(message, &self.keypairs[idx as usize].sk); + let signer = |idx: u64, message: &[u8]| { + self.keypairs[idx as usize] + .sk + .sign(Hash256::from_slice(&message)) + }; TestingAttesterSlashingBuilder::double_vote( AttesterSlashingTestTask::Valid, slashed_indices, diff --git a/beacon_node/rest_api/src/helpers.rs b/beacon_node/rest_api/src/helpers.rs index 1f82849a8c..51a12d1bf0 100644 --- a/beacon_node/rest_api/src/helpers.rs +++ b/beacon_node/rest_api/src/helpers.rs @@ -99,7 +99,7 @@ pub fn parse_pubkey_bytes(string: &str) -> Result { if string.starts_with(PREFIX) { let pubkey_bytes = hex::decode(string.trim_start_matches(PREFIX)) .map_err(|e| ApiError::BadRequest(format!("Invalid hex string: {:?}", e)))?; - let pubkey = PublicKeyBytes::from_bytes(pubkey_bytes.as_slice()).map_err(|e| { + let pubkey = PublicKeyBytes::deserialize(pubkey_bytes.as_slice()).map_err(|e| { ApiError::BadRequest(format!("Unable to deserialize public key: {:?}.", e)) })?; Ok(pubkey) diff --git a/beacon_node/rest_api/tests/test.rs b/beacon_node/rest_api/tests/test.rs index 61b39e7d93..7ec81ffba0 100644 --- a/beacon_node/rest_api/tests/test.rs +++ b/beacon_node/rest_api/tests/test.rs @@ -62,7 +62,7 @@ fn get_randao_reveal( let epoch = slot.epoch(E::slots_per_epoch()); let domain = spec.get_domain(epoch, Domain::Randao, &fork, genesis_validators_root); let message = epoch.signing_root(domain); - Signature::new(message.as_bytes(), &keypair.sk) + keypair.sk.sign(message) } /// Signs the given block (assuming the given `beacon_chain` uses deterministic keypairs). @@ -468,7 +468,7 @@ fn validator_block_post() { // Try publishing the block without a signature, ensure it is flagged as invalid. let empty_sig_block = SignedBeaconBlock { message: block.clone(), - signature: Signature::empty_signature(), + signature: Signature::empty(), }; let publish_status = env .runtime() diff --git a/common/deposit_contract/src/lib.rs b/common/deposit_contract/src/lib.rs index c3f427e213..8ec0488263 100644 --- a/common/deposit_contract/src/lib.rs +++ b/common/deposit_contract/src/lib.rs @@ -98,7 +98,7 @@ mod tests { pubkey: keypair.pk.into(), withdrawal_credentials: Hash256::from_slice(&[42; 32]), amount: u64::max_value(), - signature: Signature::empty_signature().into(), + signature: Signature::empty().into(), }; deposit_data.signature = deposit_data.create_signature(&keypair.sk, spec); deposit_data diff --git a/common/eth2_interop_keypairs/Cargo.toml b/common/eth2_interop_keypairs/Cargo.toml index 0b105a2045..6062257cdb 100644 --- a/common/eth2_interop_keypairs/Cargo.toml +++ b/common/eth2_interop_keypairs/Cargo.toml @@ -11,10 +11,10 @@ lazy_static = "1.4.0" num-bigint = "0.3.0" eth2_hashing = "0.1.0" hex = "0.4.2" -milagro_bls = { git = "https://github.com/sigp/milagro_bls", tag = "v1.1.0" } serde_yaml = "0.8.11" serde = "1.0.110" serde_derive = "1.0.110" +bls = { path = "../../crypto/bls" } [dev-dependencies] base64 = "0.12.1" diff --git a/common/eth2_interop_keypairs/src/lib.rs b/common/eth2_interop_keypairs/src/lib.rs index f80b45c18a..ce11efa8f2 100644 --- a/common/eth2_interop_keypairs/src/lib.rs +++ b/common/eth2_interop_keypairs/src/lib.rs @@ -19,8 +19,8 @@ #[macro_use] extern crate lazy_static; +use bls::{Keypair, PublicKey, SecretKey}; use eth2_hashing::hash; -use milagro_bls::{Keypair, PublicKey, SecretKey}; use num_bigint::BigUint; use serde_derive::{Deserialize, Serialize}; use std::convert::TryInto; @@ -58,17 +58,14 @@ pub fn be_private_key(validator_index: usize) -> [u8; PRIVATE_KEY_BYTES] { /// Return a public and private keypair for a given `validator_index`. pub fn keypair(validator_index: usize) -> Keypair { - let sk = SecretKey::from_bytes(&be_private_key(validator_index)).unwrap_or_else(|_| { + let sk = SecretKey::deserialize(&be_private_key(validator_index)).unwrap_or_else(|_| { panic!( "Should build valid private key for validator index {}", validator_index ) }); - Keypair { - pk: PublicKey::from_secret_key(&sk), - sk, - } + Keypair::from_components(sk.public_key(), sk) } #[derive(Serialize, Deserialize)] @@ -93,18 +90,18 @@ impl TryInto for YamlKeypair { let sk = { let mut bytes = vec![0; PRIVATE_KEY_BYTES - privkey.len()]; bytes.extend_from_slice(&privkey); - SecretKey::from_bytes(&bytes) + SecretKey::deserialize(&bytes) .map_err(|e| format!("Failed to decode bytes into secret key: {:?}", e))? }; let pk = { let mut bytes = vec![0; PUBLIC_KEY_BYTES - pubkey.len()]; bytes.extend_from_slice(&pubkey); - PublicKey::from_bytes(&bytes) + PublicKey::deserialize(&bytes) .map_err(|e| format!("Failed to decode bytes into public key: {:?}", e))? }; - Ok(Keypair { pk, sk }) + Ok(Keypair::from_components(pk, sk)) } } diff --git a/common/eth2_interop_keypairs/tests/from_file.rs b/common/eth2_interop_keypairs/tests/from_file.rs index dd62d1f3e1..6f1e28cdbf 100644 --- a/common/eth2_interop_keypairs/tests/from_file.rs +++ b/common/eth2_interop_keypairs/tests/from_file.rs @@ -14,8 +14,8 @@ fn load_from_yaml() { keypairs.into_iter().enumerate().for_each(|(i, keypair)| { assert_eq!( - keypair, - reference_keypair(i), + keypair.pk, + reference_keypair(i).pk, "Decoded key {} does not match generated key", i ) diff --git a/common/eth2_interop_keypairs/tests/generation.rs b/common/eth2_interop_keypairs/tests/generation.rs index 02df8fe37e..6f0d40e852 100644 --- a/common/eth2_interop_keypairs/tests/generation.rs +++ b/common/eth2_interop_keypairs/tests/generation.rs @@ -53,6 +53,6 @@ fn reference_public_keys() { "Reference should be 48 bytes (public key size)" ); - assert_eq!(pair.pk.as_bytes().to_vec(), reference); + assert_eq!(pair.pk.serialize().to_vec(), reference); }); } diff --git a/common/rest_types/src/validator.rs b/common/rest_types/src/validator.rs index a6b81b155f..2b0f077298 100644 --- a/common/rest_types/src/validator.rs +++ b/common/rest_types/src/validator.rs @@ -1,8 +1,6 @@ -use bls::{PublicKey, PublicKeyBytes, Signature}; -use eth2_hashing::hash; +use bls::{PublicKey, PublicKeyBytes}; use serde::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; -use std::convert::TryInto; use types::{CommitteeIndex, Epoch, Slot}; /// A Validator duty with the validator public key represented a `PublicKeyBytes`. @@ -36,23 +34,6 @@ pub struct ValidatorDutyBase { } impl ValidatorDutyBase { - /// Given a `slot_signature` determines if the validator of this duty is an aggregator. - // Note that we assume the signature is for the associated pubkey to avoid the signature - // verification - pub fn is_aggregator(&self, slot_signature: &Signature) -> bool { - if let Some(modulo) = self.aggregator_modulo { - let signature_hash = hash(&slot_signature.as_bytes()); - let signature_hash_int = u64::from_le_bytes( - signature_hash[0..8] - .try_into() - .expect("first 8 bytes of signature should always convert to fixed array"), - ); - signature_hash_int % modulo == 0 - } else { - false - } - } - /// Return `true` if these validator duties are equal, ignoring their `block_proposal_slots`. pub fn eq_ignoring_proposal_slots(&self, other: &Self) -> bool where @@ -95,11 +76,14 @@ pub struct ValidatorSubscription { #[cfg(test)] mod test { use super::*; + use bls::SecretKey; #[test] fn eq_ignoring_proposal_slots() { + let validator_pubkey = SecretKey::deserialize(&[1; 32]).unwrap().public_key(); + let duty1 = ValidatorDuty { - validator_pubkey: PublicKey::default(), + validator_pubkey, validator_index: Some(10), attestation_slot: Some(Slot::new(50)), attestation_committee_index: Some(2), diff --git a/common/validator_dir/Cargo.toml b/common/validator_dir/Cargo.toml index 6ca3585b04..9a833d2f50 100644 --- a/common/validator_dir/Cargo.toml +++ b/common/validator_dir/Cargo.toml @@ -5,7 +5,6 @@ authors = ["Paul Hauner "] edition = "2018" [features] -unencrypted_keys = [] insecure_keys = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/validator_dir/src/builder.rs b/common/validator_dir/src/builder.rs index a33d910b5a..4cdb75a5f5 100644 --- a/common/validator_dir/src/builder.rs +++ b/common/validator_dir/src/builder.rs @@ -170,7 +170,7 @@ impl<'a> Builder<'a> { pubkey: voting_keypair.pk.clone().into(), withdrawal_credentials, amount, - signature: Signature::empty_signature().into(), + signature: Signature::empty().into(), }; deposit_data.signature = deposit_data.create_signature(&voting_keypair.sk, &spec); @@ -220,7 +220,7 @@ impl<'a> Builder<'a> { // Write the withdrawal password to file. write_password_to_file( self.password_dir - .join(withdrawal_keypair.pk.as_hex_string()), + .join(withdrawal_keypair.pk.to_hex_string()), withdrawal_password.as_bytes(), )?; diff --git a/common/validator_dir/src/lib.rs b/common/validator_dir/src/lib.rs index e0363182ef..90def3d63a 100644 --- a/common/validator_dir/src/lib.rs +++ b/common/validator_dir/src/lib.rs @@ -10,7 +10,6 @@ mod builder; pub mod insecure_keys; mod manager; -pub mod unencrypted_keys; mod validator_dir; pub use crate::validator_dir::{Error, Eth1DepositData, ValidatorDir, ETH1_DEPOSIT_TX_HASH_FILE}; diff --git a/common/validator_dir/src/manager.rs b/common/validator_dir/src/manager.rs index 51f96c36f8..b6f59fa282 100644 --- a/common/validator_dir/src/manager.rs +++ b/common/validator_dir/src/manager.rs @@ -107,14 +107,14 @@ impl Manager { info!( log, "Decrypted validator keystore"; - "voting_pubkey" => kp.pk.as_hex_string() + "voting_pubkey" => kp.pk.to_hex_string() ); if lockfile_existed { warn!( log, "Lockfile already existed"; "msg" => "ensure no other validator client is running on this host", - "voting_pubkey" => kp.pk.as_hex_string() + "voting_pubkey" => kp.pk.to_hex_string() ); } } @@ -147,7 +147,7 @@ impl Manager { info!( log, "Decrypted validator keystore"; - "voting_pubkey" => kp.pk.as_hex_string() + "voting_pubkey" => kp.pk.to_hex_string() ) } (kp, v) diff --git a/common/validator_dir/src/unencrypted_keys.rs b/common/validator_dir/src/unencrypted_keys.rs deleted file mode 100644 index 53e5c5fdf4..0000000000 --- a/common/validator_dir/src/unencrypted_keys.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! The functionality in this module is only required for backward compatibility with the old -//! method of key generation (unencrypted, SSZ-encoded keypairs). It should be removed as soon as -//! we're confident that no-one is using these keypairs anymore (hopefully mid-June 2020). -#![cfg(feature = "unencrypted_keys")] - -use bls::{BLS_PUBLIC_KEY_BYTE_SIZE as PK_LEN, BLS_SECRET_KEY_BYTE_SIZE as SK_LEN}; -use eth2_keystore::PlainText; -use std::fs::File; -use std::io::Read; -use std::path::Path; -use types::{Keypair, PublicKey, SecretKey}; - -/// Read a keypair from disk, using the old format where keys were stored as unencrypted -/// SSZ-encoded keypairs. -/// -/// This only exists as compatibility with the old scheme and should not be implemented on any new -/// features. -pub fn load_unencrypted_keypair>(path: P) -> Result { - let path = path.as_ref(); - - if !path.exists() { - return Err(format!("Keypair file does not exist: {:?}", path)); - } - - let mut bytes = vec![]; - - File::open(&path) - .map_err(|e| format!("Unable to open keypair file: {}", e))? - .read_to_end(&mut bytes) - .map_err(|e| format!("Unable to read keypair file: {}", e))?; - - let bytes: PlainText = bytes.into(); - - if bytes.len() != PK_LEN + SK_LEN { - return Err(format!("Invalid keypair byte length: {}", bytes.len())); - } - - let pk_bytes = &bytes.as_bytes()[..PK_LEN]; - let sk_bytes = &bytes.as_bytes()[PK_LEN..]; - - let pk = PublicKey::from_bytes(pk_bytes) - .map_err(|e| format!("Unable to decode public key: {:?}", e))?; - - let sk = SecretKey::from_bytes(sk_bytes) - .map_err(|e| format!("Unable to decode secret key: {:?}", e))?; - - Ok(Keypair { pk, sk }) -} diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index bcb09878bc..65ac70a5f1 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -3,7 +3,6 @@ use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid, IntoWithI use rayon::prelude::*; use safe_arith::{ArithError, SafeArith}; use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set}; -use std::convert::TryInto; use tree_hash::TreeHash; use types::*; @@ -215,7 +214,7 @@ pub fn verify_block_signature( block_root, spec )? - .is_valid(), + .verify(), HeaderInvalid::ProposalSignatureInvalid ); @@ -235,8 +234,7 @@ pub fn process_randao( if verify_signatures.is_true() { // Verify RANDAO reveal signature. block_verify!( - randao_signature_set(state, |i| get_pubkey_from_state(state, i), block, spec)? - .is_valid(), + randao_signature_set(state, |i| get_pubkey_from_state(state, i), block, spec)?.verify(), BlockProcessingError::RandaoSignatureInvalid ); } @@ -452,7 +450,7 @@ pub fn process_deposit( // depositing validator already exists in the registry. state.update_pubkey_cache()?; - let pubkey: PublicKey = match (&deposit.data.pubkey).try_into() { + let pubkey: PublicKey = match deposit.data.pubkey.decompress() { Err(_) => return Ok(()), //bad public key => return early Ok(k) => k, }; diff --git a/consensus/state_processing/src/per_block_processing/block_processing_builder.rs b/consensus/state_processing/src/per_block_processing/block_processing_builder.rs index fc44964920..8ef2a18d22 100644 --- a/consensus/state_processing/src/per_block_processing/block_processing_builder.rs +++ b/consensus/state_processing/src/per_block_processing/block_processing_builder.rs @@ -126,7 +126,7 @@ impl<'a, T: EthSpec> BlockProcessingBuilder<'a, T> { let mut attestation = Attestation { aggregation_bits: BitList::with_capacity(committee.committee.len()).unwrap(), data, - signature: AggregateSignature::new(), + signature: AggregateSignature::empty(), }; for (i, &validator_index) in committee.committee.iter().enumerate() { diff --git a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs index 560695d4ca..d477063ebc 100644 --- a/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs +++ b/consensus/state_processing/src/per_block_processing/block_signature_verifier.rs @@ -71,7 +71,7 @@ where get_pubkey: F, state: &'a BeaconState, spec: &'a ChainSpec, - sets: Vec, + sets: Vec>, } impl<'a, T, F> BlockSignatureVerifier<'a, T, F> @@ -129,7 +129,7 @@ where .sets .into_par_iter() .chunks(num_chunks) - .map(verify_signature_sets) + .map(|chunk| verify_signature_sets(chunk.iter())) .reduce(|| true, |current, this| current && this); if result { diff --git a/consensus/state_processing/src/per_block_processing/is_valid_indexed_attestation.rs b/consensus/state_processing/src/per_block_processing/is_valid_indexed_attestation.rs index c9ac210dfe..66e364723b 100644 --- a/consensus/state_processing/src/per_block_processing/is_valid_indexed_attestation.rs +++ b/consensus/state_processing/src/per_block_processing/is_valid_indexed_attestation.rs @@ -45,7 +45,7 @@ pub fn is_valid_indexed_attestation( &indexed_attestation, spec )? - .is_valid(), + .verify(), Invalid::BadSignature ); } diff --git a/consensus/state_processing/src/per_block_processing/signature_sets.rs b/consensus/state_processing/src/per_block_processing/signature_sets.rs index 370145ec09..80d1b936a8 100644 --- a/consensus/state_processing/src/per_block_processing/signature_sets.rs +++ b/consensus/state_processing/src/per_block_processing/signature_sets.rs @@ -5,7 +5,6 @@ use bls::SignatureSet; use ssz::DecodeError; use std::borrow::Cow; -use std::convert::TryInto; use tree_hash::TreeHash; use types::{ AggregateSignature, AttesterSlashing, BeaconBlock, BeaconState, BeaconStateError, ChainSpec, @@ -56,7 +55,7 @@ where .validators .get(validator_index) .and_then(|v| { - let pk: Option = (&v.pubkey).try_into().ok(); + let pk: Option = v.pubkey.decompress().ok(); pk }) .map(Cow::Owned) @@ -69,7 +68,7 @@ pub fn block_proposal_signature_set<'a, T, F>( signed_block: &'a SignedBeaconBlock, block_root: Option, spec: &'a ChainSpec, -) -> Result +) -> Result> where T: EthSpec, F: Fn(usize) -> Option>, @@ -101,10 +100,10 @@ where block.signing_root(domain) }; - Ok(SignatureSet::single( + Ok(SignatureSet::single_pubkey( &signed_block.signature, get_pubkey(proposer_index).ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?, - message.as_bytes().to_vec(), + message, )) } @@ -114,7 +113,7 @@ pub fn randao_signature_set<'a, T, F>( get_pubkey: F, block: &'a BeaconBlock, spec: &'a ChainSpec, -) -> Result +) -> Result> where T: EthSpec, F: Fn(usize) -> Option>, @@ -130,10 +129,10 @@ where let message = block.slot.epoch(T::slots_per_epoch()).signing_root(domain); - Ok(SignatureSet::single( + Ok(SignatureSet::single_pubkey( &block.body.randao_reveal, get_pubkey(proposer_index).ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?, - message.as_bytes().to_vec(), + message, )) } @@ -143,7 +142,7 @@ pub fn proposer_slashing_signature_set<'a, T, F>( get_pubkey: F, proposer_slashing: &'a ProposerSlashing, spec: &'a ChainSpec, -) -> Result<(SignatureSet, SignatureSet)> +) -> Result<(SignatureSet<'a>, SignatureSet<'a>)> where T: EthSpec, F: Fn(usize) -> Option>, @@ -174,7 +173,7 @@ fn block_header_signature_set<'a, T: EthSpec>( signed_header: &'a SignedBeaconBlockHeader, pubkey: Cow<'a, PublicKey>, spec: &'a ChainSpec, -) -> Result { +) -> Result> { let domain = spec.get_domain( signed_header.message.slot.epoch(T::slots_per_epoch()), Domain::BeaconProposer, @@ -182,13 +181,9 @@ fn block_header_signature_set<'a, T: EthSpec>( state.genesis_validators_root, ); - let message = signed_header - .message - .signing_root(domain) - .as_bytes() - .to_vec(); + let message = signed_header.message.signing_root(domain); - Ok(SignatureSet::single( + Ok(SignatureSet::single_pubkey( &signed_header.signature, pubkey, message, @@ -202,7 +197,7 @@ pub fn indexed_attestation_signature_set<'a, 'b, T, F>( signature: &'a AggregateSignature, indexed_attestation: &'b IndexedAttestation, spec: &'a ChainSpec, -) -> Result +) -> Result> where T: EthSpec, F: Fn(usize) -> Option>, @@ -224,9 +219,8 @@ where ); let message = indexed_attestation.data.signing_root(domain); - let message = message.as_bytes().to_vec(); - Ok(SignatureSet::new(signature, pubkeys, message)) + Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message)) } /// Returns the signature set for the given `indexed_attestation` but pubkeys are supplied directly @@ -238,7 +232,7 @@ pub fn indexed_attestation_signature_set_from_pubkeys<'a, 'b, T, F>( fork: &Fork, genesis_validators_root: Hash256, spec: &'a ChainSpec, -) -> Result +) -> Result> where T: EthSpec, F: Fn(usize) -> Option>, @@ -260,9 +254,8 @@ where ); let message = indexed_attestation.data.signing_root(domain); - let message = message.as_bytes().to_vec(); - Ok(SignatureSet::new(signature, pubkeys, message)) + Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message)) } /// Returns the signature set for the given `attester_slashing` and corresponding `pubkeys`. @@ -271,7 +264,7 @@ pub fn attester_slashing_signature_sets<'a, T, F>( get_pubkey: F, attester_slashing: &'a AttesterSlashing, spec: &'a ChainSpec, -) -> Result<(SignatureSet, SignatureSet)> +) -> Result<(SignatureSet<'a>, SignatureSet<'a>)> where T: EthSpec, F: Fn(usize) -> Option> + Clone, @@ -300,28 +293,24 @@ where pub fn deposit_pubkey_signature_message( deposit_data: &DepositData, spec: &ChainSpec, -) -> Option<(PublicKey, Signature, Vec)> { - let pubkey = (&deposit_data.pubkey).try_into().ok()?; - let signature = (&deposit_data.signature).try_into().ok()?; +) -> Option<(PublicKey, Signature, Hash256)> { + let pubkey = deposit_data.pubkey.decompress().ok()?; + let signature = deposit_data.signature.decompress().ok()?; let domain = spec.get_deposit_domain(); - let message = deposit_data - .as_deposit_message() - .signing_root(domain) - .as_bytes() - .to_vec(); + let message = deposit_data.as_deposit_message().signing_root(domain); Some((pubkey, signature, message)) } /// Returns the signature set for some set of deposit signatures, made with /// `deposit_pubkey_signature_message`. pub fn deposit_signature_set( - pubkey_signature_message: &(PublicKey, Signature, Vec), + pubkey_signature_message: &(PublicKey, Signature, Hash256), ) -> SignatureSet { let (pubkey, signature, message) = pubkey_signature_message; // Note: Deposits are valid across forks, thus the deposit domain is computed // with the fok zeroed. - SignatureSet::single(&signature, Cow::Borrowed(pubkey), message.clone()) + SignatureSet::single_pubkey(signature, Cow::Borrowed(pubkey), *message) } /// Returns a signature set that is valid if the `SignedVoluntaryExit` was signed by the indicated @@ -331,7 +320,7 @@ pub fn exit_signature_set<'a, T, F>( get_pubkey: F, signed_exit: &'a SignedVoluntaryExit, spec: &'a ChainSpec, -) -> Result +) -> Result> where T: EthSpec, F: Fn(usize) -> Option>, @@ -346,9 +335,9 @@ where state.genesis_validators_root, ); - let message = exit.signing_root(domain).as_bytes().to_vec(); + let message = exit.signing_root(domain); - Ok(SignatureSet::single( + Ok(SignatureSet::single_pubkey( &signed_exit.signature, get_pubkey(proposer_index).ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?, message, @@ -361,7 +350,7 @@ pub fn signed_aggregate_selection_proof_signature_set<'a, T, F>( fork: &Fork, genesis_validators_root: Hash256, spec: &'a ChainSpec, -) -> Result +) -> Result> where T: EthSpec, F: Fn(usize) -> Option>, @@ -374,11 +363,11 @@ where fork, genesis_validators_root, ); - let message = slot.signing_root(domain).as_bytes().to_vec(); + let message = slot.signing_root(domain); let signature = &signed_aggregate_and_proof.message.selection_proof; let validator_index = signed_aggregate_and_proof.message.aggregator_index; - Ok(SignatureSet::single( + Ok(SignatureSet::single_pubkey( signature, get_pubkey(validator_index as usize) .ok_or_else(|| Error::ValidatorUnknown(validator_index))?, @@ -392,7 +381,7 @@ pub fn signed_aggregate_signature_set<'a, T, F>( fork: &Fork, genesis_validators_root: Hash256, spec: &'a ChainSpec, -) -> Result +) -> Result> where T: EthSpec, F: Fn(usize) -> Option>, @@ -410,15 +399,11 @@ where fork, genesis_validators_root, ); - let message = signed_aggregate_and_proof - .message - .signing_root(domain) - .as_bytes() - .to_vec(); + let message = signed_aggregate_and_proof.message.signing_root(domain); let signature = &signed_aggregate_and_proof.signature; let validator_index = signed_aggregate_and_proof.message.aggregator_index; - Ok(SignatureSet::single( + Ok(SignatureSet::single_pubkey( signature, get_pubkey(validator_index as usize) .ok_or_else(|| Error::ValidatorUnknown(validator_index))?, diff --git a/consensus/state_processing/src/per_block_processing/verify_deposit.rs b/consensus/state_processing/src/per_block_processing/verify_deposit.rs index aaaf54bb1b..510b73b2a8 100644 --- a/consensus/state_processing/src/per_block_processing/verify_deposit.rs +++ b/consensus/state_processing/src/per_block_processing/verify_deposit.rs @@ -21,7 +21,7 @@ pub fn verify_deposit_signature(deposit_data: &DepositData, spec: &ChainSpec) -> .ok_or_else(|| error(DepositInvalid::BadBlsBytes))?; verify!( - deposit_signature_set(&deposit_signature_message).is_valid(), + deposit_signature_set(&deposit_signature_message).verify(), DepositInvalid::BadSignature ); diff --git a/consensus/state_processing/src/per_block_processing/verify_exit.rs b/consensus/state_processing/src/per_block_processing/verify_exit.rs index d621d3568e..c77ffe5361 100644 --- a/consensus/state_processing/src/per_block_processing/verify_exit.rs +++ b/consensus/state_processing/src/per_block_processing/verify_exit.rs @@ -93,7 +93,7 @@ fn verify_exit_parametric( signed_exit, spec )? - .is_valid(), + .verify(), ExitInvalid::BadSignature ); } diff --git a/consensus/state_processing/src/per_block_processing/verify_proposer_slashing.rs b/consensus/state_processing/src/per_block_processing/verify_proposer_slashing.rs index f6f506ffcd..ffc9ccbd8e 100644 --- a/consensus/state_processing/src/per_block_processing/verify_proposer_slashing.rs +++ b/consensus/state_processing/src/per_block_processing/verify_proposer_slashing.rs @@ -57,8 +57,8 @@ pub fn verify_proposer_slashing( proposer_slashing, spec, )?; - verify!(signature_set_1.is_valid(), Invalid::BadProposal1Signature); - verify!(signature_set_2.is_valid(), Invalid::BadProposal2Signature); + verify!(signature_set_1.verify(), Invalid::BadProposal1Signature); + verify!(signature_set_2.verify(), Invalid::BadProposal2Signature); } Ok(()) diff --git a/consensus/state_processing/tests/tests.rs b/consensus/state_processing/tests/tests.rs index d2d4b432b2..cc136850e0 100644 --- a/consensus/state_processing/tests/tests.rs +++ b/consensus/state_processing/tests/tests.rs @@ -1,11 +1,11 @@ -#![cfg(not(feature = "fake_crypto"))] +// #![cfg(not(feature = "fake_crypto"))] use state_processing::{ per_block_processing, test_utils::BlockBuilder, BlockProcessingError, BlockSignatureStrategy, }; use types::{ - AggregateSignature, BeaconState, ChainSpec, EthSpec, Keypair, MinimalEthSpec, Signature, - SignedBeaconBlock, Slot, + AggregateSignature, BeaconState, ChainSpec, EthSpec, Hash256, Keypair, MinimalEthSpec, + Signature, SignedBeaconBlock, Slot, }; const VALIDATOR_COUNT: usize = 64; @@ -92,15 +92,15 @@ where // TODO: use lazy static fn agg_sig() -> AggregateSignature { - let mut agg_sig = AggregateSignature::new(); - agg_sig.add(&sig()); + let mut agg_sig = AggregateSignature::infinity(); + agg_sig.add_assign(&sig()); agg_sig } // TODO: use lazy static fn sig() -> Signature { let keypair = Keypair::random(); - Signature::new(&[42, 42], &keypair.sk) + keypair.sk.sign(Hash256::from_low_u64_be(42)) } type TestEthSpec = MinimalEthSpec; diff --git a/consensus/types/src/aggregate_and_proof.rs b/consensus/types/src/aggregate_and_proof.rs index 20553f86ab..737c891c9f 100644 --- a/consensus/types/src/aggregate_and_proof.rs +++ b/consensus/types/src/aggregate_and_proof.rs @@ -73,8 +73,7 @@ impl AggregateAndProof { genesis_validators_root, ); let message = self.aggregate.data.slot.signing_root(domain); - self.selection_proof - .verify(message.as_bytes(), validator_pubkey) + self.selection_proof.verify(validator_pubkey, message) } } diff --git a/consensus/types/src/attestation.rs b/consensus/types/src/attestation.rs index 6a03a43eb5..ed7b14a341 100644 --- a/consensus/types/src/attestation.rs +++ b/consensus/types/src/attestation.rs @@ -1,6 +1,6 @@ use super::{ AggregateSignature, AttestationData, BitList, ChainSpec, Domain, EthSpec, Fork, SecretKey, - Signature, SignedRoot, + SignedRoot, }; use crate::{test_utils::TestRandom, Hash256}; use safe_arith::ArithError; @@ -44,7 +44,7 @@ impl Attestation { debug_assert!(self.signers_disjoint_from(other)); self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); - self.signature.add_aggregate(&other.signature); + self.signature.add_assign_aggregate(&other.signature); } /// Signs `self`, setting the `committee_position`'th bit of `aggregation_bits` to `true`. @@ -77,8 +77,7 @@ impl Attestation { ); let message = self.data.signing_root(domain); - self.signature - .add(&Signature::new(message.as_bytes(), secret_key)); + self.signature.add_assign(&secret_key.sign(message)); Ok(()) } diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index 1a31da8752..eeb10458bf 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -35,7 +35,7 @@ impl BeaconBlock { parent_root: Hash256::zero(), state_root: Hash256::zero(), body: BeaconBlockBody { - randao_reveal: Signature::empty_signature(), + randao_reveal: Signature::empty(), eth1_data: Eth1Data { deposit_root: Hash256::zero(), block_hash: Hash256::zero(), @@ -63,7 +63,7 @@ impl BeaconBlock { let signed_header = SignedBeaconBlockHeader { message: header, - signature: Signature::empty_signature(), + signature: Signature::empty(), }; let indexed_attestation: IndexedAttestation = IndexedAttestation { attesting_indices: VariableList::new(vec![ @@ -72,7 +72,7 @@ impl BeaconBlock { ]) .unwrap(), data: AttestationData::default(), - signature: AggregateSignature::new(), + signature: AggregateSignature::empty(), }; let deposit_data = DepositData { @@ -95,7 +95,7 @@ impl BeaconBlock { aggregation_bits: BitList::with_capacity(T::MaxValidatorsPerCommittee::to_usize()) .unwrap(), data: AttestationData::default(), - signature: AggregateSignature::new(), + signature: AggregateSignature::empty(), }; let deposit = Deposit { @@ -110,7 +110,7 @@ impl BeaconBlock { let signed_voluntary_exit = SignedVoluntaryExit { message: voluntary_exit, - signature: Signature::empty_signature(), + signature: Signature::empty(), }; let mut block: BeaconBlock = BeaconBlock::empty(spec); @@ -200,7 +200,7 @@ impl BeaconBlock { genesis_validators_root, ); let message = self.signing_root(domain); - let signature = Signature::new(message.as_bytes(), secret_key); + let signature = secret_key.sign(message); SignedBeaconBlock { message: self, signature, diff --git a/consensus/types/src/beacon_block_header.rs b/consensus/types/src/beacon_block_header.rs index e542f06f13..04a20e56d3 100644 --- a/consensus/types/src/beacon_block_header.rs +++ b/consensus/types/src/beacon_block_header.rs @@ -54,7 +54,7 @@ impl BeaconBlockHeader { let epoch = self.slot.epoch(E::slots_per_epoch()); let domain = spec.get_domain(epoch, Domain::BeaconProposer, fork, genesis_validators_root); let message = self.signing_root(domain); - let signature = Signature::new(message.as_bytes(), secret_key); + let signature = secret_key.sign(message); SignedBeaconBlockHeader { message: self, signature, diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index eca1543d44..cbbc343294 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -10,7 +10,7 @@ use int_to_bytes::{int_to_bytes4, int_to_bytes8}; use pubkey_cache::PubkeyCache; use safe_arith::{ArithError, SafeArith}; use serde_derive::{Deserialize, Serialize}; -use ssz::ssz_encode; +use ssz::{ssz_encode, Encode}; use ssz_derive::{Decode, Encode}; use ssz_types::{typenum::Unsigned, BitVector, FixedVector}; use std::convert::TryInto; @@ -496,7 +496,7 @@ impl BeaconState { 1, (committee.committee.len() as u64).safe_div(spec.target_aggregators_per_committee)?, ); - let signature_hash = hash(&slot_signature.as_bytes()); + let signature_hash = hash(&slot_signature.as_ssz_bytes()); let signature_hash_int = u64::from_le_bytes( signature_hash[0..8] .try_into() diff --git a/consensus/types/src/deposit_data.rs b/consensus/types/src/deposit_data.rs index 2d55c3245b..ce72c362e2 100644 --- a/consensus/types/src/deposit_data.rs +++ b/consensus/types/src/deposit_data.rs @@ -38,7 +38,7 @@ impl DepositData { let domain = spec.get_deposit_domain(); let msg = self.as_deposit_message().signing_root(domain); - SignatureBytes::from(Signature::new(msg.as_bytes(), secret_key)) + SignatureBytes::from(secret_key.sign(msg)) } } diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 515b93005d..19697118a5 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -96,8 +96,7 @@ pub type Address = H160; pub type ForkVersion = [u8; 4]; pub use bls::{ - AggregatePublicKey, AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey, - Signature, SignatureBytes, + AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey, Signature, SignatureBytes, }; pub use ssz_types::{typenum, typenum::Unsigned, BitList, BitVector, FixedVector, VariableList}; pub use utils::{Graffiti, GRAFFITI_BYTES_LEN}; diff --git a/consensus/types/src/selection_proof.rs b/consensus/types/src/selection_proof.rs index ea773d7066..508b261de8 100644 --- a/consensus/types/src/selection_proof.rs +++ b/consensus/types/src/selection_proof.rs @@ -27,7 +27,7 @@ impl SelectionProof { ); let message = slot.signing_root(domain); - Self(Signature::new(message.as_bytes(), secret_key)) + Self(secret_key.sign(message)) } /// Returns the "modulo" used for determining if a `SelectionProof` elects an aggregator. @@ -74,7 +74,7 @@ impl SelectionProof { ); let message = slot.signing_root(domain); - self.0.verify(message.as_bytes(), pubkey) + self.0.verify(pubkey, message) } } diff --git a/consensus/types/src/signed_aggregate_and_proof.rs b/consensus/types/src/signed_aggregate_and_proof.rs index e3eef474de..f612d7074a 100644 --- a/consensus/types/src/signed_aggregate_and_proof.rs +++ b/consensus/types/src/signed_aggregate_and_proof.rs @@ -57,7 +57,7 @@ impl SignedAggregateAndProof { SignedAggregateAndProof { message, - signature: Signature::new(signing_message.as_bytes(), &secret_key), + signature: secret_key.sign(signing_message), } } @@ -77,7 +77,7 @@ impl SignedAggregateAndProof { genesis_validators_root, ); let message = self.message.signing_root(domain); - self.signature.verify(message.as_bytes(), validator_pubkey) + self.signature.verify(validator_pubkey, message) } /// Verifies the signature of the `AggregateAndProof` as well the underlying selection_proof in diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index f4e52610c9..9ab3425421 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -78,7 +78,7 @@ impl SignedBeaconBlock { self.message.signing_root(domain) }; - self.signature.verify(message.as_bytes(), pubkey) + self.signature.verify(pubkey, message) } /// Convenience accessor for the block's slot. diff --git a/consensus/types/src/test_utils/builders/testing_attestation_builder.rs b/consensus/types/src/test_utils/builders/testing_attestation_builder.rs index 6c08346bb8..a2e5f5f536 100644 --- a/consensus/types/src/test_utils/builders/testing_attestation_builder.rs +++ b/consensus/types/src/test_utils/builders/testing_attestation_builder.rs @@ -36,7 +36,7 @@ impl TestingAttestationBuilder { let attestation = Attestation { aggregation_bits, data: data_builder.build(), - signature: AggregateSignature::new(), + signature: AggregateSignature::empty(), }; Self { diff --git a/consensus/types/src/test_utils/builders/testing_attester_slashing_builder.rs b/consensus/types/src/test_utils/builders/testing_attester_slashing_builder.rs index bc90bdb8a9..7e940a3b53 100644 --- a/consensus/types/src/test_utils/builders/testing_attester_slashing_builder.rs +++ b/consensus/types/src/test_utils/builders/testing_attester_slashing_builder.rs @@ -64,7 +64,7 @@ impl TestingAttesterSlashingBuilder { validator_indices.to_vec().into() }, data: data_1, - signature: AggregateSignature::new(), + signature: AggregateSignature::empty(), }; let mut attestation_2 = IndexedAttestation { @@ -76,7 +76,7 @@ impl TestingAttesterSlashingBuilder { validator_indices.to_vec().into() }, data: data_2, - signature: AggregateSignature::new(), + signature: AggregateSignature::empty(), }; let add_signatures = |attestation: &mut IndexedAttestation| { @@ -90,7 +90,7 @@ impl TestingAttesterSlashingBuilder { for validator_index in validator_indices { let signature = signer(*validator_index, message.as_bytes()); - attestation.signature.add(&signature); + attestation.signature.add_assign(&signature); } }; diff --git a/consensus/types/src/test_utils/builders/testing_beacon_block_builder.rs b/consensus/types/src/test_utils/builders/testing_beacon_block_builder.rs index ef98ee834c..c396d8c966 100644 --- a/consensus/types/src/test_utils/builders/testing_beacon_block_builder.rs +++ b/consensus/types/src/test_utils/builders/testing_beacon_block_builder.rs @@ -101,7 +101,7 @@ impl TestingBeaconBlockBuilder { let epoch = self.block.slot.epoch(T::slots_per_epoch()); let domain = spec.get_domain(epoch, Domain::Randao, fork, genesis_validators_root); let message = epoch.signing_root(domain); - self.block.body.randao_reveal = Signature::new(message.as_bytes(), sk); + self.block.body.randao_reveal = sk.sign(message); } /// Has the randao reveal been set? @@ -368,7 +368,7 @@ impl TestingBeaconBlockBuilder { pub fn build_without_signing(self) -> SignedBeaconBlock { SignedBeaconBlock { message: self.block, - signature: Signature::empty_signature(), + signature: Signature::empty(), } } } @@ -410,7 +410,7 @@ pub fn build_double_vote_attester_slashing( .iter() .position(|&i| i == validator_index) .expect("Unable to find attester slashing key"); - Signature::new(message, secret_keys[key_index]) + secret_keys[key_index].sign(Hash256::from_slice(message)) }; TestingAttesterSlashingBuilder::double_vote( diff --git a/consensus/types/src/test_utils/builders/testing_deposit_builder.rs b/consensus/types/src/test_utils/builders/testing_deposit_builder.rs index 69da25997e..2ece83f7f9 100644 --- a/consensus/types/src/test_utils/builders/testing_deposit_builder.rs +++ b/consensus/types/src/test_utils/builders/testing_deposit_builder.rs @@ -41,7 +41,7 @@ impl TestingDepositBuilder { // Creating invalid public key bytes let mut public_key_bytes: Vec = vec![0; 48]; public_key_bytes[0] = 255; - pubkeybytes = PublicKeyBytes::from_bytes(&public_key_bytes).unwrap(); + pubkeybytes = PublicKeyBytes::deserialize(&public_key_bytes).unwrap(); } DepositTestTask::BadSig => secret_key = new_key.sk, _ => (), diff --git a/consensus/types/src/test_utils/builders/testing_proposer_slashing_builder.rs b/consensus/types/src/test_utils/builders/testing_proposer_slashing_builder.rs index 88e84a3995..51c2aeaf49 100644 --- a/consensus/types/src/test_utils/builders/testing_proposer_slashing_builder.rs +++ b/consensus/types/src/test_utils/builders/testing_proposer_slashing_builder.rs @@ -37,7 +37,7 @@ impl TestingProposerSlashingBuilder { state_root: hash_1, body_root: hash_1, }, - signature: Signature::empty_signature(), + signature: Signature::empty(), }; let slot_2 = if test_task == ProposerSlashingTestTask::ProposalEpochMismatch { @@ -52,7 +52,7 @@ impl TestingProposerSlashingBuilder { slot: slot_2, ..signed_header_1.message }, - signature: Signature::empty_signature(), + signature: Signature::empty(), }; if test_task != ProposerSlashingTestTask::BadProposal1Signature { diff --git a/consensus/types/src/test_utils/generate_deterministic_keypairs.rs b/consensus/types/src/test_utils/generate_deterministic_keypairs.rs index 188ce075d3..92534369ee 100644 --- a/consensus/types/src/test_utils/generate_deterministic_keypairs.rs +++ b/consensus/types/src/test_utils/generate_deterministic_keypairs.rs @@ -27,20 +27,10 @@ pub fn generate_deterministic_keypairs(validator_count: usize) -> Vec { /// /// This is used for testing only, and not to be used in production! pub fn generate_deterministic_keypair(validator_index: usize) -> Keypair { - let raw = keypair(validator_index); - Keypair { - pk: PublicKey::from_raw(raw.pk), - sk: SecretKey::from_raw(raw.sk), - } + keypair(validator_index) } /// Loads a list of keypairs from file. pub fn load_keypairs_from_yaml(path: PathBuf) -> Result, String> { - Ok(keypairs_from_yaml_file(path)? - .into_iter() - .map(|raw| Keypair { - pk: PublicKey::from_raw(raw.pk), - sk: SecretKey::from_raw(raw.sk), - }) - .collect()) + keypairs_from_yaml_file(path) } diff --git a/consensus/types/src/test_utils/test_random/aggregate_signature.rs b/consensus/types/src/test_utils/test_random/aggregate_signature.rs index a346d2d887..5d3c916b9a 100644 --- a/consensus/types/src/test_utils/test_random/aggregate_signature.rs +++ b/consensus/types/src/test_utils/test_random/aggregate_signature.rs @@ -4,8 +4,8 @@ use bls::{AggregateSignature, Signature}; impl TestRandom for AggregateSignature { fn random_for_test(rng: &mut impl RngCore) -> Self { let signature = Signature::random_for_test(rng); - let mut aggregate_signature = AggregateSignature::new(); - aggregate_signature.add(&signature); + let mut aggregate_signature = AggregateSignature::infinity(); + aggregate_signature.add_assign(&signature); aggregate_signature } } diff --git a/consensus/types/src/test_utils/test_random/public_key.rs b/consensus/types/src/test_utils/test_random/public_key.rs index d643eaf0b6..12821ee623 100644 --- a/consensus/types/src/test_utils/test_random/public_key.rs +++ b/consensus/types/src/test_utils/test_random/public_key.rs @@ -3,7 +3,6 @@ use bls::{PublicKey, SecretKey}; impl TestRandom for PublicKey { fn random_for_test(rng: &mut impl RngCore) -> Self { - let secret_key = SecretKey::random_for_test(rng); - PublicKey::from_secret_key(&secret_key) + SecretKey::random_for_test(rng).public_key() } } diff --git a/consensus/types/src/test_utils/test_random/public_key_bytes.rs b/consensus/types/src/test_utils/test_random/public_key_bytes.rs index e801e30d2e..f04bfc3bc8 100644 --- a/consensus/types/src/test_utils/test_random/public_key_bytes.rs +++ b/consensus/types/src/test_utils/test_random/public_key_bytes.rs @@ -1,6 +1,6 @@ use std::convert::From; -use bls::{PublicKeyBytes, BLS_PUBLIC_KEY_BYTE_SIZE}; +use bls::{PublicKeyBytes, PUBLIC_KEY_BYTES_LEN}; use super::*; @@ -12,7 +12,7 @@ impl TestRandom for PublicKeyBytes { PublicKeyBytes::from(PublicKey::random_for_test(rng)) } else { //invalid signature, just random bytes - PublicKeyBytes::from_bytes(&<[u8; BLS_PUBLIC_KEY_BYTE_SIZE]>::random_for_test(rng)) + PublicKeyBytes::deserialize(&<[u8; PUBLIC_KEY_BYTES_LEN]>::random_for_test(rng)) .unwrap() } } diff --git a/consensus/types/src/test_utils/test_random/signature.rs b/consensus/types/src/test_utils/test_random/signature.rs index 61542df66d..119c81babb 100644 --- a/consensus/types/src/test_utils/test_random/signature.rs +++ b/consensus/types/src/test_utils/test_random/signature.rs @@ -7,6 +7,6 @@ impl TestRandom for Signature { let mut message = vec![0; 32]; rng.fill_bytes(&mut message); - Signature::new(&message, &secret_key) + secret_key.sign(Hash256::from_slice(&message)) } } diff --git a/consensus/types/src/test_utils/test_random/signature_bytes.rs b/consensus/types/src/test_utils/test_random/signature_bytes.rs index cae2a8225c..a4ae772d89 100644 --- a/consensus/types/src/test_utils/test_random/signature_bytes.rs +++ b/consensus/types/src/test_utils/test_random/signature_bytes.rs @@ -1,4 +1,4 @@ -use bls::{SignatureBytes, BLS_SIG_BYTE_SIZE}; +use bls::{SignatureBytes, SIGNATURE_BYTES_LEN}; use super::*; use std::convert::From; @@ -11,7 +11,7 @@ impl TestRandom for SignatureBytes { SignatureBytes::from(Signature::random_for_test(rng)) } else { //invalid signature, just random bytes - SignatureBytes::from_bytes(&<[u8; BLS_SIG_BYTE_SIZE]>::random_for_test(rng)).unwrap() + SignatureBytes::deserialize(&<[u8; SIGNATURE_BYTES_LEN]>::random_for_test(rng)).unwrap() } } } diff --git a/consensus/types/src/tree_hash_impls.rs b/consensus/types/src/tree_hash_impls.rs index 787d62d760..749a33b5da 100644 --- a/consensus/types/src/tree_hash_impls.rs +++ b/consensus/types/src/tree_hash_impls.rs @@ -71,7 +71,7 @@ fn process_pubkey_bytes_field( leaf: &mut Hash256, force_update: bool, ) -> bool { - let new_tree_hash = merkle_root(val.as_slice(), 0); + let new_tree_hash = merkle_root(val.as_serialized(), 0); process_slice_field(new_tree_hash.as_bytes(), leaf, force_update) } diff --git a/consensus/types/src/voluntary_exit.rs b/consensus/types/src/voluntary_exit.rs index 84cca1a30e..a9509d7aff 100644 --- a/consensus/types/src/voluntary_exit.rs +++ b/consensus/types/src/voluntary_exit.rs @@ -1,6 +1,6 @@ use crate::{ - test_utils::TestRandom, ChainSpec, Domain, Epoch, Fork, Hash256, SecretKey, Signature, - SignedRoot, SignedVoluntaryExit, + test_utils::TestRandom, ChainSpec, Domain, Epoch, Fork, Hash256, SecretKey, SignedRoot, + SignedVoluntaryExit, }; use serde_derive::{Deserialize, Serialize}; @@ -36,10 +36,9 @@ impl VoluntaryExit { genesis_validators_root, ); let message = self.signing_root(domain); - let signature = Signature::new(message.as_bytes(), &secret_key); SignedVoluntaryExit { message: self, - signature, + signature: secret_key.sign(message), } } } diff --git a/crypto/bls/Cargo.toml b/crypto/bls/Cargo.toml index 0f4e9cfe13..1ebce3727c 100644 --- a/crypto/bls/Cargo.toml +++ b/crypto/bls/Cargo.toml @@ -5,17 +5,22 @@ authors = ["Paul Hauner "] edition = "2018" [dependencies] -milagro_bls = { git = "https://github.com/sigp/milagro_bls", tag = "v1.1.0" } -eth2_hashing = "0.1.0" -hex = "0.4.2" -rand = "0.7.3" -serde = "1.0.110" -serde_derive = "1.0.110" -serde_hex = { path = "../../consensus/serde_hex" } eth2_ssz = "0.1.2" tree_hash = "0.1.0" +milagro_bls = { git = "https://github.com/sigp/milagro_bls", branch = "paulh" } +rand = "0.7.2" +serde = "1.0.102" +serde_derive = "1.0.102" +serde_hex = { path = "../../consensus/serde_hex" } +hex = "0.3" +eth2_hashing = "0.1.0" +ethereum-types = "0.9.1" arbitrary = { version = "0.4.4", features = ["derive"], optional = true } zeroize = { version = "1.0.0", features = ["zeroize_derive"] } +blst = { git = "https://github.com/sigp/blst.git", rev = "968c846a2dc46e836e407bbdbac1a38a597ebc46" } [features] +default = ["supranational"] fake_crypto = [] +milagro = [] +supranational = [] diff --git a/crypto/bls/src/aggregate_public_key.rs b/crypto/bls/src/aggregate_public_key.rs deleted file mode 100644 index 9fa65e96c7..0000000000 --- a/crypto/bls/src/aggregate_public_key.rs +++ /dev/null @@ -1,99 +0,0 @@ -use super::{PublicKey, BLS_PUBLIC_KEY_BYTE_SIZE}; -use milagro_bls::AggregatePublicKey as RawAggregatePublicKey; -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; -use ssz::{Decode, DecodeError, Encode}; - -/// A BLS aggregate public key. -/// -/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ -/// serialization). -#[derive(Debug, Clone, Default)] -pub struct AggregatePublicKey(RawAggregatePublicKey); - -impl AggregatePublicKey { - pub fn new() -> Self { - AggregatePublicKey(RawAggregatePublicKey::new()) - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - let pubkey = RawAggregatePublicKey::from_bytes(&bytes).map_err(|_| { - DecodeError::BytesInvalid(format!("Invalid AggregatePublicKey bytes: {:?}", bytes)) - })?; - - Ok(AggregatePublicKey(pubkey)) - } - - pub fn add_without_affine(&mut self, public_key: &PublicKey) { - self.0.point.add(&public_key.as_raw().point) - } - - pub fn affine(&mut self) { - self.0.point.affine() - } - - pub fn add(&mut self, public_key: &PublicKey) { - self.0.add(public_key.as_raw()) - } - - /// Returns the underlying public key. - pub fn as_raw(&self) -> &RawAggregatePublicKey { - &self.0 - } - - /// Returns the underlying point as compressed bytes. - pub fn as_bytes(&self) -> [u8; BLS_PUBLIC_KEY_BYTE_SIZE] { - self.as_raw().as_bytes() - } - - pub fn into_raw(self) -> RawAggregatePublicKey { - self.0 - } - - /// Return a hex string representation of this key's bytes. - #[cfg(test)] - pub fn as_hex_string(&self) -> String { - serde_hex::encode(self.as_ssz_bytes()) - } -} - -impl_ssz!( - AggregatePublicKey, - BLS_PUBLIC_KEY_BYTE_SIZE, - "AggregatePublicKey" -); -impl_tree_hash!(AggregatePublicKey, BLS_PUBLIC_KEY_BYTE_SIZE); - -impl Serialize for AggregatePublicKey { - /// Serde serialization is compliant the Ethereum YAML test format. - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&hex_encode(self.as_ssz_bytes())) - } -} - -impl<'de> Deserialize<'de> for AggregatePublicKey { - /// Serde serialization is compliant the Ethereum YAML test format. - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; - let agg_sig = AggregatePublicKey::from_ssz_bytes(&bytes) - .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; - - Ok(agg_sig) - } -} - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary for AggregatePublicKey { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; BLS_PUBLIC_KEY_BYTE_SIZE]; - u.fill_buffer(&mut bytes)?; - Self::from_bytes(&bytes).map_err(|_| arbitrary::Error::IncorrectFormat) - } -} diff --git a/crypto/bls/src/aggregate_signature.rs b/crypto/bls/src/aggregate_signature.rs deleted file mode 100644 index 3ad4fdddf2..0000000000 --- a/crypto/bls/src/aggregate_signature.rs +++ /dev/null @@ -1,201 +0,0 @@ -use super::*; -use milagro_bls::AggregateSignature as RawAggregateSignature; -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; -use ssz::{Decode, DecodeError, Encode}; - -/// A BLS aggregate signature. -/// -/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ -/// serialization). -#[derive(Debug, PartialEq, Clone, Default, Eq)] -pub struct AggregateSignature { - aggregate_signature: RawAggregateSignature, - is_empty: bool, -} - -impl AggregateSignature { - /// Instantiate a new AggregateSignature. - /// - /// is_empty is false - /// AggregateSignature is point at infinity - pub fn new() -> Self { - Self { - aggregate_signature: RawAggregateSignature::new(), - is_empty: false, - } - } - - /// Add (aggregate) a signature to the `AggregateSignature`. - pub fn add(&mut self, signature: &Signature) { - // Only empty if both are empty - self.is_empty = self.is_empty && signature.is_empty(); - - // Note: empty signatures will have point at infinity which is equivalent of adding 0. - self.aggregate_signature.add(signature.as_raw()) - } - - /// Add (aggregate) another `AggregateSignature`. - pub fn add_aggregate(&mut self, agg_signature: &AggregateSignature) { - // Only empty if both are empty - self.is_empty = self.is_empty && agg_signature.is_empty(); - - // Note: empty signatures will have point at infinity which is equivalent of adding 0. - self.aggregate_signature - .add_aggregate(&agg_signature.aggregate_signature) - } - - /// Verify the `AggregateSignature` against an `AggregatePublicKey`. - /// - /// Only returns `true` if the set of keys in the `AggregatePublicKey` match the set of keys - /// that signed the `AggregateSignature`. - pub fn verify(&self, msg: &[u8], aggregate_public_key: &AggregatePublicKey) -> bool { - if self.is_empty { - return false; - } - self.aggregate_signature - .fast_aggregate_verify_pre_aggregated(msg, aggregate_public_key.as_raw()) - } - - /// Verify the `AggregateSignature` against an `AggregatePublicKey`. - /// - /// Only returns `true` if the set of keys in the `AggregatePublicKey` match the set of keys - /// that signed the `AggregateSignature`. - pub fn verify_unaggregated(&self, msg: &[u8], public_keys: &[&PublicKey]) -> bool { - if self.is_empty { - return false; - } - let public_key_refs: Vec<_> = public_keys.iter().map(|pk| pk.as_raw()).collect(); - self.aggregate_signature - .fast_aggregate_verify(msg, &public_key_refs) - } - - /// Verify this AggregateSignature against multiple AggregatePublickeys and Messages. - /// - /// Each AggregatePublicKey has a 1:1 ratio with a 32 byte Message. - pub fn verify_multiple(&self, messages: &[&[u8]], public_keys: &[&PublicKey]) -> bool { - if self.is_empty { - return false; - } - let public_keys_refs: Vec<_> = public_keys.iter().map(|pk| pk.as_raw()).collect(); - self.aggregate_signature - .aggregate_verify(&messages, &public_keys_refs) - } - - /// Return AggregateSignature as bytes - pub fn as_bytes(&self) -> [u8; BLS_AGG_SIG_BYTE_SIZE] { - if self.is_empty { - return [0; BLS_AGG_SIG_BYTE_SIZE]; - } - self.aggregate_signature.as_bytes() - } - - /// Convert bytes to AggregateSignature - pub fn from_bytes(bytes: &[u8]) -> Result { - for byte in bytes { - if *byte != 0 { - let sig = RawAggregateSignature::from_bytes(&bytes).map_err(|_| { - DecodeError::BytesInvalid(format!( - "Invalid AggregateSignature bytes: {:?}", - bytes - )) - })?; - - return Ok(Self { - aggregate_signature: sig, - is_empty: false, - }); - } - } - Ok(Self::empty_signature()) - } - - /// Returns the underlying signature. - pub fn as_raw(&self) -> &RawAggregateSignature { - &self.aggregate_signature - } - - /// Returns if the AggregateSignature `is_empty` - pub fn is_empty(&self) -> bool { - self.is_empty - } - - /// Creates a new AggregateSignature - /// - /// aggregate_signature set to the point infinity - /// is_empty set to true - pub fn empty_signature() -> Self { - Self { - aggregate_signature: RawAggregateSignature::new(), - is_empty: true, - } - } - - /// Return a hex string representation of the bytes of this signature. - #[cfg(test)] - pub fn as_hex_string(&self) -> String { - hex_encode(self.as_ssz_bytes()) - } -} - -impl_ssz!( - AggregateSignature, - BLS_AGG_SIG_BYTE_SIZE, - "AggregateSignature" -); - -impl_tree_hash!(AggregateSignature, BLS_AGG_SIG_BYTE_SIZE); - -impl Serialize for AggregateSignature { - /// Serde serialization is compliant the Ethereum YAML test format. - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&hex_encode(self.as_ssz_bytes())) - } -} - -impl<'de> Deserialize<'de> for AggregateSignature { - /// Serde serialization is compliant the Ethereum YAML test format. - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; - let agg_sig = AggregateSignature::from_ssz_bytes(&bytes) - .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; - - Ok(agg_sig) - } -} - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary for AggregateSignature { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; BLS_AGG_SIG_BYTE_SIZE]; - u.fill_buffer(&mut bytes)?; - Self::from_bytes(&bytes).map_err(|_| arbitrary::Error::IncorrectFormat) - } -} - -#[cfg(test)] -mod tests { - use super::super::{Keypair, Signature}; - use super::*; - use ssz::Encode; - - #[test] - pub fn test_ssz_round_trip() { - let keypair = Keypair::random(); - - let mut original = AggregateSignature::new(); - original.add(&Signature::new(&[42, 42], &keypair.sk)); - - let bytes = original.as_ssz_bytes(); - let decoded = AggregateSignature::from_ssz_bytes(&bytes).unwrap(); - - assert_eq!(original, decoded); - } -} diff --git a/crypto/bls/src/fake_aggregate_public_key.rs b/crypto/bls/src/fake_aggregate_public_key.rs deleted file mode 100644 index 4c25f21568..0000000000 --- a/crypto/bls/src/fake_aggregate_public_key.rs +++ /dev/null @@ -1,132 +0,0 @@ -use super::{PublicKey, BLS_PUBLIC_KEY_BYTE_SIZE}; -use hex::encode as hex_encode; -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use serde_hex::PrefixedHexVisitor; -use ssz::{ssz_encode, Decode, DecodeError, Encode}; -use std::fmt; - -/// A BLS aggregate public key. -/// -/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ -/// serialization). -#[derive(Clone)] -pub struct FakeAggregatePublicKey { - bytes: [u8; BLS_PUBLIC_KEY_BYTE_SIZE], -} - -impl FakeAggregatePublicKey { - pub fn new() -> Self { - Self::zero() - } - - pub fn empty_signature() -> Self { - Self { - bytes: [0; BLS_PUBLIC_KEY_BYTE_SIZE], - } - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != BLS_PUBLIC_KEY_BYTE_SIZE { - Err(DecodeError::InvalidByteLength { - len: bytes.len(), - expected: BLS_PUBLIC_KEY_BYTE_SIZE, - }) - } else { - let mut array = [0; BLS_PUBLIC_KEY_BYTE_SIZE]; - array.copy_from_slice(&bytes); - Ok(Self { bytes: array }) - } - } - - pub fn add_without_affine(&mut self, _public_key: &PublicKey) { - // No nothing. - } - - pub fn affine(&mut self) { - // No nothing. - } - - /// Creates a new all-zero's aggregate public key - pub fn zero() -> Self { - Self { - bytes: [0; BLS_PUBLIC_KEY_BYTE_SIZE], - } - } - - pub fn add(&mut self, _public_key: &PublicKey) { - // No nothing. - } - - pub fn aggregate(_pks: &[&PublicKey]) -> Self { - Self::new() - } - - pub fn from_public_key(public_key: &PublicKey) -> Self { - Self { - bytes: public_key.as_bytes(), - } - } - - pub fn as_raw(&self) -> &Self { - &self - } - - pub fn into_raw(self) -> Self { - self - } - - pub fn as_bytes(&self) -> [u8; BLS_PUBLIC_KEY_BYTE_SIZE] { - self.bytes.clone() - } -} - -impl_ssz!( - FakeAggregatePublicKey, - BLS_PUBLIC_KEY_BYTE_SIZE, - "FakeAggregatePublicKey" -); - -impl_tree_hash!(FakeAggregatePublicKey, BLS_PUBLIC_KEY_BYTE_SIZE); - -impl Serialize for FakeAggregatePublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&hex_encode(ssz_encode(self))) - } -} - -impl<'de> Deserialize<'de> for FakeAggregatePublicKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; - let pubkey = <_>::from_ssz_bytes(&bytes[..]) - .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; - Ok(pubkey) - } -} - -impl Default for FakeAggregatePublicKey { - fn default() -> Self { - Self::new() - } -} - -impl fmt::Debug for FakeAggregatePublicKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!("{:?}", self.bytes.to_vec())) - } -} - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary for FakeAggregatePublicKey { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; BLS_PUBLIC_KEY_BYTE_SIZE]; - u.fill_buffer(&mut bytes)?; - Self::from_bytes(&bytes).map_err(|_| arbitrary::Error::IncorrectFormat) - } -} diff --git a/crypto/bls/src/fake_aggregate_signature.rs b/crypto/bls/src/fake_aggregate_signature.rs deleted file mode 100644 index 2535cb13f5..0000000000 --- a/crypto/bls/src/fake_aggregate_signature.rs +++ /dev/null @@ -1,184 +0,0 @@ -use super::{ - fake_aggregate_public_key::FakeAggregatePublicKey, fake_public_key::FakePublicKey, - fake_signature::FakeSignature, BLS_AGG_SIG_BYTE_SIZE, -}; -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; -use ssz::{ssz_encode, Decode, DecodeError, Encode}; -use std::fmt; - -/// A BLS aggregate signature. -/// -/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ -/// serialization). -#[derive(Clone)] -pub struct FakeAggregateSignature { - bytes: [u8; BLS_AGG_SIG_BYTE_SIZE], -} - -impl FakeAggregateSignature { - /// Creates a new all-zero's signature - pub fn new() -> Self { - Self::zero() - } - - /// Creates a new all-zero's signature - pub fn zero() -> Self { - Self { - bytes: [0; BLS_AGG_SIG_BYTE_SIZE], - } - } - - pub fn as_raw(&self) -> &Self { - &self - } - - /// Does glorious nothing. - pub fn add(&mut self, _signature: &FakeSignature) { - // Do nothing. - } - - /// Does glorious nothing. - pub fn add_aggregate(&mut self, _agg_sig: &FakeAggregateSignature) { - // Do nothing. - } - - /// Does glorious nothing. - pub fn aggregate(&mut self, _agg_sig: &FakeAggregateSignature) { - // Do nothing. - } - - /// _Always_ returns `true`. - pub fn verify(&self, _msg: &[u8], _aggregate_public_key: &FakeAggregatePublicKey) -> bool { - true - } - - /// _Always_ returns `true`. - pub fn verify_multiple( - &self, - _messages: &[&[u8]], - _aggregate_public_keys: &[&FakePublicKey], - ) -> bool { - true - } - - /// _Always_ returns `true`. - pub fn fast_aggregate_verify_pre_aggregated( - &self, - _messages: &[u8], - _aggregate_public_keys: &FakeAggregatePublicKey, - ) -> bool { - true - } - - /// _Always_ returns `true`. - pub fn from_signature(signature: &FakeSignature) -> Self { - Self { - bytes: signature.as_bytes(), - } - } - - /// Creates a new empty FakeAggregateSignature - pub fn empty_signature() -> Self { - Self { - bytes: [0u8; BLS_AGG_SIG_BYTE_SIZE], - } - } - - /// Convert bytes to fake BLS aggregate signature - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != BLS_AGG_SIG_BYTE_SIZE { - Err(DecodeError::InvalidByteLength { - len: bytes.len(), - expected: BLS_AGG_SIG_BYTE_SIZE, - }) - } else { - let mut array = [0u8; BLS_AGG_SIG_BYTE_SIZE]; - array.copy_from_slice(bytes); - Ok(Self { bytes: array }) - } - } - - pub fn as_bytes(&self) -> [u8; BLS_AGG_SIG_BYTE_SIZE] { - self.bytes.clone() - } -} - -impl_ssz!( - FakeAggregateSignature, - BLS_AGG_SIG_BYTE_SIZE, - "FakeAggregateSignature" -); - -impl_tree_hash!(FakeAggregateSignature, BLS_AGG_SIG_BYTE_SIZE); - -impl Serialize for FakeAggregateSignature { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&hex_encode(ssz_encode(self))) - } -} - -impl<'de> Deserialize<'de> for FakeAggregateSignature { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; - let obj = <_>::from_ssz_bytes(&bytes[..]) - .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; - Ok(obj) - } -} - -impl fmt::Debug for FakeAggregateSignature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!("{:?}", self.bytes.to_vec())) - } -} - -impl PartialEq for FakeAggregateSignature { - fn eq(&self, other: &FakeAggregateSignature) -> bool { - ssz_encode(self) == ssz_encode(other) - } -} - -impl Eq for FakeAggregateSignature {} - -impl Default for FakeAggregateSignature { - fn default() -> Self { - Self::zero() - } -} - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary for FakeAggregateSignature { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; BLS_AGG_SIG_BYTE_SIZE]; - u.fill_buffer(&mut bytes)?; - Self::from_bytes(&bytes).map_err(|_| arbitrary::Error::IncorrectFormat) - } -} - -#[cfg(test)] -mod tests { - use super::super::{Keypair, Signature}; - use super::*; - use ssz::ssz_encode; - - #[test] - pub fn test_ssz_round_trip() { - let keypair = Keypair::random(); - - let mut original = FakeAggregateSignature::new(); - original.add(&Signature::new(&[42, 42], &keypair.sk)); - - let bytes = ssz_encode(&original); - let decoded = FakeAggregateSignature::from_ssz_bytes(&bytes).unwrap(); - - assert_eq!(original, decoded); - } -} diff --git a/crypto/bls/src/fake_public_key.rs b/crypto/bls/src/fake_public_key.rs deleted file mode 100644 index 02b59812fa..0000000000 --- a/crypto/bls/src/fake_public_key.rs +++ /dev/null @@ -1,186 +0,0 @@ -use super::{SecretKey, BLS_PUBLIC_KEY_BYTE_SIZE}; -use milagro_bls::PublicKey as RawPublicKey; -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; -use ssz::{ssz_encode, Decode, DecodeError, Encode}; -use std::default; -use std::fmt; -use std::hash::{Hash, Hasher}; - -/// A single BLS signature. -/// -/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ -/// serialization). -#[derive(Clone)] -pub struct FakePublicKey { - bytes: [u8; BLS_PUBLIC_KEY_BYTE_SIZE], -} - -impl FakePublicKey { - pub fn from_secret_key(_secret_key: &SecretKey) -> Self { - Self::zero() - } - - pub fn from_raw(raw: RawPublicKey) -> Self { - Self { - bytes: raw.clone().as_bytes(), - } - } - - /// Creates a new all-zero's public key - pub fn zero() -> Self { - Self { - bytes: [0; BLS_PUBLIC_KEY_BYTE_SIZE], - } - } - - /// Returns the underlying point as compressed bytes. - pub fn as_bytes(&self) -> [u8; BLS_PUBLIC_KEY_BYTE_SIZE] { - self.bytes.clone() - } - - /// Converts compressed bytes to FakePublicKey - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != BLS_PUBLIC_KEY_BYTE_SIZE { - Err(DecodeError::InvalidByteLength { - len: bytes.len(), - expected: BLS_PUBLIC_KEY_BYTE_SIZE, - }) - } else { - let mut array = [0u8; BLS_PUBLIC_KEY_BYTE_SIZE]; - array.copy_from_slice(bytes); - Ok(Self { bytes: array }) - } - } - - /// Returns the FakePublicKey as (x, y) bytes - pub fn as_uncompressed_bytes(&self) -> [u8; BLS_PUBLIC_KEY_BYTE_SIZE * 2] { - [0u8; BLS_PUBLIC_KEY_BYTE_SIZE * 2] - } - - /// Converts (x, y) bytes to FakePublicKey - pub fn from_uncompressed_bytes(bytes: &[u8]) -> Result { - if bytes.len() != BLS_PUBLIC_KEY_BYTE_SIZE * 2 { - Err(DecodeError::InvalidByteLength { - len: bytes.len(), - expected: BLS_PUBLIC_KEY_BYTE_SIZE * 2, - }) - } else { - let mut array = [0u8; BLS_PUBLIC_KEY_BYTE_SIZE]; - array.copy_from_slice(bytes); - Ok(Self { bytes: array }) - } - } - - /// Returns the last 6 bytes of the SSZ encoding of the public key, as a hex string. - /// - /// Useful for providing a short identifier to the user. - pub fn concatenated_hex_id(&self) -> String { - let bytes = ssz_encode(self); - let end_bytes = &bytes[bytes.len().saturating_sub(6)..bytes.len()]; - hex_encode(end_bytes) - } - - /// Returns the point as a hex string of the SSZ encoding. - /// - /// Note: the string is prefixed with `0x`. - pub fn as_hex_string(&self) -> String { - hex_encode(self.as_ssz_bytes()) - } - - // Returns itself - pub fn as_raw(&self) -> &Self { - self - } -} - -impl fmt::Display for FakePublicKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.concatenated_hex_id()) - } -} - -impl fmt::Debug for FakePublicKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "0x{}", self.as_hex_string()) - } -} - -impl default::Default for FakePublicKey { - fn default() -> Self { - let secret_key = SecretKey::random(); - FakePublicKey::from_secret_key(&secret_key) - } -} - -impl_ssz!(FakePublicKey, BLS_PUBLIC_KEY_BYTE_SIZE, "FakePublicKey"); - -impl_tree_hash!(FakePublicKey, BLS_PUBLIC_KEY_BYTE_SIZE); - -impl Serialize for FakePublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&hex_encode(self.as_ssz_bytes())) - } -} - -impl<'de> Deserialize<'de> for FakePublicKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; - let pubkey = Self::from_ssz_bytes(&bytes[..]) - .map_err(|e| serde::de::Error::custom(format!("invalid pubkey ({:?})", e)))?; - Ok(pubkey) - } -} - -impl PartialEq for FakePublicKey { - fn eq(&self, other: &FakePublicKey) -> bool { - ssz_encode(self) == ssz_encode(other) - } -} - -impl Eq for FakePublicKey {} - -impl Hash for FakePublicKey { - /// Note: this is distinct from consensus serialization, it will produce a different hash. - /// - /// This method uses the uncompressed bytes, which are much faster to obtain than the - /// compressed bytes required for consensus serialization. - /// - /// Use `ssz::Encode` to obtain the bytes required for consensus hashing. - fn hash(&self, state: &mut H) { - self.as_uncompressed_bytes().hash(state) - } -} - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary for FakePublicKey { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; BLS_PUBLIC_KEY_BYTE_SIZE]; - u.fill_buffer(&mut bytes)?; - Self::from_bytes(&bytes).map_err(|_| arbitrary::Error::IncorrectFormat) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ssz::ssz_encode; - - #[test] - pub fn test_ssz_round_trip() { - let sk = SecretKey::random(); - let original = FakePublicKey::from_secret_key(&sk); - - let bytes = ssz_encode(&original); - let decoded = FakePublicKey::from_ssz_bytes(&bytes).unwrap(); - - assert_eq!(original, decoded); - } -} diff --git a/crypto/bls/src/fake_signature.rs b/crypto/bls/src/fake_signature.rs deleted file mode 100644 index 2fcb669011..0000000000 --- a/crypto/bls/src/fake_signature.rs +++ /dev/null @@ -1,159 +0,0 @@ -use super::{PublicKey, SecretKey, BLS_SIG_BYTE_SIZE}; -use hex::encode as hex_encode; -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use serde_hex::PrefixedHexVisitor; -use ssz::{ssz_encode, Decode, DecodeError, Encode}; -use std::fmt; - -/// A single BLS signature. -/// -/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ -/// serialization). -#[derive(Clone)] -pub struct FakeSignature { - bytes: [u8; BLS_SIG_BYTE_SIZE], - is_empty: bool, -} - -impl FakeSignature { - /// Creates a new all-zero's signature - pub fn new(_msg: &[u8], _sk: &SecretKey) -> Self { - FakeSignature::zero() - } - - /// Creates a new all-zero's signature - pub fn zero() -> Self { - Self { - bytes: [0; BLS_SIG_BYTE_SIZE], - is_empty: true, - } - } - - /// Creates a new all-zero's signature - pub fn new_hashed(_x_real_hashed: &[u8], _x_imaginary_hashed: &[u8], _sk: &SecretKey) -> Self { - FakeSignature::zero() - } - - /// _Always_ returns `true`. - pub fn verify(&self, _msg: &[u8], _pk: &PublicKey) -> bool { - true - } - - pub fn as_raw(&self) -> &Self { - &self - } - - /// _Always_ returns true. - pub fn verify_hashed( - &self, - _x_real_hashed: &[u8], - _x_imaginary_hashed: &[u8], - _pk: &PublicKey, - ) -> bool { - true - } - - /// Convert bytes to fake BLS Signature - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != BLS_SIG_BYTE_SIZE { - Err(DecodeError::InvalidByteLength { - len: bytes.len(), - expected: BLS_SIG_BYTE_SIZE, - }) - } else { - let is_empty = bytes.iter().all(|x| *x == 0); - let mut array = [0u8; BLS_SIG_BYTE_SIZE]; - array.copy_from_slice(bytes); - Ok(Self { - bytes: array, - is_empty, - }) - } - } - - pub fn as_bytes(&self) -> [u8; BLS_SIG_BYTE_SIZE] { - self.bytes.clone() - } - - /// Returns a new empty signature. - pub fn empty_signature() -> Self { - FakeSignature::zero() - } - - // Check for empty Signature - pub fn is_empty(&self) -> bool { - self.is_empty - } -} - -impl_ssz!(FakeSignature, BLS_SIG_BYTE_SIZE, "FakeSignature"); - -impl_tree_hash!(FakeSignature, BLS_SIG_BYTE_SIZE); - -impl fmt::Debug for FakeSignature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!( - "{:?}, {:?}", - self.bytes.to_vec(), - self.is_empty() - )) - } -} - -impl PartialEq for FakeSignature { - fn eq(&self, other: &FakeSignature) -> bool { - self.bytes.to_vec() == other.bytes.to_vec() - } -} - -impl Eq for FakeSignature {} - -impl Serialize for FakeSignature { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&hex_encode(ssz_encode(self))) - } -} - -impl<'de> Deserialize<'de> for FakeSignature { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; - let pubkey = <_>::from_ssz_bytes(&bytes[..]) - .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; - Ok(pubkey) - } -} - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary for FakeSignature { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; BLS_SIG_BYTE_SIZE]; - u.fill_buffer(&mut bytes)?; - Self::from_bytes(&bytes).map_err(|_| arbitrary::Error::IncorrectFormat) - } -} - -#[cfg(test)] -mod tests { - use super::super::Keypair; - use super::*; - use ssz::ssz_encode; - - #[test] - pub fn test_ssz_round_trip() { - let keypair = Keypair::random(); - - let original = FakeSignature::new(&[42, 42], &keypair.sk); - - let bytes = ssz_encode(&original); - let decoded = FakeSignature::from_ssz_bytes(&bytes).unwrap(); - - assert_eq!(original, decoded); - } -} diff --git a/crypto/bls/src/generic_aggregate_public_key.rs b/crypto/bls/src/generic_aggregate_public_key.rs new file mode 100644 index 0000000000..09002e94e9 --- /dev/null +++ b/crypto/bls/src/generic_aggregate_public_key.rs @@ -0,0 +1,18 @@ +use crate::{Error, PUBLIC_KEY_BYTES_LEN}; + +/// Implemented on some struct from a BLS library so it may be used internally in this crate. +pub trait TAggregatePublicKey: Sized + Clone { + /// Initialize `Self` to the infinity value which can then have other public keys aggregated + /// upon it. + fn infinity() -> Self; + + /// Serialize `self` as compressed bytes. + fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN]; + + /// Deserialize `self` from compressed bytes. + fn deserialize(bytes: &[u8]) -> Result; +} + +/* + * Note: there is no immediate need for a `GenericAggregatePublicKey` struct. + */ diff --git a/crypto/bls/src/generic_aggregate_signature.rs b/crypto/bls/src/generic_aggregate_signature.rs new file mode 100644 index 0000000000..240b7d1880 --- /dev/null +++ b/crypto/bls/src/generic_aggregate_signature.rs @@ -0,0 +1,283 @@ +use crate::{ + generic_aggregate_public_key::TAggregatePublicKey, + generic_public_key::{GenericPublicKey, TPublicKey}, + generic_signature::{GenericSignature, TSignature}, + Error, Hash256, INFINITY_SIGNATURE, SIGNATURE_BYTES_LEN, +}; +use serde::de::{Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; +use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; +use ssz::{Decode, Encode}; +use std::fmt; +use std::marker::PhantomData; +use tree_hash::TreeHash; + +/// The compressed bytes used to represent `GenericAggregateSignature::empty()`. +pub const EMPTY_SIGNATURE_SERIALIZATION: [u8; SIGNATURE_BYTES_LEN] = [0; SIGNATURE_BYTES_LEN]; + +/// Implemented on some struct from a BLS library so it may be used as the `point` in an +/// `GenericAggregateSignature`. +pub trait TAggregateSignature: Sized + Clone { + /// Initialize `Self` to the infinity value which can then have other signatures aggregated + /// upon it. + fn infinity() -> Self; + + /// Aggregates a signature onto `self`. + fn add_assign(&mut self, other: &Sig); + + /// Aggregates an aggregate signature onto `self`. + fn add_assign_aggregate(&mut self, other: &Self); + + /// Serialize `self` as compressed bytes. + fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN]; + + /// Deserialize `self` from compressed bytes. + fn deserialize(bytes: &[u8]) -> Result; + + /// Verify that `self` represents an aggregate signature where all `pubkeys` have signed `msg`. + fn fast_aggregate_verify(&self, msg: Hash256, pubkeys: &[&GenericPublicKey]) -> bool; + + /// Verify that `self` represents an aggregate signature where all `pubkeys` have signed their + /// corresponding message in `msgs`. + /// + /// ## Notes + /// + /// This function only exists for EF tests, it's presently not used in production. + fn aggregate_verify(&self, msgs: &[Hash256], pubkeys: &[&GenericPublicKey]) -> bool; +} + +/// A BLS aggregate signature that is generic across: +/// +/// - `Pub`: A BLS public key. +/// - `AggPub`: A BLS aggregate public key. +/// - `Sig`: A BLS signature. +/// - `AggSig`: A BLS aggregate signature. +/// +/// Provides generic functionality whilst deferring all serious cryptographic operations to the +/// generics. +#[derive(Clone, PartialEq)] +pub struct GenericAggregateSignature { + /// The underlying point which performs *actual* cryptographic operations. + point: Option, + /// True if this point is equal to the `INFINITY_SIGNATURE`. + pub(crate) is_infinity: bool, + _phantom_pub: PhantomData, + _phantom_agg_pub: PhantomData, + _phantom_sig: PhantomData, +} + +impl GenericAggregateSignature +where + Sig: TSignature, + AggSig: TAggregateSignature, +{ + /// Initialize `Self` to the infinity value which can then have other signatures aggregated + /// upon it. + pub fn infinity() -> Self { + Self { + point: Some(AggSig::infinity()), + is_infinity: true, + _phantom_pub: PhantomData, + _phantom_agg_pub: PhantomData, + _phantom_sig: PhantomData, + } + } + + /// Initialize self to the "empty" value. This value is serialized as all-zeros. + /// + /// This value can have another signature aggregated atop of it. When this happens, `self` is + /// simply set to infinity before having the other signature aggregated onto it. + /// + /// ## Notes + /// + /// This function is not necessarily useful from a BLS cryptography perspective, it mostly + /// exists to satisfy the Eth2 specification which expects the all-zeros serialization to be + /// meaningful. + pub fn empty() -> Self { + Self { + point: None, + is_infinity: false, + _phantom_pub: PhantomData, + _phantom_agg_pub: PhantomData, + _phantom_sig: PhantomData, + } + } + + /// Returns `true` if `self` is equal to the "empty" value. + /// + /// E.g., `Self::empty().is_empty() == true` + pub fn is_empty(&self) -> bool { + self.point.is_none() + } + + /// Returns a reference to the underlying BLS point. + pub(crate) fn point(&self) -> Option<&AggSig> { + self.point.as_ref() + } + + /// Aggregates a signature onto `self`. + pub fn add_assign(&mut self, other: &GenericSignature) { + if let Some(other_point) = other.point() { + self.is_infinity = self.is_infinity && other.is_infinity; + if let Some(self_point) = &mut self.point { + self_point.add_assign(other_point) + } else { + let mut self_point = AggSig::infinity(); + self_point.add_assign(other_point); + self.point = Some(self_point) + } + } + } + + /// Aggregates an aggregate signature onto `self`. + pub fn add_assign_aggregate(&mut self, other: &Self) { + if let Some(other_point) = other.point() { + self.is_infinity = self.is_infinity && other.is_infinity; + if let Some(self_point) = &mut self.point { + self_point.add_assign_aggregate(other_point) + } else { + let mut self_point = AggSig::infinity(); + self_point.add_assign_aggregate(other_point); + self.point = Some(self_point) + } + } + } + + /// Serialize `self` as compressed bytes. + pub fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] { + if let Some(point) = &self.point { + point.serialize() + } else { + EMPTY_SIGNATURE_SERIALIZATION + } + } + + /// Deserialize `self` from compressed bytes. + pub fn deserialize(bytes: &[u8]) -> Result { + let point = if bytes == &EMPTY_SIGNATURE_SERIALIZATION[..] { + None + } else { + Some(AggSig::deserialize(bytes)?) + }; + + Ok(Self { + point, + is_infinity: bytes == &INFINITY_SIGNATURE[..], + _phantom_pub: PhantomData, + _phantom_agg_pub: PhantomData, + _phantom_sig: PhantomData, + }) + } +} + +impl GenericAggregateSignature +where + Pub: TPublicKey + Clone, + AggPub: TAggregatePublicKey + Clone, + Sig: TSignature, + AggSig: TAggregateSignature, +{ + /// Verify that `self` represents an aggregate signature where all `pubkeys` have signed `msg`. + pub fn fast_aggregate_verify(&self, msg: Hash256, pubkeys: &[&GenericPublicKey]) -> bool { + if pubkeys.is_empty() { + return false; + } + + if self.is_infinity + && pubkeys.len() == 1 + && pubkeys.first().map_or(false, |pk| pk.is_infinity) + { + return true; + } + + match self.point.as_ref() { + Some(point) => point.fast_aggregate_verify(msg, pubkeys), + None => false, + } + } + + /// Verify that `self` represents an aggregate signature where all `pubkeys` have signed their + /// corresponding message in `msgs`. + /// + /// ## Notes + /// + /// This function only exists for EF tests, it's presently not used in production. + pub fn aggregate_verify(&self, msgs: &[Hash256], pubkeys: &[&GenericPublicKey]) -> bool { + if msgs.is_empty() || msgs.len() != pubkeys.len() { + return false; + } + + if self.is_infinity + && pubkeys.len() == 1 + && pubkeys.first().map_or(false, |pk| pk.is_infinity) + { + return true; + } + + match self.point.as_ref() { + Some(point) => point.aggregate_verify(msgs, pubkeys), + None => false, + } + } +} + +impl Encode for GenericAggregateSignature +where + Sig: TSignature, + AggSig: TAggregateSignature, +{ + impl_ssz_encode!(SIGNATURE_BYTES_LEN); +} + +impl Decode for GenericAggregateSignature +where + Sig: TSignature, + AggSig: TAggregateSignature, +{ + impl_ssz_decode!(SIGNATURE_BYTES_LEN); +} + +impl TreeHash for GenericAggregateSignature +where + Sig: TSignature, + AggSig: TAggregateSignature, +{ + impl_tree_hash!(SIGNATURE_BYTES_LEN); +} + +impl Serialize for GenericAggregateSignature +where + Sig: TSignature, + AggSig: TAggregateSignature, +{ + impl_serde_serialize!(); +} + +impl<'de, Pub, AggPub, Sig, AggSig> Deserialize<'de> + for GenericAggregateSignature +where + Sig: TSignature, + AggSig: TAggregateSignature, +{ + impl_serde_deserialize!(); +} + +impl fmt::Debug for GenericAggregateSignature +where + Sig: TSignature, + AggSig: TAggregateSignature, +{ + impl_debug!(); +} + +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary + for GenericAggregateSignature +where + Pub: 'static, + AggPub: 'static, + Sig: TSignature + 'static, + AggSig: TAggregateSignature + 'static, +{ + impl_arbitrary!(SIGNATURE_BYTES_LEN); +} diff --git a/crypto/bls/src/generic_keypair.rs b/crypto/bls/src/generic_keypair.rs new file mode 100644 index 0000000000..7689ccbdc4 --- /dev/null +++ b/crypto/bls/src/generic_keypair.rs @@ -0,0 +1,54 @@ +use crate::{ + generic_public_key::{GenericPublicKey, TPublicKey}, + generic_secret_key::{GenericSecretKey, TSecretKey}, + generic_signature::TSignature, +}; +use std::fmt; +use std::marker::PhantomData; + +/// A simple wrapper around `PublicKey` and `GenericSecretKey`. +#[derive(Clone)] +pub struct GenericKeypair { + pub pk: GenericPublicKey, + pub sk: GenericSecretKey, + _phantom: PhantomData, +} + +impl GenericKeypair +where + Pub: TPublicKey, + Sec: TSecretKey, + Sig: TSignature, +{ + /// Instantiate `Self` from a public and secret key. + /// + /// This function does not check to ensure that `pk` is derived from `sk`. It would be a logic + /// error to supply such a `pk`. + pub fn from_components(pk: GenericPublicKey, sk: GenericSecretKey) -> Self { + Self { + pk, + sk, + _phantom: PhantomData, + } + } + + /// Instantiates `Self` from a randomly generated secret key. + pub fn random() -> Self { + let sk = GenericSecretKey::random(); + Self { + pk: sk.public_key(), + sk, + _phantom: PhantomData, + } + } +} + +impl fmt::Debug for GenericKeypair +where + Pub: TPublicKey, +{ + /// Defers to `self.pk` to avoid leaking the secret key. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.pk.fmt(f) + } +} diff --git a/crypto/bls/src/generic_public_key.rs b/crypto/bls/src/generic_public_key.rs new file mode 100644 index 0000000000..29814d24ac --- /dev/null +++ b/crypto/bls/src/generic_public_key.rs @@ -0,0 +1,115 @@ +use crate::Error; +use serde::de::{Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; +use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; +use ssz::{Decode, Encode}; +use std::fmt; +use std::hash::{Hash, Hasher}; +use tree_hash::TreeHash; + +/// The byte-length of a BLS public key when serialized in compressed form. +pub const PUBLIC_KEY_BYTES_LEN: usize = 48; + +/// Represents the public key at infinity. +pub const INFINITY_PUBLIC_KEY: [u8; PUBLIC_KEY_BYTES_LEN] = [ + 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + +/// Implemented on some struct from a BLS library so it may be used as the `point` in a +/// `GenericPublicKey`. +pub trait TPublicKey: Sized + Clone { + /// Serialize `self` as compressed bytes. + fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN]; + + /// Deserialize `self` from compressed bytes. + fn deserialize(bytes: &[u8]) -> Result; +} + +/// A BLS aggregate public key that is generic across some BLS point (`Pub`). +/// +/// Provides generic functionality whilst deferring all serious cryptographic operations to `Pub`. +#[derive(Clone)] +pub struct GenericPublicKey { + /// The underlying point which performs *actual* cryptographic operations. + point: Pub, + /// True if this point is equal to the `INFINITY_PUBLIC_KEY`. + pub(crate) is_infinity: bool, +} + +impl GenericPublicKey +where + Pub: TPublicKey, +{ + /// Instantiates `Self` from a `point`. + pub(crate) fn from_point(point: Pub, is_infinity: bool) -> Self { + Self { point, is_infinity } + } + + /// Returns a reference to the underlying BLS point. + pub(crate) fn point(&self) -> &Pub { + &self.point + } + + /// Returns `self.serialize()` as a `0x`-prefixed hex string. + pub fn to_hex_string(&self) -> String { + format!("{:?}", self) + } + + /// Serialize `self` as compressed bytes. + pub fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] { + self.point.serialize() + } + + /// Deserialize `self` from compressed bytes. + pub fn deserialize(bytes: &[u8]) -> Result { + Ok(Self { + point: Pub::deserialize(bytes)?, + is_infinity: bytes == &INFINITY_PUBLIC_KEY[..], + }) + } +} + +impl Eq for GenericPublicKey {} + +impl PartialEq for GenericPublicKey { + fn eq(&self, other: &Self) -> bool { + self.serialize()[..] == other.serialize()[..] + } +} + +/// Hashes the `self.serialize()` bytes. +impl Hash for GenericPublicKey { + fn hash(&self, state: &mut H) { + self.serialize()[..].hash(state); + } +} + +impl Encode for GenericPublicKey { + impl_ssz_encode!(PUBLIC_KEY_BYTES_LEN); +} + +impl Decode for GenericPublicKey { + impl_ssz_decode!(PUBLIC_KEY_BYTES_LEN); +} + +impl TreeHash for GenericPublicKey { + impl_tree_hash!(PUBLIC_KEY_BYTES_LEN); +} + +impl Serialize for GenericPublicKey { + impl_serde_serialize!(); +} + +impl<'de, Pub: TPublicKey> Deserialize<'de> for GenericPublicKey { + impl_serde_deserialize!(); +} + +impl fmt::Debug for GenericPublicKey { + impl_debug!(); +} + +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary for GenericPublicKey { + impl_arbitrary!(PUBLIC_KEY_BYTES_LEN); +} diff --git a/crypto/bls/src/generic_public_key_bytes.rs b/crypto/bls/src/generic_public_key_bytes.rs new file mode 100644 index 0000000000..beceac1c90 --- /dev/null +++ b/crypto/bls/src/generic_public_key_bytes.rs @@ -0,0 +1,150 @@ +use crate::{ + generic_public_key::{GenericPublicKey, TPublicKey}, + Error, INFINITY_PUBLIC_KEY, PUBLIC_KEY_BYTES_LEN, +}; +use serde::de::{Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; +use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; +use ssz::{Decode, Encode}; +use std::convert::TryInto; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use tree_hash::TreeHash; + +/// A wrapper around some bytes that may or may not be a `PublicKey` in compressed form. +/// +/// This struct is useful for two things: +/// +/// - Lazily verifying a serialized public key. +/// - Storing some bytes that are actually invalid (required in the case of a `Deposit` message). +#[derive(Clone)] +pub struct GenericPublicKeyBytes { + bytes: [u8; PUBLIC_KEY_BYTES_LEN], + _phantom: PhantomData, +} + +impl GenericPublicKeyBytes +where + Pub: TPublicKey, +{ + /// Decompress and deserialize the bytes in `self` into an actual public key. + /// + /// May fail if the bytes are invalid. + pub fn decompress(&self) -> Result, Error> { + let is_infinity = self.bytes[..] == INFINITY_PUBLIC_KEY[..]; + Pub::deserialize(&self.bytes).map(|point| GenericPublicKey::from_point(point, is_infinity)) + } +} + +impl GenericPublicKeyBytes { + /// Instantiates `Self` with all-zeros. + pub fn empty() -> Self { + Self { + bytes: [0; PUBLIC_KEY_BYTES_LEN], + _phantom: PhantomData, + } + } + + /// Returns a slice of the bytes contained in `self`. + /// + /// The bytes are not verified (i.e., they may not represent a valid BLS point). + pub fn as_serialized(&self) -> &[u8] { + &self.bytes + } + + /// Clones the bytes in `self`. + /// + /// The bytes are not verified (i.e., they may not represent a valid BLS point). + pub fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] { + self.bytes + } + + /// Instantiates `Self` from bytes. + /// + /// The bytes are not fully verified (i.e., they may not represent a valid BLS point). Only the + /// byte-length is checked. + pub fn deserialize(bytes: &[u8]) -> Result { + if bytes.len() == PUBLIC_KEY_BYTES_LEN { + let mut pk_bytes = [0; PUBLIC_KEY_BYTES_LEN]; + pk_bytes[..].copy_from_slice(bytes); + Ok(Self { + bytes: pk_bytes, + _phantom: PhantomData, + }) + } else { + Err(Error::InvalidByteLength { + got: bytes.len(), + expected: PUBLIC_KEY_BYTES_LEN, + }) + } + } +} + +impl Eq for GenericPublicKeyBytes {} + +impl PartialEq for GenericPublicKeyBytes { + fn eq(&self, other: &Self) -> bool { + self.bytes[..] == other.bytes[..] + } +} + +impl Hash for GenericPublicKeyBytes { + fn hash(&self, state: &mut H) { + self.bytes[..].hash(state); + } +} + +/// Serializes the `PublicKey` in compressed form, storing the bytes in the newly created `Self`. +impl From> for GenericPublicKeyBytes +where + Pub: TPublicKey, +{ + fn from(pk: GenericPublicKey) -> Self { + Self { + bytes: pk.serialize(), + _phantom: PhantomData, + } + } +} + +/// Alias to `self.decompress()`. +impl TryInto> for &GenericPublicKeyBytes +where + Pub: TPublicKey, +{ + type Error = Error; + + fn try_into(self) -> Result, Self::Error> { + self.decompress() + } +} + +impl Encode for GenericPublicKeyBytes { + impl_ssz_encode!(PUBLIC_KEY_BYTES_LEN); +} + +impl Decode for GenericPublicKeyBytes { + impl_ssz_decode!(PUBLIC_KEY_BYTES_LEN); +} + +impl TreeHash for GenericPublicKeyBytes { + impl_tree_hash!(PUBLIC_KEY_BYTES_LEN); +} + +impl Serialize for GenericPublicKeyBytes { + impl_serde_serialize!(); +} + +impl<'de, Pub> Deserialize<'de> for GenericPublicKeyBytes { + impl_serde_deserialize!(); +} + +impl fmt::Debug for GenericPublicKeyBytes { + impl_debug!(); +} + +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary for GenericPublicKeyBytes { + impl_arbitrary!(PUBLIC_KEY_BYTES_LEN); +} diff --git a/crypto/bls/src/generic_secret_key.rs b/crypto/bls/src/generic_secret_key.rs new file mode 100644 index 0000000000..168fa191d8 --- /dev/null +++ b/crypto/bls/src/generic_secret_key.rs @@ -0,0 +1,90 @@ +use crate::{ + generic_public_key::{GenericPublicKey, TPublicKey}, + generic_signature::{GenericSignature, TSignature}, + Error, Hash256, ZeroizeHash, +}; +use std::marker::PhantomData; + +/// The byte-length of a BLS secret key. +pub const SECRET_KEY_BYTES_LEN: usize = 32; + +/// Implemented on some struct from a BLS library so it may be used as the `point` in a +/// `GenericSecretKey`. +pub trait TSecretKey: Sized { + /// Instantiate `Self` from some secure source of entropy. + fn random() -> Self; + + /// Signs `msg`. + fn sign(&self, msg: Hash256) -> SignaturePoint; + + /// Returns the public key that corresponds to self. + fn public_key(&self) -> PublicKeyPoint; + + /// Serialize `self` as compressed bytes. + fn serialize(&self) -> ZeroizeHash; + + /// Deserialize `self` from compressed bytes. + fn deserialize(bytes: &[u8]) -> Result; +} + +#[derive(Clone)] +pub struct GenericSecretKey { + /// The underlying point which performs *actual* cryptographic operations. + point: Sec, + _phantom_signature: PhantomData, + _phantom_public_key: PhantomData, +} + +impl GenericSecretKey +where + Sig: TSignature, + Pub: TPublicKey, + Sec: TSecretKey, +{ + /// Instantiate `Self` from some secure source of entropy. + pub fn random() -> Self { + Self { + point: Sec::random(), + _phantom_signature: PhantomData, + _phantom_public_key: PhantomData, + } + } + + /// Signs `msg`. + pub fn sign(&self, msg: Hash256) -> GenericSignature { + let is_infinity = false; + GenericSignature::from_point(self.point.sign(msg), is_infinity) + } + + /// Returns the public key that corresponds to self. + pub fn public_key(&self) -> GenericPublicKey { + let is_infinity = false; + GenericPublicKey::from_point(self.point.public_key(), is_infinity) + } + + /// Serialize `self` as compressed bytes. + /// + /// ## Note + /// + /// The bytes that are returned are the unencrypted secret key. This is sensitive cryptographic + /// material. + pub fn serialize(&self) -> ZeroizeHash { + self.point.serialize() + } + + /// Deserialize `self` from compressed bytes. + pub fn deserialize(bytes: &[u8]) -> Result { + if bytes.len() != SECRET_KEY_BYTES_LEN { + Err(Error::InvalidSecretKeyLength { + got: bytes.len(), + expected: SECRET_KEY_BYTES_LEN, + }) + } else { + Ok(Self { + point: Sec::deserialize(bytes)?, + _phantom_signature: PhantomData, + _phantom_public_key: PhantomData, + }) + } + } +} diff --git a/crypto/bls/src/generic_signature.rs b/crypto/bls/src/generic_signature.rs new file mode 100644 index 0000000000..28a9361957 --- /dev/null +++ b/crypto/bls/src/generic_signature.rs @@ -0,0 +1,169 @@ +use crate::{ + generic_public_key::{GenericPublicKey, TPublicKey}, + Error, Hash256, +}; +use serde::de::{Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; +use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; +use ssz::{Decode, Encode}; +use std::fmt; +use std::marker::PhantomData; +use tree_hash::TreeHash; + +/// The byte-length of a BLS signature when serialized in compressed form. +pub const SIGNATURE_BYTES_LEN: usize = 96; + +/// Represents the signature at infinity. +pub const INFINITY_SIGNATURE: [u8; SIGNATURE_BYTES_LEN] = [ + 0xc0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]; + +/// The compressed bytes used to represent `GenericSignature::empty()`. +pub const NONE_SIGNATURE: [u8; SIGNATURE_BYTES_LEN] = [0; SIGNATURE_BYTES_LEN]; + +/// Implemented on some struct from a BLS library so it may be used as the `point` in an +/// `GenericSignature`. +pub trait TSignature: Sized + Clone { + /// Serialize `self` as compressed bytes. + fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN]; + + /// Deserialize `self` from compressed bytes. + fn deserialize(bytes: &[u8]) -> Result; + + /// Returns `true` if `self` is a signature across `msg` by `pubkey`. + fn verify(&self, pubkey: &GenericPublicKey, msg: Hash256) -> bool; +} + +/// A BLS signature that is generic across: +/// +/// - `Pub`: A BLS public key. +/// - `Sig`: A BLS signature. +/// +/// Provides generic functionality whilst deferring all serious cryptographic operations to the +/// generics. +#[derive(Clone, PartialEq)] +pub struct GenericSignature { + /// The underlying point which performs *actual* cryptographic operations. + point: Option, + /// True if this point is equal to the `INFINITY_SIGNATURE`. + pub(crate) is_infinity: bool, + _phantom: PhantomData, +} + +impl GenericSignature +where + Sig: TSignature, +{ + /// Initialize self to the "empty" value. This value is serialized as all-zeros. + /// + /// ## Notes + /// + /// This function is not necessarily useful from a BLS cryptography perspective, it mostly + /// exists to satisfy the Eth2 specification which expects the all-zeros serialization to be + /// meaningful. + pub fn empty() -> Self { + Self { + point: None, + is_infinity: false, + _phantom: PhantomData, + } + } + + /// Returns `true` if `self` is equal to the "empty" value. + /// + /// E.g., `Self::empty().is_empty() == true` + pub fn is_empty(&self) -> bool { + self.point.is_none() + } + + /// Returns a reference to the underlying BLS point. + pub(crate) fn point(&self) -> Option<&Sig> { + self.point.as_ref() + } + + /// Instantiates `Self` from a `point`. + pub(crate) fn from_point(point: Sig, is_infinity: bool) -> Self { + Self { + point: Some(point), + is_infinity, + _phantom: PhantomData, + } + } + + /// Serialize `self` as compressed bytes. + pub fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] { + if let Some(point) = &self.point { + point.serialize() + } else { + NONE_SIGNATURE + } + } + + /// Deserialize `self` from compressed bytes. + pub fn deserialize(bytes: &[u8]) -> Result { + let point = if bytes == &NONE_SIGNATURE[..] { + None + } else { + Some(Sig::deserialize(bytes)?) + }; + + Ok(Self { + point, + is_infinity: bytes == &INFINITY_SIGNATURE[..], + _phantom: PhantomData, + }) + } +} + +impl GenericSignature +where + Sig: TSignature, + Pub: TPublicKey + Clone, +{ + /// Returns `true` if `self` is a signature across `msg` by `pubkey`. + pub fn verify(&self, pubkey: &GenericPublicKey, msg: Hash256) -> bool { + if self.is_infinity && pubkey.is_infinity { + return true; + } + + if let Some(point) = &self.point { + point.verify(pubkey.point(), msg) + } else { + false + } + } +} + +impl> Encode for GenericSignature { + impl_ssz_encode!(SIGNATURE_BYTES_LEN); +} + +impl> Decode for GenericSignature { + impl_ssz_decode!(SIGNATURE_BYTES_LEN); +} + +impl> TreeHash for GenericSignature { + impl_tree_hash!(SIGNATURE_BYTES_LEN); +} + +impl> Serialize for GenericSignature { + impl_serde_serialize!(); +} + +impl<'de, PublicKey, T: TSignature> Deserialize<'de> for GenericSignature { + impl_serde_deserialize!(); +} + +impl> fmt::Debug for GenericSignature { + impl_debug!(); +} + +#[cfg(feature = "arbitrary")] +impl + 'static> arbitrary::Arbitrary + for GenericSignature +{ + impl_arbitrary!(SIGNATURE_BYTES_LEN); +} diff --git a/crypto/bls/src/generic_signature_bytes.rs b/crypto/bls/src/generic_signature_bytes.rs new file mode 100644 index 0000000000..1f987ecd36 --- /dev/null +++ b/crypto/bls/src/generic_signature_bytes.rs @@ -0,0 +1,142 @@ +use crate::{ + generic_public_key::TPublicKey, + generic_signature::{GenericSignature, TSignature}, + Error, INFINITY_SIGNATURE, SIGNATURE_BYTES_LEN, +}; +use serde::de::{Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; +use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; +use ssz::{Decode, Encode}; +use std::convert::TryInto; +use std::fmt; +use std::marker::PhantomData; +use tree_hash::TreeHash; + +/// A wrapper around some bytes that may or may not be a `GenericSignature` in compressed form. +/// +/// This struct is useful for two things: +/// +/// - Lazily verifying a serialized signature. +/// - Storing some bytes that are actually invalid (required in the case of a `Deposit` message). +#[derive(Clone)] +pub struct GenericSignatureBytes { + bytes: [u8; SIGNATURE_BYTES_LEN], + _phantom_public_key: PhantomData, + _phantom_signature: PhantomData, +} + +impl GenericSignatureBytes +where + Sig: TSignature, + Pub: TPublicKey, +{ + /// Decompress and deserialize the bytes in `self` into an actual signature. + /// + /// May fail if the bytes are invalid. + pub fn decompress(&self) -> Result, Error> { + let is_infinity = self.bytes[..] == INFINITY_SIGNATURE[..]; + Sig::deserialize(&self.bytes).map(|point| GenericSignature::from_point(point, is_infinity)) + } +} + +impl GenericSignatureBytes { + /// Instantiates `Self` with all-zeros. + pub fn empty() -> Self { + Self { + bytes: [0; SIGNATURE_BYTES_LEN], + _phantom_signature: PhantomData, + _phantom_public_key: PhantomData, + } + } + + /// Clones the bytes in `self`. + /// + /// The bytes are not verified (i.e., they may not represent a valid BLS point). + pub fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] { + self.bytes + } + + /// Instantiates `Self` from bytes. + /// + /// The bytes are not fully verified (i.e., they may not represent a valid BLS point). Only the + /// byte-length is checked. + pub fn deserialize(bytes: &[u8]) -> Result { + if bytes.len() == SIGNATURE_BYTES_LEN { + let mut pk_bytes = [0; SIGNATURE_BYTES_LEN]; + pk_bytes[..].copy_from_slice(bytes); + Ok(Self { + bytes: pk_bytes, + _phantom_signature: PhantomData, + _phantom_public_key: PhantomData, + }) + } else { + Err(Error::InvalidByteLength { + got: bytes.len(), + expected: SIGNATURE_BYTES_LEN, + }) + } + } +} + +impl PartialEq for GenericSignatureBytes { + fn eq(&self, other: &Self) -> bool { + self.bytes[..] == other.bytes[..] + } +} + +/// Serializes the `GenericSignature` in compressed form, storing the bytes in the newly created `Self`. +impl From> for GenericSignatureBytes +where + Pub: TPublicKey, + Sig: TSignature, +{ + fn from(sig: GenericSignature) -> Self { + Self { + bytes: sig.serialize(), + _phantom_signature: PhantomData, + _phantom_public_key: PhantomData, + } + } +} + +/// Alias to `self.decompress()`. +impl TryInto> for &GenericSignatureBytes +where + Pub: TPublicKey, + Sig: TSignature, +{ + type Error = Error; + + fn try_into(self) -> Result, Error> { + self.decompress() + } +} + +impl Encode for GenericSignatureBytes { + impl_ssz_encode!(SIGNATURE_BYTES_LEN); +} + +impl Decode for GenericSignatureBytes { + impl_ssz_decode!(SIGNATURE_BYTES_LEN); +} + +impl TreeHash for GenericSignatureBytes { + impl_tree_hash!(SIGNATURE_BYTES_LEN); +} + +impl Serialize for GenericSignatureBytes { + impl_serde_serialize!(); +} + +impl<'de, Pub, Sig> Deserialize<'de> for GenericSignatureBytes { + impl_serde_deserialize!(); +} + +impl fmt::Debug for GenericSignatureBytes { + impl_debug!(); +} + +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary for GenericSignatureBytes { + impl_arbitrary!(SIGNATURE_BYTES_LEN); +} diff --git a/crypto/bls/src/generic_signature_set.rs b/crypto/bls/src/generic_signature_set.rs new file mode 100644 index 0000000000..2856678989 --- /dev/null +++ b/crypto/bls/src/generic_signature_set.rs @@ -0,0 +1,121 @@ +use crate::{ + generic_aggregate_public_key::TAggregatePublicKey, + generic_aggregate_signature::{GenericAggregateSignature, TAggregateSignature}, + generic_public_key::{GenericPublicKey, TPublicKey}, + generic_signature::{GenericSignature, TSignature}, + Hash256, +}; +use std::borrow::Cow; +use std::marker::PhantomData; + +/// A generic way to represent a `GenericSignature` or `GenericAggregateSignature`. +pub struct WrappedSignature<'a, Pub, AggPub, Sig, AggSig> +where + Pub: TPublicKey + Clone, + AggPub: Clone, + Sig: Clone, + AggSig: Clone, +{ + aggregate: Cow<'a, GenericAggregateSignature>, +} + +impl<'a, Pub, AggPub, Sig, AggSig> Into> + for &'a GenericSignature +where + Pub: TPublicKey + Clone, + AggPub: Clone, + Sig: TSignature + Clone, + AggSig: TAggregateSignature + Clone, +{ + fn into(self) -> WrappedSignature<'a, Pub, AggPub, Sig, AggSig> { + let mut aggregate: GenericAggregateSignature = + GenericAggregateSignature::infinity(); + aggregate.add_assign(self); + WrappedSignature { + aggregate: Cow::Owned(aggregate), + } + } +} + +impl<'a, Pub, AggPub, Sig, AggSig> Into> + for &'a GenericAggregateSignature +where + Pub: TPublicKey + Clone, + AggPub: Clone, + Sig: Clone, + AggSig: Clone, +{ + fn into(self) -> WrappedSignature<'a, Pub, AggPub, Sig, AggSig> { + WrappedSignature { + aggregate: Cow::Borrowed(self), + } + } +} + +/// A generic way to represent a signature across a message by multiple public keys. +/// +/// This struct is primarily useful in a collection (e.g., `Vec`) so we can perform +/// multiple-signature verification which is much faster than verifying each signature +/// individually. +#[derive(Clone)] +pub struct GenericSignatureSet<'a, Pub, AggPub, Sig, AggSig> +where + Pub: TPublicKey + Clone, + AggPub: Clone, + Sig: Clone, + AggSig: Clone, +{ + pub signature: Cow<'a, GenericAggregateSignature>, + pub(crate) signing_keys: Vec>>, + pub(crate) message: Hash256, + _phantom: PhantomData, +} + +impl<'a, Pub, AggPub, Sig, AggSig> GenericSignatureSet<'a, Pub, AggPub, Sig, AggSig> +where + Pub: TPublicKey + Clone, + AggPub: TAggregatePublicKey + Clone, + Sig: TSignature + Clone, + AggSig: TAggregateSignature + Clone, +{ + /// Instantiate self where `signature` is only signed by a single public key. + pub fn single_pubkey( + signature: impl Into>, + signing_key: Cow<'a, GenericPublicKey>, + message: Hash256, + ) -> Self { + Self { + signature: signature.into().aggregate, + signing_keys: vec![signing_key], + message, + _phantom: PhantomData, + } + } + + /// Instantiate self where `signature` is signed by multiple public keys. + pub fn multiple_pubkeys( + signature: impl Into>, + signing_keys: Vec>>, + message: Hash256, + ) -> Self { + Self { + signature: signature.into().aggregate, + signing_keys, + message, + _phantom: PhantomData, + } + } + + /// Returns `true` if `self.signature` is a signature across `self.message` by + /// `self.signing_keys`. + pub fn verify(self) -> bool { + let pubkeys = self + .signing_keys + .iter() + .map(|pk| pk.as_ref()) + .collect::>(); + + self.signature + .fast_aggregate_verify(self.message, &pubkeys[..]) + } +} diff --git a/crypto/bls/src/get_withdrawal_credentials.rs b/crypto/bls/src/get_withdrawal_credentials.rs new file mode 100644 index 0000000000..98106434f1 --- /dev/null +++ b/crypto/bls/src/get_withdrawal_credentials.rs @@ -0,0 +1,14 @@ +use crate::PublicKey; +use eth2_hashing::hash; +use ssz::Encode; + +/// Returns the withdrawal credentials for a given public key. +/// +/// Used for submitting deposits to the Eth1 deposit contract. +pub fn get_withdrawal_credentials(pubkey: &PublicKey, prefix_byte: u8) -> Vec { + let hashed = hash(&pubkey.as_ssz_bytes()); + let mut prefixed = vec![prefix_byte]; + prefixed.extend_from_slice(&hashed[1..]); + + prefixed +} diff --git a/crypto/bls/src/impls/blst.rs b/crypto/bls/src/impls/blst.rs new file mode 100644 index 0000000000..3700c40f76 --- /dev/null +++ b/crypto/bls/src/impls/blst.rs @@ -0,0 +1,273 @@ +use crate::{ + generic_aggregate_public_key::TAggregatePublicKey, + generic_aggregate_signature::TAggregateSignature, + generic_public_key::{GenericPublicKey, TPublicKey, PUBLIC_KEY_BYTES_LEN}, + generic_secret_key::TSecretKey, + generic_signature::{TSignature, SIGNATURE_BYTES_LEN}, + Error, Hash256, ZeroizeHash, INFINITY_PUBLIC_KEY, INFINITY_SIGNATURE, +}; +pub use blst::min_pk as blst_core; +use blst::{blst_scalar, BLST_ERROR}; +use rand::Rng; +use std::iter::ExactSizeIterator; + +pub const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; +pub const RAND_BITS: usize = 64; + +/// Provides the externally-facing, core BLS types. +pub mod types { + pub use super::blst_core::PublicKey; + pub use super::blst_core::SecretKey; + pub use super::blst_core::Signature; + pub use super::verify_signature_sets; + pub use super::BlstAggregatePublicKey as AggregatePublicKey; + pub use super::BlstAggregateSignature as AggregateSignature; + pub use super::SignatureSet; +} + +pub type SignatureSet<'a> = crate::generic_signature_set::GenericSignatureSet< + 'a, + blst_core::PublicKey, + BlstAggregatePublicKey, + blst_core::Signature, + BlstAggregateSignature, +>; + +pub fn verify_signature_sets<'a>( + signature_sets: impl ExactSizeIterator>, +) -> bool { + let sets = signature_sets.collect::>(); + + if sets.is_empty() { + return false; + } + + let rng = &mut rand::thread_rng(); + + let mut rands: Vec = Vec::with_capacity(sets.len()); + let mut msgs_refs = Vec::with_capacity(sets.len()); + let mut sigs = Vec::with_capacity(sets.len()); + let mut pks = Vec::with_capacity(sets.len()); + + for set in &sets { + // If this set is simply an infinity signature and infinity pubkey then skip verification. + // This has the effect of always declaring that this sig/pubkey combination is valid. + if set.signature.is_infinity + && set.signing_keys.len() == 1 + && set.signing_keys.first().map_or(false, |pk| pk.is_infinity) + { + continue; + } + + // Generate random scalars. + let mut vals = [0u64; 4]; + vals[0] = rng.gen(); + let mut rand_i = std::mem::MaybeUninit::::uninit(); + + // TODO: remove this `unsafe` code-block once we get a safe option from `blst`. + // + // See https://github.com/supranational/blst/issues/13 + unsafe { + blst::blst_scalar_from_uint64(rand_i.as_mut_ptr(), vals.as_ptr()); + rands.push(rand_i.assume_init()); + } + + // Grab a slice of the message, to satisfy the blst API. + msgs_refs.push(set.message.as_bytes()); + + // Convert the aggregate signature into a signature. + if let Some(point) = set.signature.point() { + sigs.push(point.0.to_signature()) + } else { + // Any "empty" signature should cause a signature failure. + return false; + } + + // Sanity check. + if set.signing_keys.is_empty() { + // A signature that has no signing keys is invalid. + return false; + } + + // Collect all the public keys into a point, to satisfy the blst API. + // + // Note: we could potentially have the `SignatureSet` take a pubkey point instead of a + // `GenericPublicKey` and avoid this allocation. + let signing_keys = set + .signing_keys + .iter() + .map(|pk| pk.point()) + .collect::>(); + + // Aggregate all the public keys. + pks.push(blst_core::AggregatePublicKey::aggregate(&signing_keys).to_public_key()); + } + + // Due to an earlier check, the only case this can be empty is if all the sets consisted of + // infinity pubkeys/sigs. In such a case we wish to return `true`. + if msgs_refs.is_empty() { + return true; + } + + let (sig_refs, pks_refs): (Vec<_>, Vec<_>) = sigs.iter().zip(pks.iter()).unzip(); + + let err = blst_core::Signature::verify_multiple_aggregate_signatures( + &msgs_refs, DST, &pks_refs, &sig_refs, &rands, RAND_BITS, + ); + + err == blst::BLST_ERROR::BLST_SUCCESS +} + +impl TPublicKey for blst_core::PublicKey { + fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] { + self.compress() + } + + fn deserialize(bytes: &[u8]) -> Result { + Self::uncompress(&bytes).map_err(Into::into) + } +} + +/// A wrapper that allows for `PartialEq` and `Clone` impls. +pub struct BlstAggregatePublicKey(blst_core::AggregatePublicKey); + +impl Clone for BlstAggregatePublicKey { + fn clone(&self) -> Self { + Self(blst_core::AggregatePublicKey::from_public_key( + &self.0.to_public_key(), + )) + } +} + +impl PartialEq for BlstAggregatePublicKey { + fn eq(&self, other: &Self) -> bool { + self.0.to_public_key() == other.0.to_public_key() + } +} + +impl TAggregatePublicKey for BlstAggregatePublicKey { + fn infinity() -> Self { + blst_core::PublicKey::from_bytes(&INFINITY_PUBLIC_KEY) + .map(|pk| blst_core::AggregatePublicKey::from_public_key(&pk)) + .map(Self) + .expect("should decode infinity public key") + } + + fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] { + self.0.to_public_key().compress() + } + + fn deserialize(bytes: &[u8]) -> Result { + blst_core::PublicKey::from_bytes(&bytes) + .map_err(Into::into) + .map(|pk| blst_core::AggregatePublicKey::from_public_key(&pk)) + .map(Self) + } +} + +impl TSignature for blst_core::Signature { + fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] { + self.to_bytes() + } + + fn deserialize(bytes: &[u8]) -> Result { + Self::from_bytes(bytes).map_err(Into::into) + } + + fn verify(&self, pubkey: &blst_core::PublicKey, msg: Hash256) -> bool { + self.verify(msg.as_bytes(), DST, &[], pubkey) == BLST_ERROR::BLST_SUCCESS + } +} + +/// A wrapper that allows for `PartialEq` and `Clone` impls. +pub struct BlstAggregateSignature(blst_core::AggregateSignature); + +impl Clone for BlstAggregateSignature { + fn clone(&self) -> Self { + Self(blst_core::AggregateSignature::from_signature( + &self.0.to_signature(), + )) + } +} + +impl PartialEq for BlstAggregateSignature { + fn eq(&self, other: &Self) -> bool { + self.0.to_signature() == other.0.to_signature() + } +} + +impl TAggregateSignature + for BlstAggregateSignature +{ + fn infinity() -> Self { + blst_core::Signature::from_bytes(&INFINITY_SIGNATURE) + .map(|sig| blst_core::AggregateSignature::from_signature(&sig)) + .map(Self) + .expect("should decode infinity signature") + } + + fn add_assign(&mut self, other: &blst_core::Signature) { + self.0.add_signature(other) + } + + fn add_assign_aggregate(&mut self, other: &Self) { + self.0.add_aggregate(&other.0) + } + + fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] { + self.0.to_signature().to_bytes() + } + + fn deserialize(bytes: &[u8]) -> Result { + blst_core::Signature::from_bytes(bytes) + .map_err(Into::into) + .map(|sig| blst_core::AggregateSignature::from_signature(&sig)) + .map(Self) + } + + fn fast_aggregate_verify( + &self, + msg: Hash256, + pubkeys: &[&GenericPublicKey], + ) -> bool { + let pubkeys = pubkeys.iter().map(|pk| pk.point()).collect::>(); + let signature = self.0.clone().to_signature(); + signature.fast_aggregate_verify(msg.as_bytes(), DST, &pubkeys) == BLST_ERROR::BLST_SUCCESS + } + + fn aggregate_verify( + &self, + msgs: &[Hash256], + pubkeys: &[&GenericPublicKey], + ) -> bool { + let pubkeys = pubkeys.iter().map(|pk| pk.point()).collect::>(); + let msgs = msgs.iter().map(|hash| hash.as_bytes()).collect::>(); + let signature = self.0.clone().to_signature(); + signature.aggregate_verify(&msgs, DST, &pubkeys) == BLST_ERROR::BLST_SUCCESS + } +} + +impl TSecretKey for blst_core::SecretKey { + fn random() -> Self { + let rng = &mut rand::thread_rng(); + let ikm: [u8; 32] = rng.gen(); + + Self::key_gen(&ikm, &[]).unwrap() + } + + fn public_key(&self) -> blst_core::PublicKey { + self.sk_to_pk() + } + + fn sign(&self, msg: Hash256) -> blst_core::Signature { + self.sign(msg.as_bytes(), DST, &[]) + } + + fn serialize(&self) -> ZeroizeHash { + self.to_bytes().into() + } + + fn deserialize(bytes: &[u8]) -> Result { + Self::from_bytes(&bytes).map_err(Into::into) + } +} diff --git a/crypto/bls/src/impls/fake_crypto.rs b/crypto/bls/src/impls/fake_crypto.rs new file mode 100644 index 0000000000..d5501d5875 --- /dev/null +++ b/crypto/bls/src/impls/fake_crypto.rs @@ -0,0 +1,215 @@ +use crate::{ + generic_aggregate_public_key::TAggregatePublicKey, + generic_aggregate_signature::TAggregateSignature, + generic_public_key::{GenericPublicKey, TPublicKey, PUBLIC_KEY_BYTES_LEN}, + generic_secret_key::{TSecretKey, SECRET_KEY_BYTES_LEN}, + generic_signature::{TSignature, SIGNATURE_BYTES_LEN}, + Error, Hash256, ZeroizeHash, INFINITY_PUBLIC_KEY, INFINITY_SIGNATURE, +}; +/// Provides the externally-facing, core BLS types. +pub mod types { + pub use super::verify_signature_sets; + pub use super::AggregatePublicKey; + pub use super::AggregateSignature; + pub use super::PublicKey; + pub use super::SecretKey; + pub use super::Signature; + pub use super::SignatureSet; +} + +pub type SignatureSet<'a> = crate::generic_signature_set::GenericSignatureSet< + 'a, + PublicKey, + AggregatePublicKey, + Signature, + AggregateSignature, +>; + +pub fn verify_signature_sets<'a>( + _signature_sets: impl ExactSizeIterator>, +) -> bool { + true +} + +#[derive(Clone)] +pub struct PublicKey([u8; PUBLIC_KEY_BYTES_LEN]); + +impl PublicKey { + fn infinity() -> Self { + Self(INFINITY_PUBLIC_KEY) + } +} + +impl TPublicKey for PublicKey { + fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] { + self.0 + } + + fn deserialize(bytes: &[u8]) -> Result { + let mut pubkey = Self::infinity(); + pubkey.0[..].copy_from_slice(&bytes[0..PUBLIC_KEY_BYTES_LEN]); + Ok(pubkey) + } +} + +impl Eq for PublicKey {} + +impl PartialEq for PublicKey { + fn eq(&self, other: &Self) -> bool { + self.0[..] == other.0[..] + } +} + +#[derive(Clone)] +pub struct AggregatePublicKey([u8; PUBLIC_KEY_BYTES_LEN]); + +impl TAggregatePublicKey for AggregatePublicKey { + fn infinity() -> Self { + Self([0; PUBLIC_KEY_BYTES_LEN]) + } + + fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] { + let mut bytes = [0; PUBLIC_KEY_BYTES_LEN]; + bytes[..].copy_from_slice(&self.0); + bytes + } + + fn deserialize(bytes: &[u8]) -> Result { + let mut key = [0; PUBLIC_KEY_BYTES_LEN]; + + key[..].copy_from_slice(&bytes); + + Ok(Self(key)) + } +} + +impl Eq for AggregatePublicKey {} + +impl PartialEq for AggregatePublicKey { + fn eq(&self, other: &Self) -> bool { + self.0[..] == other.0[..] + } +} + +#[derive(Clone)] +pub struct Signature([u8; SIGNATURE_BYTES_LEN]); + +impl Signature { + fn infinity() -> Self { + Self([0; SIGNATURE_BYTES_LEN]) + } +} + +impl TSignature for Signature { + fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] { + self.0 + } + + fn deserialize(bytes: &[u8]) -> Result { + let mut signature = Self::infinity(); + signature.0[..].copy_from_slice(&bytes[0..SIGNATURE_BYTES_LEN]); + Ok(signature) + } + + fn verify(&self, _pubkey: &PublicKey, _msg: Hash256) -> bool { + true + } +} + +impl PartialEq for Signature { + fn eq(&self, other: &Self) -> bool { + self.0[..] == other.0[..] + } +} + +#[derive(Clone)] +pub struct AggregateSignature([u8; SIGNATURE_BYTES_LEN]); + +impl AggregateSignature { + fn infinity() -> Self { + Self(INFINITY_SIGNATURE) + } +} + +impl TAggregateSignature for AggregateSignature { + fn infinity() -> Self { + Self::infinity() + } + + fn add_assign(&mut self, _other: &Signature) { + // Do nothing. + } + + fn add_assign_aggregate(&mut self, _other: &Self) { + // Do nothing. + } + + fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] { + let mut bytes = [0; SIGNATURE_BYTES_LEN]; + + bytes[..].copy_from_slice(&self.0); + + bytes + } + + fn deserialize(bytes: &[u8]) -> Result { + let mut key = [0; SIGNATURE_BYTES_LEN]; + + key[..].copy_from_slice(&bytes); + + Ok(Self(key)) + } + + fn fast_aggregate_verify( + &self, + _msg: Hash256, + _pubkeys: &[&GenericPublicKey], + ) -> bool { + true + } + + fn aggregate_verify( + &self, + _msgs: &[Hash256], + _pubkeys: &[&GenericPublicKey], + ) -> bool { + true + } +} + +impl Eq for AggregateSignature {} + +impl PartialEq for AggregateSignature { + fn eq(&self, other: &Self) -> bool { + self.0[..] == other.0[..] + } +} + +#[derive(Clone)] +pub struct SecretKey([u8; SECRET_KEY_BYTES_LEN]); + +impl TSecretKey for SecretKey { + fn random() -> Self { + Self([0; SECRET_KEY_BYTES_LEN]) + } + + fn public_key(&self) -> PublicKey { + PublicKey::infinity() + } + + fn sign(&self, _msg: Hash256) -> Signature { + Signature::infinity() + } + + fn serialize(&self) -> ZeroizeHash { + let mut bytes = [0; SECRET_KEY_BYTES_LEN]; + bytes[..].copy_from_slice(&self.0[..]); + bytes.into() + } + + fn deserialize(bytes: &[u8]) -> Result { + let mut sk = Self::random(); + sk.0[..].copy_from_slice(&bytes[0..SECRET_KEY_BYTES_LEN]); + Ok(sk) + } +} diff --git a/crypto/bls/src/impls/milagro.rs b/crypto/bls/src/impls/milagro.rs new file mode 100644 index 0000000000..2830c99873 --- /dev/null +++ b/crypto/bls/src/impls/milagro.rs @@ -0,0 +1,197 @@ +use crate::{ + generic_aggregate_public_key::TAggregatePublicKey, + generic_aggregate_signature::TAggregateSignature, + generic_public_key::{GenericPublicKey, TPublicKey, PUBLIC_KEY_BYTES_LEN}, + generic_secret_key::{TSecretKey, SECRET_KEY_BYTES_LEN}, + generic_signature::{TSignature, SIGNATURE_BYTES_LEN}, + Error, Hash256, ZeroizeHash, INFINITY_PUBLIC_KEY, +}; +pub use milagro_bls as milagro; +use rand::thread_rng; +use std::iter::ExactSizeIterator; + +/// Provides the externally-facing, core BLS types. +pub mod types { + pub use super::milagro::AggregatePublicKey; + pub use super::milagro::AggregateSignature; + pub use super::milagro::PublicKey; + pub use super::milagro::SecretKey; + pub use super::milagro::Signature; + pub use super::verify_signature_sets; + pub use super::SignatureSet; +} + +pub type SignatureSet<'a> = crate::generic_signature_set::GenericSignatureSet< + 'a, + milagro::PublicKey, + milagro::AggregatePublicKey, + milagro::Signature, + milagro::AggregateSignature, +>; + +pub fn verify_signature_sets<'a>( + signature_sets: impl ExactSizeIterator>, +) -> bool { + if signature_sets.len() == 0 { + return false; + } + + signature_sets + .map(|signature_set| { + let mut aggregate = milagro::AggregatePublicKey::from_public_key( + signature_set.signing_keys.first().ok_or(())?.point(), + ); + + for signing_key in signature_set.signing_keys.iter().skip(1) { + aggregate.add(signing_key.point()) + } + + if signature_set.signature.point().is_none() { + return Err(()); + } + + Ok(( + signature_set.signature.as_ref(), + aggregate, + signature_set.message, + )) + }) + .collect::, ()>>() + .map(|aggregates| { + milagro::AggregateSignature::verify_multiple_aggregate_signatures( + &mut rand::thread_rng(), + aggregates.iter().map(|(signature, aggregate, message)| { + ( + signature + .point() + .expect("guarded against none by previous check"), + aggregate, + message.as_bytes(), + ) + }), + ) + }) + .unwrap_or(false) +} + +impl TPublicKey for milagro::PublicKey { + fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] { + let mut bytes = [0; PUBLIC_KEY_BYTES_LEN]; + bytes[..].copy_from_slice(&self.as_bytes()); + bytes + } + + fn deserialize(bytes: &[u8]) -> Result { + Self::from_bytes(&bytes).map_err(Into::into) + } +} + +impl TAggregatePublicKey for milagro::AggregatePublicKey { + fn infinity() -> Self { + Self::from_bytes(&INFINITY_PUBLIC_KEY).expect("should decode infinity public key") + } + + fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] { + let mut bytes = [0; PUBLIC_KEY_BYTES_LEN]; + bytes[..].copy_from_slice(&self.as_bytes()); + bytes + } + + fn deserialize(bytes: &[u8]) -> Result { + Self::from_bytes(&bytes).map_err(Into::into) + } +} + +impl TSignature for milagro::Signature { + fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] { + let mut bytes = [0; SIGNATURE_BYTES_LEN]; + + bytes[..].copy_from_slice(&self.as_bytes()); + + bytes + } + + fn deserialize(bytes: &[u8]) -> Result { + milagro::Signature::from_bytes(&bytes).map_err(Error::MilagroError) + } + + fn verify(&self, pubkey: &milagro::PublicKey, msg: Hash256) -> bool { + self.verify(msg.as_bytes(), pubkey) + } +} + +impl TAggregateSignature + for milagro::AggregateSignature +{ + fn infinity() -> Self { + milagro::AggregateSignature::new() + } + + fn add_assign(&mut self, other: &milagro::Signature) { + self.add(other) + } + + fn add_assign_aggregate(&mut self, other: &Self) { + self.add_aggregate(other) + } + + fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] { + let mut bytes = [0; SIGNATURE_BYTES_LEN]; + + bytes[..].copy_from_slice(&self.as_bytes()); + + bytes + } + + fn deserialize(bytes: &[u8]) -> Result { + milagro::AggregateSignature::from_bytes(&bytes).map_err(Error::MilagroError) + } + + fn fast_aggregate_verify( + &self, + msg: Hash256, + pubkeys: &[&GenericPublicKey], + ) -> bool { + let pubkeys = pubkeys.iter().map(|pk| pk.point()).collect::>(); + self.fast_aggregate_verify(msg.as_bytes(), &pubkeys) + } + + fn aggregate_verify( + &self, + msgs: &[Hash256], + pubkeys: &[&GenericPublicKey], + ) -> bool { + let pubkeys = pubkeys.iter().map(|pk| pk.point()).collect::>(); + let msgs = msgs.iter().map(|hash| hash.as_bytes()).collect::>(); + self.aggregate_verify(&msgs, &pubkeys) + } +} + +impl TSecretKey for milagro::SecretKey { + fn random() -> Self { + Self::random(&mut thread_rng()) + } + + fn public_key(&self) -> milagro::PublicKey { + let point = milagro::PublicKey::from_secret_key(self).point; + milagro::PublicKey { point } + } + + fn sign(&self, msg: Hash256) -> milagro::Signature { + let point = milagro::Signature::new(msg.as_bytes(), self).point; + milagro::Signature { point } + } + + fn serialize(&self) -> ZeroizeHash { + let mut bytes = [0; SECRET_KEY_BYTES_LEN]; + + // Takes the right-hand 32 bytes from the secret key. + bytes[..].copy_from_slice(&self.as_bytes()); + + bytes.into() + } + + fn deserialize(bytes: &[u8]) -> Result { + Self::from_bytes(&bytes).map_err(Into::into) + } +} diff --git a/crypto/bls/src/impls/mod.rs b/crypto/bls/src/impls/mod.rs new file mode 100644 index 0000000000..1bcac20a5b --- /dev/null +++ b/crypto/bls/src/impls/mod.rs @@ -0,0 +1,3 @@ +pub mod blst; +pub mod fake_crypto; +pub mod milagro; diff --git a/crypto/bls/src/keypair.rs b/crypto/bls/src/keypair.rs deleted file mode 100644 index 756197c2ff..0000000000 --- a/crypto/bls/src/keypair.rs +++ /dev/null @@ -1,41 +0,0 @@ -use super::{PublicKey, SecretKey}; -use std::fmt; -use std::hash::{Hash, Hasher}; - -#[derive(Clone)] -pub struct Keypair { - pub sk: SecretKey, - pub pk: PublicKey, -} - -impl Keypair { - /// Instantiate a Keypair using SecretKey::random(). - pub fn random() -> Self { - let sk = SecretKey::random(); - let pk = PublicKey::from_secret_key(&sk); - Keypair { sk, pk } - } - - pub fn identifier(&self) -> String { - self.pk.concatenated_hex_id() - } -} - -#[allow(clippy::derive_hash_xor_eq)] -impl Hash for Keypair { - /// Note: this is distinct from consensus serialization, it will produce a different hash. - /// - /// This method uses the uncompressed bytes, which are much faster to obtain than the - /// compressed bytes required for consensus serialization. - /// - /// Use `ssz::Encode` to obtain the bytes required for consensus hashing. - fn hash(&self, state: &mut H) { - self.pk.as_uncompressed_bytes().hash(state) - } -} - -impl fmt::Display for Keypair { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.pk) - } -} diff --git a/crypto/bls/src/lib.rs b/crypto/bls/src/lib.rs index dff6403d16..a22fdd8bf7 100644 --- a/crypto/bls/src/lib.rs +++ b/crypto/bls/src/lib.rs @@ -1,84 +1,140 @@ -extern crate milagro_bls; -extern crate ssz; +//! This library provides a wrapper around several BLS implementations to provide +//! Lighthouse-specific functionality. +//! +//! This crate should not perform direct cryptographic operations, instead it should do these via +//! external libraries. However, seeing as it is an interface to a real cryptographic library, it +//! may contain logic that affects the outcomes of cryptographic operations. +//! +//! A source of complexity in this crate is that *multiple* BLS implementations (a.k.a. "backends") +//! are supported via compile-time flags. There are three backends supported via features: +//! +//! - `supranational`: the pure-assembly, highly optimized version from the `blst` crate. +//! - `milagro`: the classic pure-Rust `milagro_bls` crate. +//! - `fake_crypto`: an always-returns-valid implementation that is only useful for testing +//! scenarios which intend to *ignore* real cryptography. +//! +//! This crate uses traits to reduce code-duplication between the two implementations. For example, +//! the `GenericPublicKey` struct exported from this crate is generic across the `TPublicKey` trait +//! (i.e., `PublicKey`). `TPublicKey` is implemented by all three backends (see the +//! `impls.rs` module). When compiling with the `milagro` feature, we export +//! `type PublicKey = GenericPublicKey`. #[macro_use] mod macros; -mod keypair; -mod public_key_bytes; -mod secret_hash; -mod secret_key; -mod signature_bytes; -mod signature_set; - -pub use crate::keypair::Keypair; -pub use crate::public_key_bytes::PublicKeyBytes; -pub use crate::secret_key::SecretKey; -pub use crate::signature_bytes::SignatureBytes; -pub use secret_hash::SecretHash; -pub use signature_set::{verify_signature_sets, SignatureSet}; - -#[cfg(feature = "arbitrary")] -pub use arbitrary; +mod generic_aggregate_public_key; +mod generic_aggregate_signature; +mod generic_keypair; +mod generic_public_key; +mod generic_public_key_bytes; +mod generic_secret_key; +mod generic_signature; +mod generic_signature_bytes; +mod generic_signature_set; +mod get_withdrawal_credentials; +mod zeroize_hash; -#[cfg(feature = "fake_crypto")] -mod fake_aggregate_public_key; -#[cfg(feature = "fake_crypto")] -mod fake_aggregate_signature; -#[cfg(feature = "fake_crypto")] -mod fake_public_key; -#[cfg(feature = "fake_crypto")] -mod fake_signature; +pub mod impls; -#[cfg(not(feature = "fake_crypto"))] -mod aggregate_public_key; -#[cfg(not(feature = "fake_crypto"))] -mod aggregate_signature; -#[cfg(not(feature = "fake_crypto"))] -mod public_key; -#[cfg(not(feature = "fake_crypto"))] -mod signature; +pub use generic_public_key::{INFINITY_PUBLIC_KEY, PUBLIC_KEY_BYTES_LEN}; +pub use generic_secret_key::SECRET_KEY_BYTES_LEN; +pub use generic_signature::{INFINITY_SIGNATURE, SIGNATURE_BYTES_LEN}; +pub use get_withdrawal_credentials::get_withdrawal_credentials; +pub use zeroize_hash::ZeroizeHash; -#[cfg(feature = "fake_crypto")] -pub use fakes::*; -#[cfg(feature = "fake_crypto")] -mod fakes { - pub use crate::fake_aggregate_public_key::FakeAggregatePublicKey as AggregatePublicKey; - pub use crate::fake_aggregate_signature::FakeAggregateSignature as AggregateSignature; - pub use crate::fake_public_key::FakePublicKey as PublicKey; - pub use crate::fake_signature::FakeSignature as Signature; +use blst::BLST_ERROR as BlstError; +use milagro_bls::AmclError; + +pub type Hash256 = ethereum_types::H256; + +#[derive(Clone, Debug, PartialEq)] +pub enum Error { + /// An error was raised from the Milagro BLS library. + MilagroError(AmclError), + /// An error was raised from the Supranational BLST BLS library. + BlstError(BlstError), + /// The provided bytes were an incorrect length. + InvalidByteLength { got: usize, expected: usize }, + /// The provided secret key bytes were an incorrect length. + InvalidSecretKeyLength { got: usize, expected: usize }, +} + +impl From for Error { + fn from(e: AmclError) -> Error { + Error::MilagroError(e) + } } -#[cfg(not(feature = "fake_crypto"))] -pub use reals::*; -#[cfg(not(feature = "fake_crypto"))] -mod reals { - pub use crate::aggregate_public_key::AggregatePublicKey; - pub use crate::aggregate_signature::AggregateSignature; - pub use crate::public_key::PublicKey; - pub use crate::signature::Signature; +impl From for Error { + fn from(e: BlstError) -> Error { + Error::BlstError(e) + } } -pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96; -pub const BLS_SIG_BYTE_SIZE: usize = 96; -pub const BLS_SECRET_KEY_BYTE_SIZE: usize = 32; -pub const BLS_PUBLIC_KEY_BYTE_SIZE: usize = 48; +/// Generic implementations which are only generally useful for docs. +pub mod generics { + pub use crate::generic_aggregate_signature::GenericAggregateSignature; + pub use crate::generic_keypair::GenericKeypair; + pub use crate::generic_public_key::GenericPublicKey; + pub use crate::generic_public_key_bytes::GenericPublicKeyBytes; + pub use crate::generic_secret_key::GenericSecretKey; + pub use crate::generic_signature::GenericSignature; + pub use crate::generic_signature_bytes::GenericSignatureBytes; +} -use eth2_hashing::hash; -use ssz::ssz_encode; +/// Defines all the fundamental BLS points which should be exported by this crate by making +/// concrete the generic type parameters using the points from some external BLS library (e.g., +/// Milagro, BLST). +macro_rules! define_mod { + ($name: ident, $mod: path) => { + pub mod $name { + use $mod as bls_variant; -/// Returns the withdrawal credentials for a given public key. -pub fn get_withdrawal_credentials(pubkey: &PublicKey, prefix_byte: u8) -> Vec { - let hashed = hash(&ssz_encode(pubkey)); - let mut prefixed = vec![prefix_byte]; - prefixed.extend_from_slice(&hashed[1..]); + use crate::generics::*; - prefixed -} + pub use bls_variant::{verify_signature_sets, SignatureSet}; -pub fn bls_verify_aggregate( - pubkey: &AggregatePublicKey, - message: &[u8], - signature: &AggregateSignature, -) -> bool { - signature.verify(message, pubkey) + pub type PublicKey = GenericPublicKey; + pub type PublicKeyBytes = GenericPublicKeyBytes; + pub type Signature = GenericSignature; + pub type AggregateSignature = GenericAggregateSignature< + bls_variant::PublicKey, + bls_variant::AggregatePublicKey, + bls_variant::Signature, + bls_variant::AggregateSignature, + >; + pub type SignatureBytes = + GenericSignatureBytes; + pub type SecretKey = GenericSecretKey< + bls_variant::Signature, + bls_variant::PublicKey, + bls_variant::SecretKey, + >; + pub type Keypair = GenericKeypair< + bls_variant::PublicKey, + bls_variant::SecretKey, + bls_variant::Signature, + >; + } + }; } + +define_mod!(milagro_implementations, crate::impls::milagro::types); +define_mod!(blst_implementations, crate::impls::blst::types); +#[cfg(feature = "fake_crypto")] +define_mod!( + fake_crypto_implementations, + crate::impls::fake_crypto::types +); + +#[cfg(all(feature = "milagro", not(feature = "fake_crypto"),))] +pub use milagro_implementations::*; + +#[cfg(all( + feature = "supranational", + not(feature = "fake_crypto"), + not(feature = "milagro") +))] +pub use blst_implementations::*; + +#[cfg(feature = "fake_crypto")] +pub use fake_crypto_implementations::*; diff --git a/crypto/bls/src/macros.rs b/crypto/bls/src/macros.rs index 8a2f386e51..ca103da6da 100644 --- a/crypto/bls/src/macros.rs +++ b/crypto/bls/src/macros.rs @@ -1,265 +1,132 @@ -macro_rules! impl_ssz { - ($type: ident, $byte_size: expr, $item_str: expr) => { - impl ssz::Encode for $type { - fn is_ssz_fixed_len() -> bool { - true - } - - fn ssz_fixed_len() -> usize { - $byte_size - } - - fn ssz_bytes_len(&self) -> usize { - $byte_size - } - - fn ssz_append(&self, buf: &mut Vec) { - buf.extend_from_slice(&self.as_bytes()) - } +/// Contains the functions required for a `TreeHash` implementation. +/// +/// Does not include the `Impl` section since it gets very complicated when it comes to generics. +macro_rules! impl_tree_hash { + ($byte_size: expr) => { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::Vector } - impl ssz::Decode for $type { - fn is_ssz_fixed_len() -> bool { - true - } - - fn ssz_fixed_len() -> usize { - $byte_size - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - let len = bytes.len(); - let expected = ::ssz_fixed_len(); - - if len != expected { - Err(ssz::DecodeError::InvalidByteLength { len, expected }) - } else { - $type::from_bytes(bytes) - } - } + fn tree_hash_packed_encoding(&self) -> Vec { + unreachable!("Vector should never be packed.") } - }; -} - -macro_rules! impl_tree_hash { - ($type: ty, $byte_size: expr) => { - impl tree_hash::TreeHash for $type { - fn tree_hash_type() -> tree_hash::TreeHashType { - tree_hash::TreeHashType::Vector - } - fn tree_hash_packed_encoding(&self) -> Vec { - unreachable!("Vector should never be packed.") - } - - fn tree_hash_packing_factor() -> usize { - unreachable!("Vector should never be packed.") - } - - fn tree_hash_root(&self) -> tree_hash::Hash256 { - // We could use the tree hash implementation for `FixedVec`, - // but benchmarks have show that to be at least 15% slower because of the - // unnecessary copying and allocation (one Vec per byte) - let values_per_chunk = tree_hash::BYTES_PER_CHUNK; - let minimum_chunk_count = ($byte_size + values_per_chunk - 1) / values_per_chunk; - - let mut hasher = tree_hash::MerkleHasher::with_leaves(minimum_chunk_count); - hasher - .write(&self.as_ssz_bytes()) - .expect("bls should not exceed leaf count"); - hasher - .finish() - .expect("bls should not exceed leaf count from buffer") - } + fn tree_hash_packing_factor() -> usize { + unreachable!("Vector should never be packed.") } - }; -} -macro_rules! bytes_struct { - ($name: ident, $type: ty, $byte_size: expr, $small_name: expr, - $type_str: expr, $byte_size_str: expr) => { - #[doc = "Stores `"] - #[doc = $byte_size_str] - #[doc = "` bytes which may or may not represent a valid BLS "] - #[doc = $small_name] - #[doc = ".\n\nThe `"] - #[doc = $type_str] - #[doc = "` struct performs validation when it is instantiated, where as this struct does \ - not. This struct is suitable where we may wish to store bytes that are \ - potentially not a valid "] - #[doc = $small_name] - #[doc = " (e.g., from the deposit contract)."] - #[derive(Clone)] - pub struct $name { - bytes: [u8; $byte_size], + fn tree_hash_root(&self) -> tree_hash::Hash256 { + // We could use the tree hash implementation for `FixedVec`, + // but benchmarks have show that to be at least 15% slower because of the + // unnecessary copying and allocation (one Vec per byte) + let values_per_chunk = tree_hash::BYTES_PER_CHUNK; + let minimum_chunk_count = ($byte_size + values_per_chunk - 1) / values_per_chunk; + tree_hash::merkle_root(&self.serialize(), minimum_chunk_count) } }; - ($name: ident, $type: ty, $byte_size: expr, $small_name: expr) => { - bytes_struct!($name, $type, $byte_size, $small_name, stringify!($type), - stringify!($byte_size)); - - impl $name { - pub fn from_bytes(bytes: &[u8]) -> Result { - Ok(Self { - bytes: Self::get_bytes(bytes)?, - }) - } - - pub fn empty() -> Self { - Self { - bytes: [0; $byte_size], - } - } - - pub fn as_bytes(&self) -> Vec { - self.bytes.to_vec() - } - - pub fn as_slice(&self) -> &[u8] { - &self.bytes - } +} - fn get_bytes(bytes: &[u8]) -> Result<[u8; $byte_size], ssz::DecodeError> { - let mut result = [0; $byte_size]; - if bytes.len() != $byte_size { - Err(ssz::DecodeError::InvalidByteLength { - len: bytes.len(), - expected: $byte_size, - }) - } else { - result[..].copy_from_slice(bytes); - Ok(result) - } - } +/// Contains the functions required for a `ssz::Encode` implementation. +/// +/// Does not include the `Impl` section since it gets very complicated when it comes to generics. +macro_rules! impl_ssz_encode { + ($byte_size: expr) => { + fn is_ssz_fixed_len() -> bool { + true } - impl std::fmt::Debug for $name { - fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - self.bytes[..].fmt(formatter) - } + fn ssz_fixed_len() -> usize { + $byte_size } - impl PartialEq for $name { - fn eq(&self, other: &Self) -> bool { - &self.bytes[..] == &other.bytes[..] - } + fn ssz_bytes_len(&self) -> usize { + $byte_size } - impl std::hash::Hash for $name { - fn hash(&self, state: &mut H) { - self.bytes.hash(state) - } + fn ssz_append(&self, buf: &mut Vec) { + buf.extend_from_slice(&self.serialize()) } + }; +} - impl Eq for $name {} - - impl std::convert::TryInto<$type> for &$name { - type Error = ssz::DecodeError; - - fn try_into(self) -> Result<$type, Self::Error> { - <$type>::from_bytes(&self.bytes[..]) - } +/// Contains the functions required for a `ssz::Decode` implementation. +/// +/// Does not include the `Impl` section since it gets very complicated when it comes to generics. +macro_rules! impl_ssz_decode { + ($byte_size: expr) => { + fn is_ssz_fixed_len() -> bool { + true } - impl std::convert::From<$type> for $name { - fn from(obj: $type) -> Self { - // We know that obj.as_bytes() always has exactly $byte_size many bytes. - Self::from_bytes(obj.as_ssz_bytes().as_slice()).unwrap() - } + fn ssz_fixed_len() -> usize { + $byte_size } - impl ssz::Encode for $name { - fn is_ssz_fixed_len() -> bool { - true - } + fn from_ssz_bytes(bytes: &[u8]) -> Result { + let len = bytes.len(); + let expected = ::ssz_fixed_len(); - fn ssz_fixed_len() -> usize { - $byte_size - } - - fn ssz_bytes_len(&self) -> usize { - $byte_size - } - - fn ssz_append(&self, buf: &mut Vec) { - buf.extend_from_slice(&self.bytes) + if len != expected { + Err(ssz::DecodeError::InvalidByteLength { len, expected }) + } else { + Self::deserialize(bytes) + .map_err(|e| ssz::DecodeError::BytesInvalid(format!("{:?}", e))) } } + }; +} - impl ssz::Decode for $name { - fn is_ssz_fixed_len() -> bool { - true - } - - fn ssz_fixed_len() -> usize { - $byte_size - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - let len = bytes.len(); - let expected = ::ssz_fixed_len(); - - if len != expected { - Err(ssz::DecodeError::InvalidByteLength { len, expected }) - } else { - Self::from_bytes(bytes) - } - } +/// Contains the functions required for a `serde::Serialize` implementation. +/// +/// Does not include the `Impl` section since it gets very complicated when it comes to generics. +macro_rules! impl_serde_serialize { + () => { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&hex_encode(self.serialize().to_vec())) } + }; +} - impl tree_hash::TreeHash for $name { - fn tree_hash_type() -> tree_hash::TreeHashType { - tree_hash::TreeHashType::Vector - } - - fn tree_hash_packed_encoding(&self) -> Vec { - unreachable!("Vector should never be packed.") - } - - fn tree_hash_packing_factor() -> usize { - unreachable!("Vector should never be packed.") - } - - fn tree_hash_root(&self) -> tree_hash::Hash256 { - let values_per_chunk = tree_hash::BYTES_PER_CHUNK; - let minimum_chunk_count = ($byte_size + values_per_chunk - 1) / values_per_chunk; - - let mut hasher = tree_hash::MerkleHasher::with_leaves(minimum_chunk_count); - hasher.write(&self.bytes).expect("bls should not exceed leaf count"); - hasher.finish().expect("bls should not exceed leaf count from buffer") - } +/// Contains the functions required for a `serde::Deserialize` implementation. +/// +/// Does not include the `Impl` section since it gets very complicated when it comes to generics. +macro_rules! impl_serde_deserialize { + () => { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; + Self::deserialize(&bytes[..]) + .map_err(|e| serde::de::Error::custom(format!("invalid pubkey ({:?})", e))) } + }; +} - impl serde::ser::Serialize for $name { - /// Serde serialization is compliant the Ethereum YAML test format. - fn serialize(&self, serializer: S) -> Result - where - S: serde::ser::Serializer, - { - serializer.serialize_str(&serde_hex::encode(ssz::ssz_encode(self))) - } +/// Contains the functions required for a `Debug` implementation. +/// +/// Does not include the `Impl` section since it gets very complicated when it comes to generics. +macro_rules! impl_debug { + () => { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", hex_encode(&self.serialize().to_vec())) } + }; +} - impl<'de> serde::de::Deserialize<'de> for $name { - /// Serde serialization is compliant the Ethereum YAML test format. - fn deserialize(deserializer: D) -> Result - where - D: serde::de::Deserializer<'de>, - { - let bytes = deserializer.deserialize_str(serde_hex::PrefixedHexVisitor)?; - let signature = Self::from_ssz_bytes(&bytes[..]) - .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; - Ok(signature) - } +/// Contains the functions required for an `Arbitrary` implementation. +/// +/// Does not include the `Impl` section since it gets very complicated when it comes to generics. +#[cfg(feature = "arbitrary")] +macro_rules! impl_arbitrary { + ($byte_size: expr) => { + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + let mut bytes = [0u8; $byte_size]; + u.fill_buffer(&mut bytes)?; + Self::deserialize(&bytes).map_err(|_| arbitrary::Error::IncorrectFormat) } - - #[cfg(feature = "arbitrary")] - impl $crate::arbitrary::Arbitrary for $name { - fn arbitrary(u: &mut $crate::arbitrary::Unstructured<'_>) -> $crate::arbitrary::Result { - let mut bytes = [0u8; $byte_size]; - u.fill_buffer(&mut bytes)?; - Self::from_bytes(&bytes).map_err(|_| $crate::arbitrary::Error::IncorrectFormat) - } - } }; } diff --git a/crypto/bls/src/public_key.rs b/crypto/bls/src/public_key.rs deleted file mode 100644 index c9b454c37f..0000000000 --- a/crypto/bls/src/public_key.rs +++ /dev/null @@ -1,169 +0,0 @@ -use super::{SecretKey, BLS_PUBLIC_KEY_BYTE_SIZE}; -use milagro_bls::PublicKey as RawPublicKey; -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; -use ssz::{Decode, DecodeError, Encode}; -use std::default; -use std::fmt; -use std::hash::{Hash, Hasher}; - -/// A single BLS signature. -/// -/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ -/// serialization). -#[derive(Clone, Eq)] -pub struct PublicKey(RawPublicKey); - -impl PublicKey { - pub fn from_secret_key(secret_key: &SecretKey) -> Self { - PublicKey(RawPublicKey::from_secret_key(secret_key.as_raw())) - } - - pub fn from_raw(raw: RawPublicKey) -> Self { - Self(raw) - } - - /// Returns a reference to the underlying signature. - pub fn as_raw(&self) -> &RawPublicKey { - &self.0 - } - - /// Returns the underlying point as compressed bytes. - pub fn as_bytes(&self) -> [u8; BLS_PUBLIC_KEY_BYTE_SIZE] { - self.as_raw().as_bytes() - } - - /// Converts compressed bytes to PublicKey - pub fn from_bytes(bytes: &[u8]) -> Result { - let pubkey = RawPublicKey::from_bytes(&bytes).map_err(|_| { - DecodeError::BytesInvalid(format!("Invalid PublicKey bytes: {:?}", bytes)) - })?; - - Ok(PublicKey(pubkey)) - } - - /// Returns the PublicKey as (x, y) bytes - pub fn as_uncompressed_bytes(&self) -> [u8; BLS_PUBLIC_KEY_BYTE_SIZE * 2] { - RawPublicKey::as_uncompressed_bytes(&mut self.0.clone()) - } - - /// Converts (x, y) bytes to PublicKey - pub fn from_uncompressed_bytes(bytes: &[u8]) -> Result { - let pubkey = RawPublicKey::from_uncompressed_bytes(&bytes).map_err(|_| { - DecodeError::BytesInvalid("Invalid PublicKey uncompressed bytes.".to_string()) - })?; - Ok(PublicKey(pubkey)) - } - - /// Returns the last 6 bytes of the SSZ encoding of the public key, as a hex string. - /// - /// Useful for providing a short identifier to the user. - pub fn concatenated_hex_id(&self) -> String { - self.as_hex_string()[0..6].to_string() - } - - /// Returns the point as a hex string of the SSZ encoding. - /// - /// Note: the string is prefixed with `0x`. - pub fn as_hex_string(&self) -> String { - hex_encode(self.as_ssz_bytes()) - } -} - -impl fmt::Display for PublicKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.concatenated_hex_id()) - } -} - -impl fmt::Debug for PublicKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_hex_string()) - } -} - -impl default::Default for PublicKey { - fn default() -> Self { - let secret_key = SecretKey::random(); - PublicKey::from_secret_key(&secret_key) - } -} - -impl_ssz!(PublicKey, BLS_PUBLIC_KEY_BYTE_SIZE, "PublicKey"); - -impl_tree_hash!(PublicKey, BLS_PUBLIC_KEY_BYTE_SIZE); - -impl Serialize for PublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&hex_encode(self.as_ssz_bytes())) - } -} - -impl<'de> Deserialize<'de> for PublicKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; - let pubkey = Self::from_ssz_bytes(&bytes[..]) - .map_err(|e| serde::de::Error::custom(format!("invalid pubkey ({:?})", e)))?; - Ok(pubkey) - } -} - -impl PartialEq for PublicKey { - fn eq(&self, other: &PublicKey) -> bool { - self.as_ssz_bytes() == other.as_ssz_bytes() - } -} - -impl Hash for PublicKey { - /// Note: this is distinct from consensus serialization, it will produce a different hash. - /// - /// This method uses the uncompressed bytes, which are much faster to obtain than the - /// compressed bytes required for consensus serialization. - /// - /// Use `ssz::Encode` to obtain the bytes required for consensus hashing. - fn hash(&self, state: &mut H) { - self.as_uncompressed_bytes().hash(state) - } -} - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary for PublicKey { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; BLS_PUBLIC_KEY_BYTE_SIZE]; - u.fill_buffer(&mut bytes)?; - Self::from_bytes(&bytes).map_err(|_| arbitrary::Error::IncorrectFormat) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ssz::ssz_encode; - - #[test] - pub fn test_ssz_round_trip() { - let sk = SecretKey::random(); - let original = PublicKey::from_secret_key(&sk); - - let bytes = ssz_encode(&original); - let decoded = PublicKey::from_ssz_bytes(&bytes).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_byte_size() { - let sk = SecretKey::random(); - let original = PublicKey::from_secret_key(&sk); - - let bytes = ssz_encode(&original); - assert_eq!(bytes.len(), BLS_PUBLIC_KEY_BYTE_SIZE); - } -} diff --git a/crypto/bls/src/public_key_bytes.rs b/crypto/bls/src/public_key_bytes.rs deleted file mode 100644 index 528ef82544..0000000000 --- a/crypto/bls/src/public_key_bytes.rs +++ /dev/null @@ -1,43 +0,0 @@ -use ssz::{Decode, DecodeError, Encode}; - -use super::{PublicKey, BLS_PUBLIC_KEY_BYTE_SIZE}; - -bytes_struct!( - PublicKeyBytes, - PublicKey, - BLS_PUBLIC_KEY_BYTE_SIZE, - "public key" -); - -#[cfg(test)] -mod tests { - use std::convert::TryInto; - - use ssz::ssz_encode; - - use super::super::Keypair; - use super::*; - - #[test] - pub fn test_valid_public_key() { - let keypair = Keypair::random(); - - let bytes = ssz_encode(&keypair.pk); - let public_key_bytes = PublicKeyBytes::from_bytes(&bytes).unwrap(); - let public_key: Result = (&public_key_bytes).try_into(); - assert!(public_key.is_ok()); - assert_eq!(keypair.pk, public_key.unwrap()); - } - - #[test] - #[cfg(not(feature = "fake_crypto"))] - pub fn test_invalid_public_key() { - let mut public_key_bytes = [0; BLS_PUBLIC_KEY_BYTE_SIZE]; - public_key_bytes[0] = 255; //a_flag1 == b_flag1 == c_flag1 == 1 and x1 = 0 shouldn't be allowed - let public_key_bytes = PublicKeyBytes::from_bytes(&public_key_bytes[..]); - assert!(public_key_bytes.is_ok()); - - let public_key: Result = public_key_bytes.as_ref().unwrap().try_into(); - assert!(public_key.is_err()); - } -} diff --git a/crypto/bls/src/secret_key.rs b/crypto/bls/src/secret_key.rs deleted file mode 100644 index 3d03805ea1..0000000000 --- a/crypto/bls/src/secret_key.rs +++ /dev/null @@ -1,68 +0,0 @@ -extern crate rand; - -use crate::SecretHash; -use milagro_bls::SecretKey as RawSecretKey; -use ssz::DecodeError; - -/// A single BLS signature. -/// -/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ -/// serialization). -#[derive(Clone)] -pub struct SecretKey(RawSecretKey); - -impl SecretKey { - /// Generate a new `Self` using `rand::thread_rng`. - pub fn random() -> Self { - SecretKey(RawSecretKey::random(&mut rand::thread_rng())) - } - - pub fn from_raw(raw: RawSecretKey) -> Self { - Self(raw) - } - - /// Returns the secret key as a byte array (wrapped in `SecretHash` wrapper so it is zeroized on - /// `Drop`). - /// - /// Extreme care should be taken not to leak these bytes as they are the unencrypted secret - /// key. - pub fn as_bytes(&self) -> SecretHash { - self.as_raw().as_bytes().into() - } - - /// Instantiate a SecretKey from existing bytes. - /// - /// Note: this is _not_ SSZ decoding. - pub fn from_bytes(bytes: &[u8]) -> Result { - Ok(SecretKey(RawSecretKey::from_bytes(bytes).map_err(|e| { - DecodeError::BytesInvalid(format!( - "Invalid SecretKey bytes: {:?} Error: {:?}", - bytes, e - )) - })?)) - } - - /// Returns the underlying secret key. - pub(crate) fn as_raw(&self) -> &RawSecretKey { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - pub fn test_ssz_round_trip() { - let byte_key = [ - 3, 211, 210, 129, 231, 69, 162, 234, 16, 15, 244, 214, 126, 201, 0, 85, 28, 239, 82, - 121, 208, 190, 223, 6, 169, 202, 86, 236, 197, 218, 3, 69, - ]; - let original = SecretKey::from_bytes(&byte_key).unwrap(); - - let bytes = original.as_bytes(); - let decoded = SecretKey::from_bytes(bytes.as_ref()).unwrap(); - - assert!(original.as_bytes().as_ref().to_vec() == decoded.as_bytes().as_ref().to_vec()); - } -} diff --git a/crypto/bls/src/signature.rs b/crypto/bls/src/signature.rs deleted file mode 100644 index 5a2bea773a..0000000000 --- a/crypto/bls/src/signature.rs +++ /dev/null @@ -1,174 +0,0 @@ -use super::{PublicKey, SecretKey, BLS_SIG_BYTE_SIZE}; -use milagro_bls::Signature as RawSignature; -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; -use ssz::{ssz_encode, Decode, DecodeError, Encode}; - -/// A single BLS signature. -/// -/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ -/// serialization). -#[derive(Debug, PartialEq, Clone, Eq)] -pub struct Signature { - signature: RawSignature, - is_empty: bool, -} - -impl Signature { - /// Instantiate a new Signature from a message and a SecretKey. - pub fn new(msg: &[u8], sk: &SecretKey) -> Self { - Signature { - signature: RawSignature::new(msg, sk.as_raw()), - is_empty: false, - } - } - - /// Verify the Signature against a PublicKey. - pub fn verify(&self, msg: &[u8], pk: &PublicKey) -> bool { - if self.is_empty { - return false; - } - self.signature.verify(msg, pk.as_raw()) - } - - /// Returns the underlying signature. - pub fn as_raw(&self) -> &RawSignature { - &self.signature - } - - /// Returns a new empty signature. - pub fn empty_signature() -> Self { - // Set RawSignature = infinity - let mut empty = [0u8; BLS_SIG_BYTE_SIZE]; - empty[0] += u8::pow(2, 6) + u8::pow(2, 7); - Signature { - signature: RawSignature::from_bytes(&empty).unwrap(), - is_empty: true, - } - } - - // Converts a BLS Signature to bytes - pub fn as_bytes(&self) -> [u8; BLS_SIG_BYTE_SIZE] { - if self.is_empty { - return [0u8; BLS_SIG_BYTE_SIZE]; - } - self.signature.as_bytes() - } - - // Convert bytes to BLS Signature - pub fn from_bytes(bytes: &[u8]) -> Result { - for byte in bytes { - if *byte != 0 { - let raw_signature = RawSignature::from_bytes(&bytes).map_err(|_| { - DecodeError::BytesInvalid(format!("Invalid Signature bytes: {:?}", bytes)) - })?; - return Ok(Signature { - signature: raw_signature, - is_empty: false, - }); - } - } - Ok(Signature::empty_signature()) - } - - // Check for empty Signature - pub fn is_empty(&self) -> bool { - self.is_empty - } - - /// Display a signature as a hex string of its bytes. - #[cfg(test)] - pub fn as_hex_string(&self) -> String { - hex_encode(self.as_ssz_bytes()) - } -} - -impl_ssz!(Signature, BLS_SIG_BYTE_SIZE, "Signature"); - -impl_tree_hash!(Signature, BLS_SIG_BYTE_SIZE); - -impl Serialize for Signature { - /// Serde serialization is compliant the Ethereum YAML test format. - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&hex_encode(ssz_encode(self))) - } -} - -impl<'de> Deserialize<'de> for Signature { - /// Serde serialization is compliant the Ethereum YAML test format. - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; - let signature = Self::from_ssz_bytes(&bytes[..]) - .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; - Ok(signature) - } -} - -#[cfg(feature = "arbitrary")] -impl arbitrary::Arbitrary for Signature { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let mut bytes = [0u8; BLS_SIG_BYTE_SIZE]; - u.fill_buffer(&mut bytes)?; - Self::from_bytes(&bytes).map_err(|_| arbitrary::Error::IncorrectFormat) - } -} - -#[cfg(test)] -mod tests { - use super::super::Keypair; - use super::*; - use ssz::ssz_encode; - - #[test] - pub fn test_ssz_round_trip() { - let keypair = Keypair::random(); - - let original = Signature::new(&[42, 42], &keypair.sk); - - let bytes = ssz_encode(&original); - let decoded = Signature::from_ssz_bytes(&bytes).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_byte_size() { - let keypair = Keypair::random(); - - let signature = Signature::new(&[42, 42], &keypair.sk); - let bytes = ssz_encode(&signature); - assert_eq!(bytes.len(), BLS_SIG_BYTE_SIZE); - } - - #[test] - pub fn test_infinity_signature() { - let sig = Signature::empty_signature(); - - let sig_as_bytes = sig.as_raw().as_bytes(); - - assert_eq!(sig_as_bytes.len(), BLS_SIG_BYTE_SIZE); - for (i, one_byte) in sig_as_bytes.iter().enumerate() { - if i == 0 { - assert_eq!(*one_byte, u8::pow(2, 6) + u8::pow(2, 7)); - } else { - assert_eq!(*one_byte, 0); - } - } - } - - #[test] - pub fn test_empty_signature() { - let sig = Signature::empty_signature(); - - let sig_as_bytes = sig.as_bytes().to_vec(); - - assert_eq!(sig_as_bytes, vec![0u8; BLS_SIG_BYTE_SIZE]); - } -} diff --git a/crypto/bls/src/signature_bytes.rs b/crypto/bls/src/signature_bytes.rs deleted file mode 100644 index 56e766af79..0000000000 --- a/crypto/bls/src/signature_bytes.rs +++ /dev/null @@ -1,39 +0,0 @@ -use ssz::{Decode, DecodeError, Encode}; - -use super::{Signature, BLS_SIG_BYTE_SIZE}; - -bytes_struct!(SignatureBytes, Signature, BLS_SIG_BYTE_SIZE, "signature"); - -#[cfg(test)] -mod tests { - use std::convert::TryInto; - - use ssz::ssz_encode; - - use super::super::Keypair; - use super::*; - - #[test] - pub fn test_valid_signature() { - let keypair = Keypair::random(); - let original = Signature::new(&[42, 42], &keypair.sk); - - let bytes = ssz_encode(&original); - let signature_bytes = SignatureBytes::from_bytes(&bytes).unwrap(); - let signature: Result = (&signature_bytes).try_into(); - assert!(signature.is_ok()); - assert_eq!(original, signature.unwrap()); - } - - #[test] - #[cfg(not(feature = "fake_crypto"))] - pub fn test_invalid_signature() { - let mut signature_bytes = [0; BLS_SIG_BYTE_SIZE]; - signature_bytes[0] = 255; //a_flag1 == b_flag1 == c_flag1 == 1 and x1 = 0 shouldn't be allowed - let signature_bytes = SignatureBytes::from_bytes(&signature_bytes[..]); - assert!(signature_bytes.is_ok()); - - let signature: Result = signature_bytes.as_ref().unwrap().try_into(); - assert!(signature.is_err()); - } -} diff --git a/crypto/bls/src/signature_set.rs b/crypto/bls/src/signature_set.rs deleted file mode 100644 index c391ea45bb..0000000000 --- a/crypto/bls/src/signature_set.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::{AggregateSignature, PublicKey, Signature}; -use std::borrow::Cow; - -#[cfg(not(feature = "fake_crypto"))] -use milagro_bls::{ - AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature, - PublicKey as RawPublicKey, -}; - -#[cfg(feature = "fake_crypto")] -use crate::fakes::{ - AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature, - PublicKey as RawPublicKey, -}; - -type Message = Vec; - -#[derive(Clone, Debug)] -pub struct SignatureSet { - pub signature: RawAggregateSignature, - signing_keys: RawAggregatePublicKey, - message: Message, -} - -impl SignatureSet { - pub fn single(signature: &Signature, signing_key: Cow, message: Message) -> Self { - Self { - signature: RawAggregateSignature::from_signature(signature.as_raw()), - signing_keys: RawAggregatePublicKey::from_public_key(signing_key.as_raw()), - message, - } - } - - pub fn new( - signature: &AggregateSignature, - signing_keys: Vec>, - message: Message, - ) -> Self -where { - let signing_keys_refs: Vec<&RawPublicKey> = - signing_keys.iter().map(|pk| pk.as_raw()).collect(); - Self { - signature: signature.as_raw().clone(), - signing_keys: RawAggregatePublicKey::aggregate(&signing_keys_refs), - message, - } - } - - pub fn is_valid(&self) -> bool { - self.signature - .fast_aggregate_verify_pre_aggregated(&self.message, &self.signing_keys) - } -} - -#[cfg(not(feature = "fake_crypto"))] -type VerifySet<'a> = ( - &'a RawAggregateSignature, - &'a RawAggregatePublicKey, - &'a [u8], -); - -#[cfg(not(feature = "fake_crypto"))] -pub fn verify_signature_sets(sets: Vec) -> bool { - let rng = &mut rand::thread_rng(); - let verify_set: Vec = sets - .iter() - .map(|ss| (&ss.signature, &ss.signing_keys, ss.message.as_slice())) - .collect(); - RawAggregateSignature::verify_multiple_aggregate_signatures(rng, verify_set.into_iter()) -} - -#[cfg(feature = "fake_crypto")] -pub fn verify_signature_sets<'a>(_: Vec) -> bool { - true -} diff --git a/crypto/bls/src/secret_hash.rs b/crypto/bls/src/zeroize_hash.rs similarity index 54% rename from crypto/bls/src/secret_hash.rs rename to crypto/bls/src/zeroize_hash.rs index c1d92d00db..3d81df1d81 100644 --- a/crypto/bls/src/secret_hash.rs +++ b/crypto/bls/src/zeroize_hash.rs @@ -1,15 +1,15 @@ -use super::BLS_SECRET_KEY_BYTE_SIZE; +use super::SECRET_KEY_BYTES_LEN; use zeroize::Zeroize; -/// Provides a wrapper around a `[u8; HASH_SIZE]` that implements `Zeroize` on `Drop`. +/// Provides a wrapper around a `[u8; SECRET_KEY_BYTES_LEN]` that implements `Zeroize` on `Drop`. #[derive(Zeroize)] #[zeroize(drop)] -pub struct SecretHash([u8; BLS_SECRET_KEY_BYTE_SIZE]); +pub struct ZeroizeHash([u8; SECRET_KEY_BYTES_LEN]); -impl SecretHash { +impl ZeroizeHash { /// Instantiates `Self` with all zeros. pub fn zero() -> Self { - Self([0; BLS_SECRET_KEY_BYTE_SIZE]) + Self([0; SECRET_KEY_BYTES_LEN]) } /// Returns a reference to the underlying bytes. @@ -23,13 +23,13 @@ impl SecretHash { } } -impl From<[u8; BLS_SECRET_KEY_BYTE_SIZE]> for SecretHash { - fn from(array: [u8; BLS_SECRET_KEY_BYTE_SIZE]) -> Self { +impl From<[u8; SECRET_KEY_BYTES_LEN]> for ZeroizeHash { + fn from(array: [u8; SECRET_KEY_BYTES_LEN]) -> Self { Self(array) } } -impl AsRef<[u8]> for SecretHash { +impl AsRef<[u8]> for ZeroizeHash { fn as_ref(&self) -> &[u8] { &self.0 } diff --git a/crypto/bls/tests/tests.rs b/crypto/bls/tests/tests.rs new file mode 100644 index 0000000000..6767837187 --- /dev/null +++ b/crypto/bls/tests/tests.rs @@ -0,0 +1,614 @@ +use bls::{Hash256, INFINITY_PUBLIC_KEY, INFINITY_SIGNATURE}; +use ssz::{Decode, Encode}; +use std::borrow::Cow; +use std::fmt::Debug; + +fn ssz_round_trip(item: T) { + assert_eq!(item, T::from_ssz_bytes(&item.as_ssz_bytes()).unwrap()); +} + +macro_rules! test_suite { + ($impls: ident) => { + use super::*; + use bls::$impls::*; + + fn secret_from_u64(i: u64) -> SecretKey { + let mut secret_bytes = [0; 32]; + // Use i + 1 to avoid the all-zeros secret key. + secret_bytes[32 - 8..].copy_from_slice(&(i + 1).to_be_bytes()); + SecretKey::deserialize(&secret_bytes).unwrap() + } + + #[test] + fn infinity_agg_sig() { + assert_eq!( + &AggregateSignature::infinity().serialize()[..], + &INFINITY_SIGNATURE[..] + ); + assert_eq!( + AggregateSignature::deserialize(&INFINITY_SIGNATURE).unwrap(), + AggregateSignature::infinity(), + ); + } + + #[test] + fn ssz_round_trip_multiple_types() { + let mut agg_sig = AggregateSignature::infinity(); + ssz_round_trip(agg_sig.clone()); + + let msg = Hash256::from_low_u64_be(42); + let secret = secret_from_u64(42); + + let sig = secret.sign(msg); + ssz_round_trip(sig.clone()); + + agg_sig.add_assign(&sig); + ssz_round_trip(agg_sig); + } + + #[test] + fn ssz_round_trip_sig_empty() { + ssz_round_trip(Signature::empty()) + } + + #[test] + fn ssz_round_trip_agg_sig_empty() { + ssz_round_trip(AggregateSignature::empty()) + } + + #[test] + fn ssz_round_trip_agg_sig_infinity() { + ssz_round_trip(AggregateSignature::infinity()) + } + + #[test] + fn partial_eq_empty_sig() { + assert_eq!(Signature::empty(), Signature::empty()) + } + + #[test] + fn partial_eq_empty_sig_and_non_empty_sig() { + assert!(Signature::empty() != SignatureTester::default().sig) + } + + #[test] + fn partial_eq_empty_agg_sig() { + assert_eq!(AggregateSignature::empty(), AggregateSignature::empty()) + } + + #[test] + fn partial_eq_empty_agg_sig_and_real_agg_sig() { + assert!( + AggregateSignature::empty() != AggregateSignatureTester::new_with_single_msg(1).sig + ) + } + + #[test] + fn partial_eq_infinity_agg_sig() { + assert_eq!( + AggregateSignature::infinity(), + AggregateSignature::infinity() + ) + } + + #[test] + fn partial_eq_infinity_agg_sig_and_real_agg_sig() { + assert!( + AggregateSignature::infinity() + != AggregateSignatureTester::new_with_single_msg(1).sig + ) + } + + #[test] + fn partial_eq_infinity_agg_sig_and_empty_agg_sig() { + assert!(AggregateSignature::infinity() != AggregateSignature::empty()) + } + + /// A helper struct for composing tests via the builder pattern. + struct SignatureTester { + sig: Signature, + pubkey: PublicKey, + msg: Hash256, + } + + impl Default for SignatureTester { + fn default() -> Self { + let secret = SecretKey::deserialize(&[42; 32]).unwrap(); + let pubkey = secret.public_key(); + let msg = Hash256::from_low_u64_be(42); + + Self { + sig: secret.sign(msg), + pubkey, + msg, + } + } + } + + impl SignatureTester { + pub fn infinity_sig(mut self) -> Self { + self.sig = Signature::deserialize(&INFINITY_SIGNATURE[..]).unwrap(); + self + } + + pub fn infinity_pubkey(mut self) -> Self { + self.pubkey = PublicKey::deserialize(&INFINITY_PUBLIC_KEY[..]).unwrap(); + self + } + + pub fn assert_verify(self, is_valid: bool) { + assert_eq!(self.sig.verify(&self.pubkey, self.msg), is_valid); + + // Check a single-signature signature set. + assert_eq!( + SignatureSet::single_pubkey(&self.sig, Cow::Borrowed(&self.pubkey), self.msg,) + .verify(), + is_valid + ) + } + } + + #[test] + fn standard_signature_is_valid_with_standard_pubkey() { + SignatureTester::default().assert_verify(true) + } + + #[test] + fn infinity_signature_is_valid_with_infinity_pubkey() { + SignatureTester::default() + .infinity_sig() + .infinity_pubkey() + .assert_verify(true) + } + + #[test] + fn infinity_signature_is_invalid_with_standard_pubkey() { + SignatureTester::default() + .infinity_sig() + .assert_verify(false) + } + + #[test] + fn standard_signature_is_invalid_with_infinity_pubkey() { + SignatureTester::default() + .infinity_pubkey() + .assert_verify(false) + } + + /// A helper struct for composing tests via the builder pattern. + struct AggregateSignatureTester { + sig: AggregateSignature, + pubkeys: Vec, + msgs: Vec, + } + + impl AggregateSignatureTester { + fn new_with_single_msg(num_pubkeys: u64) -> Self { + let mut pubkeys = Vec::with_capacity(num_pubkeys as usize); + let mut sig = AggregateSignature::infinity(); + let msg = Hash256::from_low_u64_be(42); + + for i in 0..num_pubkeys { + let secret = secret_from_u64(i); + pubkeys.push(secret.public_key()); + sig.add_assign(&secret.sign(msg)); + } + + Self { + sig, + pubkeys, + msgs: vec![msg], + } + } + + pub fn empty_sig(mut self) -> Self { + self.sig = AggregateSignature::empty(); + self + } + + pub fn wrong_sig(mut self) -> Self { + let sk = SecretKey::deserialize(&[1; 32]).unwrap(); + self.sig = AggregateSignature::infinity(); + self.sig.add_assign(&sk.sign(Hash256::from_low_u64_be(1))); + self + } + + pub fn infinity_sig(mut self) -> Self { + self.sig = AggregateSignature::deserialize(&INFINITY_SIGNATURE[..]).unwrap(); + self + } + + pub fn aggregate_empty_sig(mut self) -> Self { + self.sig.add_assign(&Signature::empty()); + self + } + + pub fn aggregate_empty_agg_sig(mut self) -> Self { + self.sig.add_assign_aggregate(&AggregateSignature::empty()); + self + } + + pub fn aggregate_infinity_sig(mut self) -> Self { + self.sig + .add_assign(&Signature::deserialize(&INFINITY_SIGNATURE[..]).unwrap()); + self + } + + pub fn single_infinity_pubkey(mut self) -> Self { + self.pubkeys = vec![PublicKey::deserialize(&INFINITY_PUBLIC_KEY[..]).unwrap()]; + self + } + + pub fn push_infinity_pubkey(mut self) -> Self { + self.pubkeys + .push(PublicKey::deserialize(&INFINITY_PUBLIC_KEY[..]).unwrap()); + self + } + + pub fn assert_single_message_verify(self, is_valid: bool) { + assert!(self.msgs.len() == 1); + let msg = self.msgs.first().unwrap(); + let pubkeys = self.pubkeys.iter().collect::>(); + + assert_eq!( + self.sig.fast_aggregate_verify(*msg, &pubkeys), + is_valid, + "fast_aggregate_verify expected {} but got {}", + is_valid, + !is_valid + ); + + let msgs = pubkeys.iter().map(|_| msg.clone()).collect::>(); + + assert_eq!( + self.sig.aggregate_verify(&msgs, &pubkeys), + is_valid, + "aggregate_verify expected {} but got {}", + is_valid, + !is_valid + ); + } + } + + /// An aggregate without any signatures should not verify. + #[test] + fn fast_aggregate_verify_0_pubkeys() { + AggregateSignatureTester::new_with_single_msg(0).assert_single_message_verify(false) + } + + /// An aggregate of size 1 should verify. + #[test] + fn fast_aggregate_verify_1_pubkey() { + AggregateSignatureTester::new_with_single_msg(1).assert_single_message_verify(true) + } + + /// An aggregate of size 128 should verify. + #[test] + fn fast_aggregate_verify_128_pubkeys() { + AggregateSignatureTester::new_with_single_msg(128).assert_single_message_verify(true) + } + + /// The infinity signature should not verify against 1 non-infinity pubkey. + #[test] + fn fast_aggregate_verify_infinity_signature_with_1_regular_public_key() { + AggregateSignatureTester::new_with_single_msg(1) + .infinity_sig() + .assert_single_message_verify(false) + } + + /// The infinity signature should not verify against 128 non-infinity pubkeys. + #[test] + fn fast_aggregate_verify_infinity_signature_with_128_regular_public_keys() { + AggregateSignatureTester::new_with_single_msg(128) + .infinity_sig() + .assert_single_message_verify(false) + } + + /// The infinity signature and one infinity pubkey should verify. + #[test] + fn fast_aggregate_verify_infinity_signature_with_one_infinity_pubkey() { + AggregateSignatureTester::new_with_single_msg(1) + .infinity_sig() + .single_infinity_pubkey() + .assert_single_message_verify(true) + } + + /// Adding a infinity signature (without an infinity pubkey) should verify. + #[test] + fn fast_aggregate_verify_with_one_aggregated_infinity_sig() { + AggregateSignatureTester::new_with_single_msg(1) + .aggregate_infinity_sig() + .assert_single_message_verify(true) + } + + /// Adding four infinity signatures (without any infinity pubkeys) should verify. + #[test] + fn fast_aggregate_verify_with_four_aggregated_infinity_sig() { + AggregateSignatureTester::new_with_single_msg(1) + .aggregate_infinity_sig() + .aggregate_infinity_sig() + .aggregate_infinity_sig() + .aggregate_infinity_sig() + .assert_single_message_verify(true) + } + + /// Adding a infinity pubkey and an infinity signature should verify. + #[test] + fn fast_aggregate_verify_with_one_additional_infinity_pubkey_and_matching_sig() { + AggregateSignatureTester::new_with_single_msg(1) + .aggregate_infinity_sig() + .push_infinity_pubkey() + .assert_single_message_verify(true) + } + + /// Adding a single infinity pubkey **without** updating the signature **should verify**. + #[test] + fn fast_aggregate_verify_with_one_additional_infinity_pubkey() { + AggregateSignatureTester::new_with_single_msg(1) + .push_infinity_pubkey() + .assert_single_message_verify(true) + } + + /// Adding multiple infinity pubkeys **without** updating the signature **should verify**. + #[test] + fn fast_aggregate_verify_with_four_additional_infinity_pubkeys() { + AggregateSignatureTester::new_with_single_msg(1) + .push_infinity_pubkey() + .push_infinity_pubkey() + .push_infinity_pubkey() + .push_infinity_pubkey() + .assert_single_message_verify(true) + } + + /// The wrong signature should not verify. + #[test] + fn fast_aggregate_verify_wrong_signature() { + AggregateSignatureTester::new_with_single_msg(1) + .wrong_sig() + .assert_single_message_verify(false) + } + + /// An "empty" signature should not verify. + #[test] + fn fast_aggregate_verify_empty_signature() { + AggregateSignatureTester::new_with_single_msg(1) + .empty_sig() + .assert_single_message_verify(false) + } + + /// Aggregating an "empty" signature should have no effect. + #[test] + fn fast_aggregate_verify_with_aggregated_empty_sig() { + AggregateSignatureTester::new_with_single_msg(1) + .aggregate_empty_sig() + .assert_single_message_verify(true) + } + + /// Aggregating an "empty" aggregate signature should have no effect. + #[test] + fn fast_aggregate_verify_with_aggregated_empty_agg_sig() { + AggregateSignatureTester::new_with_single_msg(1) + .aggregate_empty_agg_sig() + .assert_single_message_verify(true) + } + + /// A helper struct to make it easer to deal with `SignatureSet` lifetimes. + struct OwnedSignatureSet { + signature: AggregateSignature, + signing_keys: Vec, + message: Hash256, + should_be_valid: bool, + } + + impl OwnedSignatureSet { + pub fn multiple_pubkeys(&self) -> SignatureSet { + let signing_keys = self.signing_keys.iter().map(Cow::Borrowed).collect(); + SignatureSet::multiple_pubkeys(&self.signature, signing_keys, self.message) + } + + pub fn run_checks(&self) { + assert_eq!( + self.multiple_pubkeys().verify(), + self.should_be_valid, + "multiple pubkey expected {} but got {}", + self.should_be_valid, + !self.should_be_valid + ) + } + } + + /// A helper struct for composing tests via the builder pattern. + #[derive(Default)] + struct SignatureSetTester { + owned_sets: Vec, + } + + impl SignatureSetTester { + pub fn push_valid_set(mut self, num_signers: usize) -> Self { + let mut signature = AggregateSignature::infinity(); + let message = Hash256::from_low_u64_be(42); + + let signing_keys = (0..num_signers) + .map(|i| { + let secret = secret_from_u64(i as u64); + signature.add_assign(&secret.sign(message)); + + secret.public_key() + }) + .collect(); + + self.owned_sets.push(OwnedSignatureSet { + signature, + signing_keys, + message, + should_be_valid: true, + }); + + self + } + + pub fn push_invalid_set(mut self) -> Self { + let mut signature = AggregateSignature::infinity(); + let message = Hash256::from_low_u64_be(42); + + signature.add_assign(&secret_from_u64(0).sign(message)); + + self.owned_sets.push(OwnedSignatureSet { + signature, + signing_keys: vec![secret_from_u64(42).public_key()], + message, + should_be_valid: false, + }); + + self + } + + pub fn push_invalid_sig_infinity_set(mut self) -> Self { + let mut signature = AggregateSignature::infinity(); + signature.add_assign(&secret_from_u64(42).sign(Hash256::zero())); + self.owned_sets.push(OwnedSignatureSet { + signature, + signing_keys: vec![PublicKey::deserialize(&INFINITY_PUBLIC_KEY).unwrap()], + message: Hash256::zero(), + should_be_valid: false, + }); + self + } + + pub fn push_invalid_pubkey_infinity_set(mut self) -> Self { + self.owned_sets.push(OwnedSignatureSet { + signature: AggregateSignature::deserialize(&INFINITY_SIGNATURE).unwrap(), + signing_keys: vec![secret_from_u64(42).public_key()], + message: Hash256::zero(), + should_be_valid: false, + }); + self + } + + pub fn push_valid_infinity_set(mut self) -> Self { + self.owned_sets.push(OwnedSignatureSet { + signature: AggregateSignature::deserialize(&INFINITY_SIGNATURE).unwrap(), + signing_keys: vec![PublicKey::deserialize(&INFINITY_PUBLIC_KEY).unwrap()], + message: Hash256::zero(), + should_be_valid: true, + }); + self + } + + pub fn run_checks(self) { + assert!(!self.owned_sets.is_empty(), "empty test is meaningless"); + + for owned_set in &self.owned_sets { + owned_set.run_checks() + } + + let should_be_valid = self + .owned_sets + .iter() + .all(|owned_set| owned_set.should_be_valid); + + let signature_sets = self + .owned_sets + .iter() + .map(|owned_set| owned_set.multiple_pubkeys()) + .collect::>(); + + assert_eq!( + verify_signature_sets(signature_sets.iter()), + should_be_valid + ); + } + } + + #[test] + fn signature_set_1_valid_set_with_1_signer() { + SignatureSetTester::default().push_valid_set(1).run_checks() + } + + #[test] + fn signature_set_1_invalid_set() { + SignatureSetTester::default() + .push_invalid_set() + .run_checks() + } + + #[test] + fn signature_set_1_valid_set_with_2_signers() { + SignatureSetTester::default().push_valid_set(2).run_checks() + } + + #[test] + fn signature_set_1_valid_set_with_128_signers() { + SignatureSetTester::default() + .push_valid_set(128) + .run_checks() + } + + #[test] + fn signature_set_2_valid_set_with_one_signer_each() { + SignatureSetTester::default() + .push_valid_set(1) + .push_valid_set(1) + .run_checks() + } + + #[test] + fn signature_set_2_valid_set_with_2_signers_each() { + SignatureSetTester::default() + .push_valid_set(2) + .push_valid_set(2) + .run_checks() + } + + #[test] + fn signature_set_2_valid_set_with_1_invalid_set() { + SignatureSetTester::default() + .push_valid_set(2) + .push_invalid_set() + .run_checks() + } + + #[test] + fn signature_set_1_valid_set_with_1_infinity_set() { + SignatureSetTester::default() + .push_valid_infinity_set() + .run_checks() + } + + #[test] + fn signature_set_3_sets_with_one_valid_infinity_set() { + SignatureSetTester::default() + .push_valid_set(2) + .push_valid_infinity_set() + .push_valid_set(2) + .run_checks() + } + + #[test] + fn signature_set_3_sets_with_one_invalid_pubkey_infinity_set() { + SignatureSetTester::default() + .push_valid_set(2) + .push_invalid_pubkey_infinity_set() + .push_valid_set(2) + .run_checks() + } + + #[test] + fn signature_set_3_sets_with_one_invalid_sig_infinity_set() { + SignatureSetTester::default() + .push_valid_set(2) + .push_invalid_sig_infinity_set() + .push_valid_set(2) + .run_checks() + } + }; +} + +mod blst { + test_suite!(blst_implementations); +} + +#[cfg(not(debug_assertions))] +mod milagro { + test_suite!(milagro_implementations); +} diff --git a/crypto/eth2_key_derivation/src/derived_key.rs b/crypto/eth2_key_derivation/src/derived_key.rs index caacac162f..74dfcfbf3d 100644 --- a/crypto/eth2_key_derivation/src/derived_key.rs +++ b/crypto/eth2_key_derivation/src/derived_key.rs @@ -1,4 +1,4 @@ -use crate::{lamport_secret_key::LamportSecretKey, secret_bytes::SecretBytes, SecretHash}; +use crate::{lamport_secret_key::LamportSecretKey, secret_bytes::SecretBytes, ZeroizeHash}; use num_bigint_dig::BigUint; use ring::hkdf::{KeyType, Prk, Salt, HKDF_SHA256}; use sha2::{Digest, Sha256}; @@ -32,7 +32,7 @@ pub const MOD_R_L: usize = 48; // little over-cautious here; we don't require high-speed key generation at this stage. #[derive(Zeroize)] #[zeroize(drop)] -pub struct DerivedKey(SecretHash); +pub struct DerivedKey(ZeroizeHash); impl DerivedKey { /// Instantiates `Self` from some secret seed bytes. @@ -64,14 +64,14 @@ impl DerivedKey { /// Derives the "master" BLS secret key from some `seed` bytes. /// /// Equivalent to `derive_master_SK` in EIP-2333. -fn derive_master_sk(seed: &[u8]) -> SecretHash { +fn derive_master_sk(seed: &[u8]) -> ZeroizeHash { hkdf_mod_r(seed) } /// From the given `parent_sk`, derives a child key at index`. /// /// Equivalent to `derive_child_SK` in EIP-2333. -fn derive_child_sk(parent_sk: &[u8], index: u32) -> SecretHash { +fn derive_child_sk(parent_sk: &[u8], index: u32) -> ZeroizeHash { let compressed_lamport_pk = parent_sk_to_lamport_pk(parent_sk, index); hkdf_mod_r(compressed_lamport_pk.as_bytes()) } @@ -80,7 +80,7 @@ fn derive_child_sk(parent_sk: &[u8], index: u32) -> SecretHash { /// BLS private key within the order of the BLS-381 curve. /// /// Equivalent to `HKDF_mod_r` in EIP-2333. -fn hkdf_mod_r(ikm: &[u8]) -> SecretHash { +fn hkdf_mod_r(ikm: &[u8]) -> ZeroizeHash { let prk = hkdf_extract(b"BLS-SIG-KEYGEN-SALT-", ikm); let okm = &hkdf_expand(prk, MOD_R_L); mod_r(okm.as_bytes()) @@ -90,7 +90,7 @@ fn hkdf_mod_r(ikm: &[u8]) -> SecretHash { /// BLS-381 curve. /// /// This function is a part of the `HKDF_mod_r` function in EIP-2333. -fn mod_r(bytes: &[u8]) -> SecretHash { +fn mod_r(bytes: &[u8]) -> ZeroizeHash { let n = BigUint::from_bytes_be(bytes); let r = BigUint::parse_bytes(R.as_bytes(), 10).expect("must be able to parse R"); let x = SecretBytes::from((n % r).to_bytes_be()); @@ -99,7 +99,7 @@ fn mod_r(bytes: &[u8]) -> SecretHash { debug_assert!(x_slice.len() <= HASH_SIZE); - let mut output = SecretHash::zero(); + let mut output = ZeroizeHash::zero(); output.as_mut_bytes()[HASH_SIZE - x_slice.len()..].copy_from_slice(&x_slice); output } @@ -107,7 +107,7 @@ fn mod_r(bytes: &[u8]) -> SecretHash { /// Generates a Lamport public key from the given `ikm` (which is assumed to be a BLS secret key). /// /// Equivalent to `parent_SK_to_lamport_PK` in EIP-2333. -fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> SecretHash { +fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> ZeroizeHash { let salt = index.to_be_bytes(); let not_ikm = flip_bits(ikm); @@ -130,7 +130,7 @@ fn parent_sk_to_lamport_pk(ikm: &[u8], index: u32) -> SecretHash { pk_bytes[i * HASH_SIZE..(i + 1) * HASH_SIZE].copy_from_slice(&hasher.finalize()); }); - let mut compressed_lamport_pk = SecretHash::zero(); + let mut compressed_lamport_pk = ZeroizeHash::zero(); let mut hasher = Sha256::new(); hasher.update(lamport_pk.as_bytes()); compressed_lamport_pk @@ -183,10 +183,10 @@ fn hkdf_expand(prk: Prk, l: usize) -> SecretBytes { /// ## Panics /// /// If `input` is not 32-bytes. -fn flip_bits(input: &[u8]) -> SecretHash { +fn flip_bits(input: &[u8]) -> ZeroizeHash { assert_eq!(input.len(), HASH_SIZE); - let mut output = SecretHash::zero(); + let mut output = ZeroizeHash::zero(); let output_bytes = output.as_mut_bytes(); for (i, byte) in input.iter().enumerate() { diff --git a/crypto/eth2_key_derivation/src/lib.rs b/crypto/eth2_key_derivation/src/lib.rs index 763f8fe04c..8c5c1c5192 100644 --- a/crypto/eth2_key_derivation/src/lib.rs +++ b/crypto/eth2_key_derivation/src/lib.rs @@ -6,6 +6,6 @@ mod lamport_secret_key; mod plain_text; mod secret_bytes; -pub use bls::SecretHash; +pub use bls::ZeroizeHash; pub use derived_key::DerivedKey; pub use plain_text::PlainText; diff --git a/crypto/eth2_keystore/src/keystore.rs b/crypto/eth2_keystore/src/keystore.rs index 4867146102..536a7ae2ec 100644 --- a/crypto/eth2_keystore/src/keystore.rs +++ b/crypto/eth2_keystore/src/keystore.rs @@ -10,7 +10,7 @@ use crate::Uuid; use aes_ctr::stream_cipher::generic_array::GenericArray; use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher}; use aes_ctr::Aes128Ctr as AesCtr; -use bls::{Keypair, PublicKey, SecretHash, SecretKey}; +use bls::{Keypair, SecretKey, ZeroizeHash}; use eth2_key_derivation::PlainText; use hmac::Hmac; use pbkdf2::pbkdf2; @@ -21,7 +21,6 @@ use scrypt::{ }; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use ssz::DecodeError; use std::io::{Read, Write}; /// The byte-length of a BLS secret key. @@ -58,7 +57,7 @@ pub const HASH_SIZE: usize = 32; pub enum Error { InvalidSecretKeyLen { len: usize, expected: usize }, InvalidPassword, - InvalidSecretKeyBytes(DecodeError), + InvalidSecretKeyBytes(bls::Error), PublicKeyMismatch, EmptyPassword, UnableToSerialize(String), @@ -147,7 +146,7 @@ impl Keystore { uuid: Uuid, path: String, ) -> Result { - let secret: SecretHash = keypair.sk.as_bytes(); + let secret: ZeroizeHash = keypair.sk.serialize(); let (cipher_text, checksum) = encrypt(secret.as_bytes(), password, &kdf, &cipher)?; @@ -172,7 +171,7 @@ impl Keystore { }, uuid, path, - pubkey: keypair.pk.as_hex_string()[2..].to_string(), + pubkey: keypair.pk.to_hex_string()[2..].to_string(), version: Version::four(), }, }) @@ -201,7 +200,7 @@ impl Keystore { let keypair = keypair_from_secret(plain_text.as_bytes())?; // Verify that the derived `PublicKey` matches `self`. - if keypair.pk.as_hex_string()[2..] != self.json.pubkey { + if keypair.pk.to_hex_string()[2..] != self.json.pubkey { return Err(Error::PublicKeyMismatch); } @@ -258,9 +257,9 @@ impl Keystore { /// - If `secret.len() != 32`. /// - If `secret` does not represent a point in the BLS curve. pub fn keypair_from_secret(secret: &[u8]) -> Result { - let sk = SecretKey::from_bytes(secret).map_err(Error::InvalidSecretKeyBytes)?; - let pk = PublicKey::from_secret_key(&sk); - Ok(Keypair { sk, pk }) + let sk = SecretKey::deserialize(secret).map_err(Error::InvalidSecretKeyBytes)?; + let pk = sk.public_key(); + Ok(Keypair::from_components(pk, sk)) } /// Returns `Kdf` used by default when creating keystores. diff --git a/crypto/eth2_keystore/src/lib.rs b/crypto/eth2_keystore/src/lib.rs index 385b1950b6..afa5e75de3 100644 --- a/crypto/eth2_keystore/src/lib.rs +++ b/crypto/eth2_keystore/src/lib.rs @@ -6,7 +6,7 @@ mod keystore; pub mod json_keystore; -pub use bls::SecretHash; +pub use bls::ZeroizeHash; pub use eth2_key_derivation::PlainText; pub use keystore::{ decrypt, default_kdf, encrypt, keypair_from_secret, Error, Keystore, KeystoreBuilder, DKLEN, diff --git a/crypto/eth2_keystore/tests/eip2335_vectors.rs b/crypto/eth2_keystore/tests/eip2335_vectors.rs index 60bb02be65..b0049ed003 100644 --- a/crypto/eth2_keystore/tests/eip2335_vectors.rs +++ b/crypto/eth2_keystore/tests/eip2335_vectors.rs @@ -14,7 +14,7 @@ pub fn decode_and_check_sk(json: &str) -> Keystore { let keystore = Keystore::from_json_str(json).expect("should decode keystore json"); let expected_sk = hex::decode(EXPECTED_SECRET).unwrap(); let keypair = keystore.decrypt_keypair(PASSWORD.as_bytes()).unwrap(); - assert_eq!(keypair.sk.as_bytes().as_ref(), &expected_sk[..]); + assert_eq!(keypair.sk.serialize().as_ref(), &expected_sk[..]); keystore } diff --git a/crypto/eth2_wallet/tests/tests.rs b/crypto/eth2_wallet/tests/tests.rs index bcc579d86a..d75b0ab34b 100644 --- a/crypto/eth2_wallet/tests/tests.rs +++ b/crypto/eth2_wallet/tests/tests.rs @@ -242,13 +242,13 @@ fn key_derivation_from_seed() { .expect("should decrypt voting keypair"); assert_eq!( - voting_keypair.sk.as_bytes().as_ref(), + voting_keypair.sk.serialize().as_ref(), &manually_derived_voting_key(i)[..], "voting secret should match manually derived" ); assert_eq!( - voting_keypair.sk.as_bytes().as_ref(), + voting_keypair.sk.serialize().as_ref(), &recovered_voting_key(&wallet, i)[..], "voting secret should match recovered" ); @@ -259,20 +259,20 @@ fn key_derivation_from_seed() { .expect("should decrypt withdrawal keypair"); assert_eq!( - withdrawal_keypair.sk.as_bytes().as_ref(), + withdrawal_keypair.sk.serialize().as_ref(), &manually_derived_withdrawal_key(i)[..], "withdrawal secret should match manually derived" ); assert_eq!( - withdrawal_keypair.sk.as_bytes().as_ref(), + withdrawal_keypair.sk.serialize().as_ref(), &recovered_withdrawal_key(&wallet, i)[..], "withdrawal secret should match recovered" ); assert_ne!( - withdrawal_keypair.sk.as_bytes().as_ref(), - voting_keypair.sk.as_bytes().as_bytes(), + withdrawal_keypair.sk.serialize().as_ref(), + voting_keypair.sk.serialize().as_bytes(), "voting and withdrawal keypairs should be distinct" ); diff --git a/lighthouse/tests/account_manager.rs b/lighthouse/tests/account_manager.rs index b8daa2b726..079f78cba7 100644 --- a/lighthouse/tests/account_manager.rs +++ b/lighthouse/tests/account_manager.rs @@ -1,7 +1,6 @@ #![cfg(not(debug_assertions))] use account_manager::{ - upgrade_legacy_keypairs::{CMD as UPGRADE_CMD, *}, validator::{create::*, CMD as VALIDATOR_CMD}, wallet::{ create::{CMD as CREATE_CMD, *}, @@ -16,7 +15,6 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use std::str::from_utf8; use tempfile::{tempdir, TempDir}; -use types::Keypair; use validator_dir::ValidatorDir; // TODO: create tests for the `lighthouse account validator deposit` command. This involves getting @@ -365,56 +363,3 @@ fn validator_create() { assert_eq!(dir_child_count(validator_dir.path()), 6); } - -fn write_legacy_keypair>(name: &str, dir: P) -> Keypair { - let keypair = Keypair::random(); - - let mut keypair_bytes = keypair.pk.as_bytes().to_vec(); - keypair_bytes.extend_from_slice(keypair.sk.as_bytes().as_ref()); - - fs::write(dir.as_ref().join(name), &keypair_bytes).unwrap(); - - keypair -} - -#[test] -fn upgrade_legacy_keypairs() { - let validators_dir = tempdir().unwrap(); - let secrets_dir = tempdir().unwrap(); - - let validators = (0..2) - .into_iter() - .map(|i| { - let validator_dir = validators_dir.path().join(format!("myval{}", i)); - - fs::create_dir_all(&validator_dir).unwrap(); - - let voting_keypair = write_legacy_keypair(VOTING_KEYPAIR_FILE, &validator_dir); - let withdrawal_keypair = write_legacy_keypair(WITHDRAWAL_KEYPAIR_FILE, &validator_dir); - - (validator_dir, voting_keypair, withdrawal_keypair) - }) - .collect::>(); - - account_cmd() - .arg(UPGRADE_CMD) - .arg(format!("--{}", VALIDATOR_DIR_FLAG)) - .arg(validators_dir.path().as_os_str()) - .arg(format!("--{}", SECRETS_DIR_FLAG)) - .arg(secrets_dir.path().as_os_str()) - .output() - .unwrap(); - - for (validator_dir, voting_keypair, withdrawal_keypair) in validators { - let dir = ValidatorDir::open(&validator_dir).unwrap(); - - assert_eq!( - voting_keypair.pk, - dir.voting_keypair(secrets_dir.path()).unwrap().pk - ); - assert_eq!( - withdrawal_keypair.pk, - dir.withdrawal_keypair(secrets_dir.path()).unwrap().pk - ); - } -} diff --git a/testing/ef_tests/Cargo.toml b/testing/ef_tests/Cargo.toml index 2dc48a2972..9d7dfd81cc 100644 --- a/testing/ef_tests/Cargo.toml +++ b/testing/ef_tests/Cargo.toml @@ -7,10 +7,11 @@ edition = "2018" [features] # `ef_tests` feature must be enabled to actually run the tests ef_tests = [] +milagro = ["bls/milagro"] fake_crypto = ["bls/fake_crypto"] [dependencies] -bls = { path = "../../crypto/bls" } +bls = { path = "../../crypto/bls", default-features = false } compare_fields = { path = "../../common/compare_fields" } ethereum-types = "0.9.1" hex = "0.4.2" diff --git a/testing/ef_tests/src/cases/bls_aggregate_sigs.rs b/testing/ef_tests/src/cases/bls_aggregate_sigs.rs index 71554705b3..776e410718 100644 --- a/testing/ef_tests/src/cases/bls_aggregate_sigs.rs +++ b/testing/ef_tests/src/cases/bls_aggregate_sigs.rs @@ -14,27 +14,27 @@ impl BlsCase for BlsAggregateSigs {} impl Case for BlsAggregateSigs { fn result(&self, _case_index: usize) -> Result<(), Error> { - let mut aggregate_signature = AggregateSignature::new(); + let mut aggregate_signature = AggregateSignature::infinity(); for key_str in &self.input { let sig = hex::decode(&key_str[2..]) .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; - let sig = Signature::from_bytes(&sig) + let sig = Signature::deserialize(&sig) .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; - aggregate_signature.add(&sig); + aggregate_signature.add_assign(&sig); } // Check for YAML null value, indicating invalid input. This is a bit of a hack, // as our mutating `aggregate_signature.add` API doesn't play nicely with aggregating 0 // inputs. let output_bytes = if self.output == "~" { - AggregateSignature::new().as_bytes().to_vec() + AggregateSignature::infinity().serialize().to_vec() } else { hex::decode(&self.output[2..]) .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))? }; - let aggregate_signature = Ok(aggregate_signature.as_bytes().to_vec()); + let aggregate_signature = Ok(aggregate_signature.serialize().to_vec()); compare_result::, Vec>(&aggregate_signature, &Some(output_bytes)) } diff --git a/testing/ef_tests/src/cases/bls_aggregate_verify.rs b/testing/ef_tests/src/cases/bls_aggregate_verify.rs index a2f74c0b2f..22fc76035a 100644 --- a/testing/ef_tests/src/cases/bls_aggregate_verify.rs +++ b/testing/ef_tests/src/cases/bls_aggregate_verify.rs @@ -3,6 +3,7 @@ use crate::case_result::compare_result; use crate::cases::common::BlsCase; use bls::{AggregateSignature, PublicKey}; use serde_derive::Deserialize; +use types::Hash256; #[derive(Debug, Clone, Deserialize)] pub struct BlsAggregateVerifyInput { @@ -26,23 +27,22 @@ impl Case for BlsAggregateVerify { .messages .iter() .map(|message| { - hex::decode(&message[2..]).map_err(|e| Error::FailedToParseTest(format!("{:?}", e))) + let bytes = hex::decode(&message[2..]) + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + Ok(Hash256::from_slice(&bytes)) }) - .collect::>, _>>()?; - - let message_refs = messages - .iter() - .map(|x| x.as_slice()) - .collect::>(); + .collect::, _>>()?; let pubkey_refs = self.input.pubkeys.iter().collect::>(); - let signature_ok = hex::decode(&self.input.signature[2..]) + let signature_bytes = hex::decode(&self.input.signature[2..]) + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + + let signature_valid = AggregateSignature::deserialize(&signature_bytes) .ok() - .and_then(|bytes: Vec| AggregateSignature::from_bytes(&bytes).ok()) - .map(|signature| signature.verify_multiple(&message_refs, &pubkey_refs)) + .map(|signature| signature.aggregate_verify(&messages, &pubkey_refs)) .unwrap_or(false); - compare_result::(&Ok(signature_ok), &Some(self.output)) + compare_result::(&Ok(signature_valid), &Some(self.output)) } } diff --git a/testing/ef_tests/src/cases/bls_fast_aggregate_verify.rs b/testing/ef_tests/src/cases/bls_fast_aggregate_verify.rs index 3f204e54f0..d63f4da370 100644 --- a/testing/ef_tests/src/cases/bls_fast_aggregate_verify.rs +++ b/testing/ef_tests/src/cases/bls_fast_aggregate_verify.rs @@ -1,9 +1,10 @@ use super::*; use crate::case_result::compare_result; use crate::cases::common::BlsCase; -use bls::{AggregatePublicKey, AggregateSignature, PublicKey, PublicKeyBytes}; +use bls::{AggregateSignature, PublicKey, PublicKeyBytes}; use serde_derive::Deserialize; use std::convert::TryInto; +use types::Hash256; #[derive(Debug, Clone, Deserialize)] pub struct BlsFastAggregateVerifyInput { @@ -22,27 +23,25 @@ impl BlsCase for BlsFastAggregateVerify {} impl Case for BlsFastAggregateVerify { fn result(&self, _case_index: usize) -> Result<(), Error> { - let message = hex::decode(&self.input.message[2..]) - .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + let message = Hash256::from_slice( + &hex::decode(&self.input.message[2..]) + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?, + ); - let signature_ok = self + let pubkeys = self .input .pubkeys .iter() - .try_fold( - AggregatePublicKey::new(), - |mut agg, pkb| -> Option { - let pk: Result = pkb.try_into(); - agg.add(&pk.ok()?); - Some(agg) - }, - ) - .and_then(|aggregate_pubkey| { - hex::decode(&self.input.signature[2..]) - .ok() - .and_then(|bytes: Vec| AggregateSignature::from_bytes(&bytes).ok()) - .map(|signature| signature.verify(&message, &aggregate_pubkey)) - }) + .map(|pkb| pkb.try_into()) + .collect::, bls::Error>>() + .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; + + let pubkey_refs = pubkeys.iter().collect::>(); + + let signature_ok = hex::decode(&self.input.signature[2..]) + .ok() + .and_then(|bytes: Vec| AggregateSignature::deserialize(&bytes).ok()) + .map(|signature| signature.fast_aggregate_verify(message, &pubkey_refs)) .unwrap_or(false); compare_result::(&Ok(signature_ok), &Some(self.output)) diff --git a/testing/ef_tests/src/cases/bls_sign_msg.rs b/testing/ef_tests/src/cases/bls_sign_msg.rs index 2662746995..05edc18840 100644 --- a/testing/ef_tests/src/cases/bls_sign_msg.rs +++ b/testing/ef_tests/src/cases/bls_sign_msg.rs @@ -1,8 +1,9 @@ use super::*; use crate::case_result::compare_result; use crate::cases::common::BlsCase; -use bls::{SecretKey, Signature}; +use bls::SecretKey; use serde_derive::Deserialize; +use types::Hash256; #[derive(Debug, Clone, Deserialize)] pub struct BlsSignInput { @@ -23,16 +24,19 @@ impl Case for BlsSign { // Convert private_key and message to required types let sk = hex::decode(&self.input.privkey[2..]) .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; - let sk = SecretKey::from_bytes(&sk).unwrap(); + + assert_eq!(sk.len(), 32); + + let sk = SecretKey::deserialize(&sk).unwrap(); let msg = hex::decode(&self.input.message[2..]) .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; - let signature = Signature::new(&msg, &sk); + let signature = sk.sign(Hash256::from_slice(&msg)); // Convert the output to one set of bytes let decoded = hex::decode(&self.output[2..]) .map_err(|e| Error::FailedToParseTest(format!("{:?}", e)))?; - compare_result::, Vec>(&Ok(signature.as_bytes().to_vec()), &Some(decoded)) + compare_result::, Vec>(&Ok(signature.serialize().to_vec()), &Some(decoded)) } } diff --git a/testing/ef_tests/src/cases/bls_verify_msg.rs b/testing/ef_tests/src/cases/bls_verify_msg.rs index 421006ff8c..cd4bb93afa 100644 --- a/testing/ef_tests/src/cases/bls_verify_msg.rs +++ b/testing/ef_tests/src/cases/bls_verify_msg.rs @@ -4,6 +4,7 @@ use crate::cases::common::BlsCase; use bls::{PublicKey, Signature, SignatureBytes}; use serde_derive::Deserialize; use std::convert::TryInto; +use types::Hash256; #[derive(Debug, Clone, Deserialize)] pub struct BlsVerifyInput { @@ -27,7 +28,9 @@ impl Case for BlsVerify { let signature_ok = (&self.input.signature) .try_into() - .map(|signature: Signature| signature.verify(&message, &self.input.pubkey)) + .map(|signature: Signature| { + signature.verify(&self.input.pubkey, Hash256::from_slice(&message)) + }) .unwrap_or(false); compare_result::(&Ok(signature_ok), &Some(self.output)) diff --git a/testing/eth1_test_rig/src/lib.rs b/testing/eth1_test_rig/src/lib.rs index a861c06b58..53715133bf 100644 --- a/testing/eth1_test_rig/src/lib.rs +++ b/testing/eth1_test_rig/src/lib.rs @@ -124,7 +124,7 @@ impl DepositContract { pubkey: keypair.pk.into(), withdrawal_credentials, amount, - signature: Signature::empty_signature().into(), + signature: Signature::empty().into(), }; deposit.signature = deposit.create_signature(&keypair.sk, &E::default_spec()); @@ -142,7 +142,7 @@ impl DepositContract { pubkey: keypair.pk.into(), withdrawal_credentials: Hash256::zero(), amount: 32_000_000_000, - signature: Signature::empty_signature().into(), + signature: Signature::empty().into(), }; deposit.signature = deposit.create_signature(&keypair.sk, &E::default_spec()); @@ -168,7 +168,7 @@ impl DepositContract { pubkey: keypair.pk.into(), withdrawal_credentials: Hash256::zero(), amount, - signature: Signature::empty_signature().into(), + signature: Signature::empty().into(), }; deposit.signature = deposit.create_signature(&keypair.sk, &E::default_spec()); diff --git a/validator_client/slashing_protection/src/slashing_database.rs b/validator_client/slashing_protection/src/slashing_database.rs index a0856b5255..cd2413efdb 100644 --- a/validator_client/slashing_protection/src/slashing_database.rs +++ b/validator_client/slashing_protection/src/slashing_database.rs @@ -148,7 +148,7 @@ impl SlashingDatabase { let mut stmt = txn.prepare("INSERT INTO validators (public_key) VALUES (?1)")?; for pubkey in public_keys { - stmt.execute(&[pubkey.as_hex_string()])?; + stmt.execute(&[pubkey.to_hex_string()])?; } } txn.commit()?; @@ -163,7 +163,7 @@ impl SlashingDatabase { fn get_validator_id(txn: &Transaction, public_key: &PublicKey) -> Result { txn.query_row( "SELECT id FROM validators WHERE public_key = ?1", - params![&public_key.as_hex_string()], + params![&public_key.to_hex_string()], |row| row.get(0), ) .optional()? diff --git a/validator_client/src/validator_store.rs b/validator_client/src/validator_store.rs index 6b11f9a138..f7d0442d37 100644 --- a/validator_client/src/validator_store.rs +++ b/validator_client/src/validator_store.rs @@ -129,7 +129,7 @@ impl ValidatorStore { ); let message = epoch.signing_root(domain); - Some(Signature::new(message.as_bytes(), &voting_keypair.sk)) + Some(voting_keypair.sk.sign(message)) }) }