From 25f2e212c307627ef169a93557ad1527d08e0151 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 30 Aug 2019 13:30:07 +1000 Subject: [PATCH] Update to latest interop keypair spec --- .../generate_deterministic_keypairs.rs | 15 +- eth2/utils/bls/src/public_key.rs | 4 + eth2/utils/bls/src/secret_key.rs | 4 + eth2/utils/eth2_interop_keypairs/Cargo.toml | 8 + eth2/utils/eth2_interop_keypairs/src/lib.rs | 138 +++++------------- .../utils/eth2_interop_keypairs/tests/test.rs | 64 ++++++++ 6 files changed, 121 insertions(+), 112 deletions(-) create mode 100644 eth2/utils/eth2_interop_keypairs/tests/test.rs diff --git a/eth2/types/src/test_utils/generate_deterministic_keypairs.rs b/eth2/types/src/test_utils/generate_deterministic_keypairs.rs index 172b142ef..a687eb978 100644 --- a/eth2/types/src/test_utils/generate_deterministic_keypairs.rs +++ b/eth2/types/src/test_utils/generate_deterministic_keypairs.rs @@ -1,5 +1,5 @@ use crate::*; -use eth2_interop_keypairs::be_private_key; +use eth2_interop_keypairs::keypair; use log::debug; use rayon::prelude::*; @@ -15,8 +15,8 @@ pub fn generate_deterministic_keypairs(validator_count: usize) -> Vec { let keypairs: Vec = (0..validator_count) .collect::>() - .par_iter() - .map(|&i| generate_deterministic_keypair(i)) + .into_par_iter() + .map(generate_deterministic_keypair) .collect(); keypairs @@ -26,8 +26,9 @@ 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 sk = SecretKey::from_bytes(&be_private_key(validator_index)) - .expect("be_private_key always returns valid keys"); - let pk = PublicKey::from_secret_key(&sk); - Keypair { sk, pk } + let raw = keypair(validator_index); + Keypair { + pk: PublicKey::from_raw(raw.pk), + sk: SecretKey::from_raw(raw.sk), + } } diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index e03b17686..4b5abb58e 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -20,6 +20,10 @@ impl PublicKey { PublicKey(RawPublicKey::from_secret_key(secret_key.as_raw())) } + pub fn from_raw(raw: RawPublicKey) -> Self { + Self(raw) + } + /// Returns the underlying signature. pub fn as_raw(&self) -> &RawPublicKey { &self.0 diff --git a/eth2/utils/bls/src/secret_key.rs b/eth2/utils/bls/src/secret_key.rs index 12f9a713b..54da0fa0f 100644 --- a/eth2/utils/bls/src/secret_key.rs +++ b/eth2/utils/bls/src/secret_key.rs @@ -20,6 +20,10 @@ impl SecretKey { SecretKey(RawSecretKey::random(&mut rand::thread_rng())) } + pub fn from_raw(raw: RawSecretKey) -> Self { + Self(raw) + } + /// Returns the underlying point as compressed bytes. fn as_bytes(&self) -> Vec { self.as_raw().as_bytes() diff --git a/eth2/utils/eth2_interop_keypairs/Cargo.toml b/eth2/utils/eth2_interop_keypairs/Cargo.toml index e1c4dab04..31f9718cd 100644 --- a/eth2/utils/eth2_interop_keypairs/Cargo.toml +++ b/eth2/utils/eth2_interop_keypairs/Cargo.toml @@ -7,5 +7,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +lazy_static = "1.4" num-bigint = "0.2" eth2_hashing = "0.1" +milagro_bls = { git = "https://github.com/sigp/milagro_bls", tag = "v0.10.0" } + +[dev-dependencies] +base64 = "0.10" +serde = "1.0" +serde_derive = "1.0" +serde_yaml = "0.8" diff --git a/eth2/utils/eth2_interop_keypairs/src/lib.rs b/eth2/utils/eth2_interop_keypairs/src/lib.rs index 8ba2b9eba..4c1320723 100644 --- a/eth2/utils/eth2_interop_keypairs/src/lib.rs +++ b/eth2/utils/eth2_interop_keypairs/src/lib.rs @@ -5,126 +5,54 @@ //! keys generated here are **not secret** and are **not for production use**. //! //! Note: these keys have not been tested against a reference implementation, yet. +#[macro_use] +extern crate lazy_static; use eth2_hashing::hash; +use milagro_bls::{Keypair, PublicKey, SecretKey}; use num_bigint::BigUint; pub const CURVE_ORDER_BITS: usize = 255; pub const PRIVATE_KEY_BYTES: usize = 48; pub const HASH_BYTES: usize = 32; -fn hash_big_int_le(uint: BigUint) -> BigUint { - let mut preimage = uint.to_bytes_le(); - preimage.resize(32, 0_u8); - BigUint::from_bytes_le(&hash(&preimage)) +lazy_static! { + static ref CURVE_ORDER: BigUint = + "52435875175126190479447740508185965837690552500527637822603658699938581184513" + .parse::() + .expect("Curve order should be valid"); } -fn private_key(validator_index: usize) -> BigUint { - let mut key = BigUint::from(validator_index); - loop { - key = hash_big_int_le(key); - if key.bits() <= CURVE_ORDER_BITS { - break key; - } - } -} - -/// Generates an **unsafe** BLS12-381 private key for the given validator index, where that private -/// key is represented in big-endian bytes. -pub fn be_private_key(validator_index: usize) -> [u8; PRIVATE_KEY_BYTES] { - let vec = private_key(validator_index).to_bytes_be(); - - let mut out = [0; PRIVATE_KEY_BYTES]; - out[PRIVATE_KEY_BYTES - vec.len()..PRIVATE_KEY_BYTES].copy_from_slice(&vec); - out -} - -/// Generates an **unsafe** BLS12-381 private key for the given validator index, where that private -/// key is represented in little-endian bytes. pub fn le_private_key(validator_index: usize) -> [u8; PRIVATE_KEY_BYTES] { - let vec = private_key(validator_index).to_bytes_le(); + let preimage = { + let mut bytes = [0; HASH_BYTES]; + let index = validator_index.to_le_bytes(); + bytes[0..index.len()].copy_from_slice(&index); + bytes + }; - let mut out = [0; PRIVATE_KEY_BYTES]; - out[0..vec.len()].copy_from_slice(&vec); - out + let privkey = BigUint::from_bytes_le(&hash(&preimage)) % &*CURVE_ORDER; + + let mut bytes = [0; PRIVATE_KEY_BYTES]; + let privkey_bytes = privkey.to_bytes_le(); + bytes[0..privkey_bytes.len()].copy_from_slice(&privkey_bytes); + bytes } -#[cfg(test)] -mod tests { - use super::*; +pub fn keypair(validator_index: usize) -> Keypair { + let bytes = le_private_key(validator_index); - fn flip(vec: &[u8]) -> Vec { - let len = vec.len(); - let mut out = vec![0; len]; - for i in 0..len { - out[len - 1 - i] = vec[i]; - } - out - } + let sk = + SecretKey::from_bytes(&swap_bytes(bytes.to_vec())).expect("Should be valid private key"); - fn pad_le_bls(mut vec: Vec) -> Vec { - vec.resize(PRIVATE_KEY_BYTES, 0_u8); - vec - } - - fn pad_be_bls(mut vec: Vec) -> Vec { - let mut out = vec![0; PRIVATE_KEY_BYTES - vec.len()]; - out.append(&mut vec); - out - } - - fn pad_le_hash(index: usize) -> Vec { - let mut vec = index.to_le_bytes().to_vec(); - vec.resize(HASH_BYTES, 0_u8); - vec - } - - fn multihash(index: usize, rounds: usize) -> Vec { - let mut vec = pad_le_hash(index); - for _ in 0..rounds { - vec = hash(&vec); - } - vec - } - - fn compare(validator_index: usize, preimage: &[u8]) { - assert_eq!( - &le_private_key(validator_index)[..], - &pad_le_bls(hash(preimage))[..] - ); - assert_eq!( - &be_private_key(validator_index)[..], - &pad_be_bls(flip(&hash(preimage)))[..] - ); - } - - #[test] - fn consistency() { - for i in 0..256 { - let le = BigUint::from_bytes_le(&le_private_key(i)); - let be = BigUint::from_bytes_be(&be_private_key(i)); - assert_eq!(le, be); - } - } - - #[test] - fn non_repeats() { - // These indices only need one hash to be in the curve order. - compare(0, &pad_le_hash(0)); - compare(3, &pad_le_hash(3)); - } - - #[test] - fn repeats() { - // Index 5 needs 5x hashes to get into the curve order. - compare(5, &multihash(5, 5)); - } - - #[test] - fn doesnt_panic() { - for i in 0..256 { - be_private_key(i); - le_private_key(i); - } + Keypair { + pk: PublicKey::from_secret_key(&sk), + sk, } } + +fn swap_bytes(input: Vec) -> Vec { + let mut output = vec![]; + input.into_iter().rev().for_each(|byte| output.push(byte)); + output +} diff --git a/eth2/utils/eth2_interop_keypairs/tests/test.rs b/eth2/utils/eth2_interop_keypairs/tests/test.rs new file mode 100644 index 000000000..45f128db6 --- /dev/null +++ b/eth2/utils/eth2_interop_keypairs/tests/test.rs @@ -0,0 +1,64 @@ +#![cfg(test)] +use eth2_interop_keypairs::{keypair, le_private_key}; +use num_bigint::BigUint; + +#[test] +fn reference_private_keys() { + // Sourced from: + // + // https://github.com/ethereum/eth2.0-pm/blob/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start/keygen_test_vector.yaml + let reference = [ + "16808672146709759238327133555736750089977066230599028589193936481731504400486", + "37006103240406073079686739739280712467525465637222501547219594975923976982528", + "22330876536127119444572216874798222843352868708084730796787004036811744442455", + "17048462031355941381150076874414096388968985457797372268770826099852902060945", + "28647806952216650698330424381872693846361470773871570637461872359310549743691", + "2416304019107052589452838695606585506736351107897780798170812672519914514344", + "7300215445567548136411883691093515822872548648751398235557229381530420545683", + "26495790445032093722332687600112008700915252495659977774957922313678954054133", + "2908643403277969554503670470854573663206729491025062456164283925661321952518", + "19554639423851580804889717218680781396599791537051606512605582393920758869044", + ]; + reference + .into_iter() + .enumerate() + .for_each(|(i, reference)| { + let bytes = le_private_key(i); + let num = BigUint::from_bytes_le(&bytes); + assert_eq!(&num.to_str_radix(10), reference) + }); +} + +#[test] +fn reference_public_keys() { + // Sourced from: + // + // https://github.com/ethereum/eth2.0-pm/blob/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start/keygen_test_vector.yaml + let reference = [ + "qZp27XeW974i1bfoXe63xWd+iOUR4LM3YY+MTrYTSbS/LRU/ZJ97UzWf6LlKOORM", + "uJvrxpl2lyajGMjplxvTFxKXxhrqSmV4p6T5S1R9y6W6wWqJEItrah/jaV0ah0oL", + "o6MrD4tN24PxoKhT2B3XJd/ld9T0w9uOzlLOKwJuyoSBXBp+jpKk3j11VzO/fkqb", + "iMFB33fNnY16cadcgmxBqcnwPG7hsYDz54UvaigAmd7TUbWNZuZTr45CgWpNj1Mu", + "gSg7eiDhykYOvZu9dwBdVXNwyrsfmkT1MMTExmIw9nX434tMKBiFGqfXeoDKWkpe", + "qwvdoPhfhC9DG+rM8SUL8f17pRtBAP1kNktkAf2oW7AGmz5xW1iBloTn/AsQpyo0", + "mXfxyLcxqNVVgUa/uGyuomQ088WHi1ib8oCkLJFZ5wDp3w5AhilsILAR0ueMJ9Nz", + "qNTHwneVpyWWExfvWVOnAy7W2Dc524sOinI1PRuLRDlCf376LInKoDzJ8o+Muris", + "ptMQ27+rmiJFD1mZP4ekzl22Ij87Xx8w0sTscYki1ADgs8d0HejlmWD3JBGg7hCn", + "mJNBPAAoOj+e2f2YRd2hzqOCKNIlZ/lUHczDV+VKLWpuIEEDySVky8BfSQWsfEk6", + ]; + reference + .into_iter() + .enumerate() + .for_each(|(i, reference)| { + let pair = keypair(i); + let reference = base64::decode(reference).expect("Reference should be valid base64"); + + assert_eq!( + reference.len(), + 48, + "Reference should be 48 bytes (public key size)" + ); + + assert_eq!(pair.pk.as_bytes(), reference); + }); +}