Merge pull request #240 from sigp/signature-scheme-update

Signature scheme update
This commit is contained in:
Paul Hauner 2019-02-25 20:56:26 +13:00 committed by GitHub
commit 26827c60a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 150 additions and 78 deletions

View File

@ -129,10 +129,13 @@ impl AttestationAggregator {
Some(validator_record) => validator_record, Some(validator_record) => validator_record,
}; };
if !free_attestation if !free_attestation.signature.verify(
.signature &signable_message,
.verify(&signable_message, &validator_record.pubkey) cached_state
{ .fork
.get_domain(cached_state.current_epoch(spec), spec.domain_attestation),
&validator_record.pubkey,
) {
invalid_outcome!(Message::BadSignature); invalid_outcome!(Message::BadSignature);
} }

View File

@ -25,23 +25,23 @@ impl LocalSigner {
} }
/// Sign some message. /// Sign some message.
fn bls_sign(&self, message: &[u8]) -> Option<Signature> { fn bls_sign(&self, message: &[u8], domain: u64) -> Option<Signature> {
Some(Signature::new(message, &self.keypair.sk)) Some(Signature::new(message, domain, &self.keypair.sk))
} }
} }
impl BlockProposerSigner for LocalSigner { impl BlockProposerSigner for LocalSigner {
fn sign_block_proposal(&self, message: &[u8]) -> Option<Signature> { fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option<Signature> {
self.bls_sign(message) self.bls_sign(message, domain)
} }
fn sign_randao_reveal(&self, message: &[u8]) -> Option<Signature> { fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option<Signature> {
self.bls_sign(message) self.bls_sign(message, domain)
} }
} }
impl AttesterSigner for LocalSigner { impl AttesterSigner for LocalSigner {
fn sign_attestation_message(&self, message: &[u8]) -> Option<Signature> { fn sign_attestation_message(&self, message: &[u8], domain: u64) -> Option<Signature> {
self.bls_sign(message) self.bls_sign(message, domain)
} }
} }

View File

@ -10,6 +10,7 @@ pub use self::traits::{
}; };
const PHASE_0_CUSTODY_BIT: bool = false; const PHASE_0_CUSTODY_BIT: bool = false;
const DOMAIN_ATTESTATION: u64 = 1;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum PollOutcome { pub enum PollOutcome {
@ -136,8 +137,10 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> Attester<T, U, V,
fn sign_attestation_data(&mut self, attestation_data: &AttestationData) -> Option<Signature> { fn sign_attestation_data(&mut self, attestation_data: &AttestationData) -> Option<Signature> {
self.store_produce(attestation_data); self.store_produce(attestation_data);
self.signer self.signer.sign_attestation_message(
.sign_attestation_message(&attestation_data.signable_message(PHASE_0_CUSTODY_BIT)[..]) &attestation_data.signable_message(PHASE_0_CUSTODY_BIT)[..],
DOMAIN_ATTESTATION,
)
} }
/// Returns `true` if signing some attestation_data is safe (non-slashable). /// Returns `true` if signing some attestation_data is safe (non-slashable).

View File

@ -25,7 +25,7 @@ impl LocalSigner {
} }
impl Signer for LocalSigner { impl Signer for LocalSigner {
fn sign_attestation_message(&self, message: &[u8]) -> Option<Signature> { fn sign_attestation_message(&self, message: &[u8], domain: u64) -> Option<Signature> {
Some(Signature::new(message, &self.keypair.sk)) Some(Signature::new(message, domain, &self.keypair.sk))
} }
} }

View File

@ -45,5 +45,5 @@ pub trait DutiesReader: Send + Sync {
/// Signs message using an internally-maintained private key. /// Signs message using an internally-maintained private key.
pub trait Signer { pub trait Signer {
fn sign_attestation_message(&self, message: &[u8]) -> Option<Signature>; fn sign_attestation_message(&self, message: &[u8], domain: u64) -> Option<Signature>;
} }

View File

@ -134,7 +134,10 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
// TODO: add domain, etc to this message. Also ensure result matches `into_to_bytes32`. // TODO: add domain, etc to this message. Also ensure result matches `into_to_bytes32`.
let message = int_to_bytes32(slot.epoch(self.spec.epoch_length).as_u64()); let message = int_to_bytes32(slot.epoch(self.spec.epoch_length).as_u64());
match self.signer.sign_randao_reveal(&message) { match self
.signer
.sign_randao_reveal(&message, self.spec.domain_randao)
{
None => return Ok(PollOutcome::SignerRejection(slot)), None => return Ok(PollOutcome::SignerRejection(slot)),
Some(signature) => signature, Some(signature) => signature,
} }
@ -166,10 +169,10 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
fn sign_block(&mut self, mut block: BeaconBlock) -> Option<BeaconBlock> { fn sign_block(&mut self, mut block: BeaconBlock) -> Option<BeaconBlock> {
self.store_produce(&block); self.store_produce(&block);
match self match self.signer.sign_block_proposal(
.signer &block.proposal_root(&self.spec)[..],
.sign_block_proposal(&block.proposal_root(&self.spec)[..]) self.spec.domain_proposal,
{ ) {
None => None, None => None,
Some(signature) => { Some(signature) => {
block.signature = signature; block.signature = signature;

View File

@ -25,11 +25,11 @@ impl LocalSigner {
} }
impl Signer for LocalSigner { impl Signer for LocalSigner {
fn sign_block_proposal(&self, message: &[u8]) -> Option<Signature> { fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option<Signature> {
Some(Signature::new(message, &self.keypair.sk)) Some(Signature::new(message, domain, &self.keypair.sk))
} }
fn sign_randao_reveal(&self, message: &[u8]) -> Option<Signature> { fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option<Signature> {
Some(Signature::new(message, &self.keypair.sk)) Some(Signature::new(message, domain, &self.keypair.sk))
} }
} }

View File

@ -44,6 +44,6 @@ pub trait DutiesReader: Send + Sync {
/// Signs message using an internally-maintained private key. /// Signs message using an internally-maintained private key.
pub trait Signer { pub trait Signer {
fn sign_block_proposal(&self, message: &[u8]) -> Option<Signature>; fn sign_block_proposal(&self, message: &[u8], domain: u64) -> Option<Signature>;
fn sign_randao_reveal(&self, message: &[u8]) -> Option<Signature>; fn sign_randao_reveal(&self, message: &[u8], domain: u64) -> Option<Signature>;
} }

View File

@ -0,0 +1 @@

View File

@ -396,14 +396,12 @@ fn validate_attestation_signature_optional(
Ok(()) Ok(())
} }
fn get_domain(_fork: &Fork, _epoch: Epoch, _domain_type: u64) -> u64 { fn get_domain(fork: &Fork, epoch: Epoch, domain_type: u64) -> u64 {
// TODO: stubbed out. fork.get_domain(epoch, domain_type)
0
} }
fn bls_verify(pubkey: &PublicKey, message: &[u8], signature: &Signature, _domain: u64) -> bool { fn bls_verify(pubkey: &PublicKey, message: &[u8], signature: &Signature, domain: u64) -> bool {
// TODO: add domain signature.verify(message, domain, pubkey)
signature.verify(message, pubkey)
} }
impl From<AttestationValidationError> for Error { impl From<AttestationValidationError> for Error {

View File

@ -26,11 +26,13 @@ impl Attestation {
&self, &self,
group_public_key: &AggregatePublicKey, group_public_key: &AggregatePublicKey,
custody_bit: bool, custody_bit: bool,
// TODO: use domain. domain: u64,
_domain: u64,
) -> bool { ) -> bool {
self.aggregate_signature self.aggregate_signature.verify(
.verify(&self.signable_message(custody_bit), group_public_key) &self.signable_message(custody_bit),
domain,
group_public_key,
)
} }
} }

View File

@ -2,8 +2,8 @@ use self::epoch_cache::EpochCache;
use crate::test_utils::TestRandom; use crate::test_utils::TestRandom;
use crate::{ use crate::{
validator::StatusFlags, validator_registry::get_active_validator_indices, AttestationData, validator::StatusFlags, validator_registry::get_active_validator_indices, AttestationData,
Bitfield, ChainSpec, Crosslink, Deposit, DepositData, Epoch, Eth1Data, Eth1DataVote, Fork, Bitfield, ChainSpec, Crosslink, Deposit, DepositData, DepositInput, Epoch, Eth1Data,
Hash256, PendingAttestation, PublicKey, Signature, Slot, Validator, Eth1DataVote, Fork, Hash256, PendingAttestation, PublicKey, Signature, Slot, Validator,
}; };
use bls::verify_proof_of_possession; use bls::verify_proof_of_possession;
use honey_badger_split::SplitExt; use honey_badger_split::SplitExt;
@ -515,6 +515,7 @@ impl BeaconState {
&self, &self,
slot: Slot, slot: Slot,
registry_change: bool, registry_change: bool,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<Vec<Vec<usize>>, Error> { ) -> Result<Vec<Vec<usize>>, Error> {
let (_committees_per_epoch, seed, shuffling_epoch, _shuffling_start_shard) = let (_committees_per_epoch, seed, shuffling_epoch, _shuffling_start_shard) =
@ -792,6 +793,30 @@ impl BeaconState {
self.validator_registry_update_epoch = current_epoch; self.validator_registry_update_epoch = current_epoch;
} }
/// Confirm validator owns PublicKey
///
/// Spec v0.2.0
pub fn validate_proof_of_possession(
&self,
pubkey: PublicKey,
proof_of_possession: Signature,
withdrawal_credentials: Hash256,
spec: &ChainSpec,
) -> bool {
let proof_of_possession_data = DepositInput {
pubkey: pubkey.clone(),
withdrawal_credentials,
proof_of_possession: Signature::empty_signature(),
};
proof_of_possession.verify(
&proof_of_possession_data.hash_tree_root(),
self.fork
.get_domain(self.slot.epoch(spec.epoch_length), spec.domain_deposit),
&pubkey,
)
}
/// Process multiple deposits in sequence. /// Process multiple deposits in sequence.
/// ///
/// Builds a hashmap of validator pubkeys to validator index and passes it to each successive /// Builds a hashmap of validator pubkeys to validator index and passes it to each successive
@ -843,8 +868,17 @@ impl BeaconState {
pubkey_map: Option<&HashMap<PublicKey, usize>>, pubkey_map: Option<&HashMap<PublicKey, usize>>,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<usize, ()> { ) -> Result<usize, ()> {
// TODO: ensure verify proof-of-possession represents the spec accurately. // TODO: update proof of possession to function written above (
if !verify_proof_of_possession(&proof_of_possession, &pubkey) { // requires bls::create_proof_of_possession to be updated
// https://github.com/sigp/lighthouse/issues/239
if !verify_proof_of_possession(&proof_of_possession, &pubkey)
//if !self.validate_proof_of_possession(
// pubkey.clone(),
// proof_of_possession,
// withdrawal_credentials,
// &spec,
// )
{
return Err(()); return Err(());
} }

View File

@ -11,6 +11,22 @@ pub struct Fork {
pub epoch: Epoch, pub epoch: Epoch,
} }
impl Fork {
/// Return the fork version of the given ``epoch``.
pub fn get_fork_version(&self, epoch: Epoch) -> u64 {
if epoch < self.epoch {
return self.previous_version;
}
self.current_version
}
/// Get the domain number that represents the fork meta and signature domain.
pub fn get_domain(&self, epoch: Epoch, domain_type: u64) -> u64 {
let fork_version = self.get_fork_version(epoch);
fork_version * u64::pow(2, 32) + domain_type
}
}
impl TreeHash for Fork { impl TreeHash for Fork {
fn hash_tree_root_internal(&self) -> Vec<u8> { fn hash_tree_root_internal(&self) -> Vec<u8> {
let mut result: Vec<u8> = vec![]; let mut result: Vec<u8> = vec![];

View File

@ -8,6 +8,6 @@ impl<T: RngCore> TestRandom<T> for Signature {
let mut message = vec![0; 32]; let mut message = vec![0; 32];
rng.fill_bytes(&mut message); rng.fill_bytes(&mut message);
Signature::new(&message, &secret_key) Signature::new(&message, 0, &secret_key)
} }
} }

View File

@ -5,7 +5,7 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "v0.3.0" } bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "0.5.2" }
hashing = { path = "../hashing" } hashing = { path = "../hashing" }
hex = "0.3" hex = "0.3"
serde = "1.0" serde = "1.0"

View File

@ -27,8 +27,13 @@ impl AggregateSignature {
/// ///
/// Only returns `true` if the set of keys in the `AggregatePublicKey` match the set of keys /// Only returns `true` if the set of keys in the `AggregatePublicKey` match the set of keys
/// that signed the `AggregateSignature`. /// that signed the `AggregateSignature`.
pub fn verify(&self, msg: &[u8], aggregate_public_key: &AggregatePublicKey) -> bool { pub fn verify(
self.0.verify(msg, aggregate_public_key) &self,
msg: &[u8],
domain: u64,
aggregate_public_key: &AggregatePublicKey,
) -> bool {
self.0.verify(msg, domain, aggregate_public_key)
} }
} }
@ -73,7 +78,7 @@ mod tests {
let keypair = Keypair::random(); let keypair = Keypair::random();
let mut original = AggregateSignature::new(); let mut original = AggregateSignature::new();
original.add(&Signature::new(&[42, 42], &keypair.sk)); original.add(&Signature::new(&[42, 42], 0, &keypair.sk));
let bytes = ssz_encode(&original); let bytes = ssz_encode(&original);
let (decoded, _) = AggregateSignature::ssz_decode(&bytes, 0).unwrap(); let (decoded, _) = AggregateSignature::ssz_decode(&bytes, 0).unwrap();

View File

@ -1,5 +1,4 @@
extern crate bls_aggregates; extern crate bls_aggregates;
extern crate hashing;
extern crate ssz; extern crate ssz;
mod aggregate_signature; mod aggregate_signature;
@ -16,37 +15,29 @@ pub use crate::signature::Signature;
pub use self::bls_aggregates::AggregatePublicKey; pub use self::bls_aggregates::AggregatePublicKey;
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 97; pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96;
use hashing::hash;
use ssz::ssz_encode; use ssz::ssz_encode;
use std::default::Default;
fn extend_if_needed(hash: &mut Vec<u8>) {
// NOTE: bls_aggregates crate demands 48 bytes, this may be removed as we get closer to production
hash.resize(48, Default::default())
}
/// For some signature and public key, ensure that the signature message was the public key and it /// For some signature and public key, ensure that the signature message was the public key and it
/// was signed by the secret key that corresponds to that public key. /// was signed by the secret key that corresponds to that public key.
pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey) -> bool { pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey) -> bool {
let mut hash = hash(&ssz_encode(pubkey)); // TODO: replace this function with state.validate_proof_of_possession
extend_if_needed(&mut hash); // https://github.com/sigp/lighthouse/issues/239
sig.verify_hashed(&hash, &pubkey) sig.verify(&ssz_encode(pubkey), 0, &pubkey)
} }
// TODO: Update this method
// https://github.com/sigp/lighthouse/issues/239
pub fn create_proof_of_possession(keypair: &Keypair) -> Signature { pub fn create_proof_of_possession(keypair: &Keypair) -> Signature {
let mut hash = hash(&ssz_encode(&keypair.pk)); Signature::new(&ssz_encode(&keypair.pk), 0, &keypair.sk)
extend_if_needed(&mut hash);
Signature::new_hashed(&hash, &keypair.sk)
} }
pub fn bls_verify_aggregate( pub fn bls_verify_aggregate(
pubkey: &AggregatePublicKey, pubkey: &AggregatePublicKey,
message: &[u8], message: &[u8],
signature: &AggregateSignature, signature: &AggregateSignature,
_domain: u64, domain: u64,
) -> bool { ) -> bool {
// TODO: add domain signature.verify(message, domain, pubkey)
signature.verify(message, pubkey)
} }

View File

@ -14,24 +14,34 @@ pub struct Signature(RawSignature);
impl Signature { impl Signature {
/// Instantiate a new Signature from a message and a SecretKey. /// Instantiate a new Signature from a message and a SecretKey.
pub fn new(msg: &[u8], sk: &SecretKey) -> Self { pub fn new(msg: &[u8], domain: u64, sk: &SecretKey) -> Self {
Signature(RawSignature::new(msg, sk.as_raw())) Signature(RawSignature::new(msg, domain, sk.as_raw()))
} }
/// Instantiate a new Signature from a message and a SecretKey, where the message has already /// Instantiate a new Signature from a message and a SecretKey, where the message has already
/// been hashed. /// been hashed.
pub fn new_hashed(msg_hashed: &[u8], sk: &SecretKey) -> Self { pub fn new_hashed(x_real_hashed: &[u8], x_imaginary_hashed: &[u8], sk: &SecretKey) -> Self {
Signature(RawSignature::new_hashed(msg_hashed, sk.as_raw())) Signature(RawSignature::new_hashed(
x_real_hashed,
x_imaginary_hashed,
sk.as_raw(),
))
} }
/// Verify the Signature against a PublicKey. /// Verify the Signature against a PublicKey.
pub fn verify(&self, msg: &[u8], pk: &PublicKey) -> bool { pub fn verify(&self, msg: &[u8], domain: u64, pk: &PublicKey) -> bool {
self.0.verify(msg, pk.as_raw()) self.0.verify(msg, domain, pk.as_raw())
} }
/// Verify the Signature against a PublicKey, where the message has already been hashed. /// Verify the Signature against a PublicKey, where the message has already been hashed.
pub fn verify_hashed(&self, msg_hash: &[u8], pk: &PublicKey) -> bool { pub fn verify_hashed(
self.0.verify_hashed(msg_hash, pk.as_raw()) &self,
x_real_hashed: &[u8],
x_imaginary_hashed: &[u8],
pk: &PublicKey,
) -> bool {
self.0
.verify_hashed(x_real_hashed, x_imaginary_hashed, pk.as_raw())
} }
/// Returns the underlying signature. /// Returns the underlying signature.
@ -41,7 +51,9 @@ impl Signature {
/// Returns a new empty signature. /// Returns a new empty signature.
pub fn empty_signature() -> Self { pub fn empty_signature() -> Self {
let empty: Vec<u8> = vec![0; 97]; let mut empty: Vec<u8> = vec![0; 96];
// TODO: Modify the way flags are used (b_flag should not be used for empty_signature in the future)
empty[0] += u8::pow(2, 6);
Signature(RawSignature::from_bytes(&empty).unwrap()) Signature(RawSignature::from_bytes(&empty).unwrap())
} }
} }
@ -85,7 +97,7 @@ mod tests {
pub fn test_ssz_round_trip() { pub fn test_ssz_round_trip() {
let keypair = Keypair::random(); let keypair = Keypair::random();
let original = Signature::new(&[42, 42], &keypair.sk); let original = Signature::new(&[42, 42], 0, &keypair.sk);
let bytes = ssz_encode(&original); let bytes = ssz_encode(&original);
let (decoded, _) = Signature::ssz_decode(&bytes, 0).unwrap(); let (decoded, _) = Signature::ssz_decode(&bytes, 0).unwrap();
@ -99,9 +111,13 @@ mod tests {
let sig_as_bytes: Vec<u8> = sig.as_raw().as_bytes(); let sig_as_bytes: Vec<u8> = sig.as_raw().as_bytes();
assert_eq!(sig_as_bytes.len(), 97); assert_eq!(sig_as_bytes.len(), 96);
for one_byte in sig_as_bytes.iter() { for (i, one_byte) in sig_as_bytes.iter().enumerate() {
assert_eq!(*one_byte, 0); if i == 0 {
assert_eq!(*one_byte, u8::pow(2, 6));
} else {
assert_eq!(*one_byte, 0);
}
} }
} }
} }