From c0c85cdec3d1082711ceca643668eb61044eb230 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Fri, 13 Oct 2023 02:10:59 +0200 Subject: [PATCH] blockchain: change signing schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than signing the block, change the code so that validators sign block_height + block_hash. The advantage of this is that checking malicious signatures doesn’t require having the whole block. This will reduce amount of data contract and fishermen need to store to work. --- common/blockchain/Cargo.toml | 2 +- common/blockchain/src/block.rs | 46 +++++++++----- common/blockchain/src/manager.rs | 16 ++--- common/blockchain/src/validators.rs | 97 ++++++++++++++++------------- 4 files changed, 94 insertions(+), 67 deletions(-) diff --git a/common/blockchain/Cargo.toml b/common/blockchain/Cargo.toml index b3233a9c..881850c6 100644 --- a/common/blockchain/Cargo.toml +++ b/common/blockchain/Cargo.toml @@ -9,11 +9,11 @@ borsh.workspace = true derive_more.workspace = true lib = { workspace = true, features = ["borsh"] } +stdx.workspace = true [dev-dependencies] lib = { workspace = true, features = ["borsh", "test_utils"] } rand.workspace = true -stdx.workspace = true [features] std = [] diff --git a/common/blockchain/src/block.rs b/common/blockchain/src/block.rs index 2b11aa48..c2cbdf29 100644 --- a/common/blockchain/src/block.rs +++ b/common/blockchain/src/block.rs @@ -2,7 +2,7 @@ use lib::hash::CryptoHash; use crate::epoch; use crate::height::{BlockHeight, HostHeight}; -use crate::validators::PubKey; +use crate::validators::{PubKey, Signer}; type Result = core::result::Result; @@ -71,18 +71,31 @@ impl Block { builder.build() } - /// Sign the block using provided signer function. - pub fn sign( - &self, - // TODO(mina86): Consider using signature::Signer. - signer: impl FnOnce(&[u8]) -> Result, - ) -> Result { - borsh::to_vec(self).and_then(|vec| signer(vec.as_slice())) + /// Calculates block’s fingerprint. + /// + /// Fingerprint is a concatenation of block height and block hash. It’s + /// what validators sign when signing a block. + pub(crate) fn calc_fingerprint(&self) -> [u8; 40] { + let mut buf = [0; 40]; + let (height, hash) = stdx::split_array_mut(&mut buf); + *height = u64::from(self.block_height).to_be_bytes(); + *hash = self.calc_hash().0; + buf + } + + /// Signs the block. + pub fn sign(&self, signer: &S) -> PK::Signature + where + S: Signer, + { + let fp = self.calc_fingerprint(); + signer.sign(&fp[..]) } - #[cfg(test)] - fn verify(&self, pk: &PK, signature: &PK::Signature) -> bool { - crate::validators::Signature::verify(signature, &self.calc_hash(), pk) + /// Verifies signature for the block. + pub fn verify(&self, pk: &PK, signature: &PK::Signature) -> bool { + let fp = self.calc_fingerprint(); + pk.verify(&fp[..], signature) } /// Constructs next block. @@ -150,7 +163,7 @@ impl Block { #[test] fn test_block_generation() { - use crate::validators::{MockPubKey, MockSignature}; + use crate::validators::{MockPubKey, MockSignature, MockSigner}; // Generate a genesis block and test it’s behaviour. let genesis_hash = "Zq3s+b7x6R8tKV1iQtByAWqlDMXVVD9tSDOlmuLH7wI="; @@ -179,12 +192,13 @@ fn test_block_generation() { assert_ne!(genesis_hash, block.calc_hash()); let pk = MockPubKey(77); - let signature = - genesis.sign(|msg| Ok(MockSignature::new(msg, pk))).unwrap(); - assert_eq!(MockSignature(1722674425, pk), signature); + let signer = MockSigner(pk); + let signature = genesis.sign(&signer); + assert_eq!(MockSignature(0, 1722674425, pk), signature); assert!(genesis.verify(&pk, &signature)); assert!(!genesis.verify(&MockPubKey(88), &signature)); - assert!(!genesis.verify(&pk, &MockSignature(0, pk))); + assert!(!genesis.verify(&pk, &MockSignature(1, 1722674425, pk))); + assert!(!genesis.verify(&pk, &MockSignature(0, 0, pk))); let mut block = genesis.clone(); block.host_timestamp += 1; diff --git a/common/blockchain/src/manager.rs b/common/blockchain/src/manager.rs index d50d499c..8d1a23a7 100644 --- a/common/blockchain/src/manager.rs +++ b/common/blockchain/src/manager.rs @@ -9,7 +9,7 @@ use lib::hash::CryptoHash; use crate::candidates::Candidates; pub use crate::candidates::UpdateCandidateError; use crate::height::HostHeight; -use crate::validators::{PubKey, Signature}; +use crate::validators::PubKey; use crate::{block, chain, epoch}; pub struct ChainManager { @@ -45,12 +45,12 @@ pub struct ChainManager { struct PendingBlock { /// The block that waits for signatures. next_block: block::Block, - /// Hash of the block. + /// Fingerprint of the block. /// - /// This is what validators are signing. It equals `next_block.calc_hash()` - /// and we’re keeping it as a field to avoid having to hash the block each - /// time. - hash: CryptoHash, + /// This is what validators are signing. It equals + /// `next_block.calc_fingerprint()` and we’re keeping it as a field to avoid + /// having to hash the block each time. + fingerprint: [u8; 40], /// Validators who so far submitted valid signatures for the block. signers: Set, /// Sum of stake of validators who have signed the block. @@ -138,7 +138,7 @@ impl ChainManager { next_epoch, )?; self.pending_block = Some(PendingBlock { - hash: next_block.calc_hash(), + fingerprint: next_block.calc_fingerprint(), next_block, signers: Set::new(), signing_stake: 0, @@ -196,7 +196,7 @@ impl ChainManager { if pending.signers.contains(&pubkey) { return Ok(false); } - if !signature.verify(&pending.hash, &pubkey) { + if !pubkey.verify(&pending.fingerprint[..], signature) { return Err(AddSignatureError::BadSignature); } diff --git a/common/blockchain/src/validators.rs b/common/blockchain/src/validators.rs index d6624159..7d9517e7 100644 --- a/common/blockchain/src/validators.rs +++ b/common/blockchain/src/validators.rs @@ -11,22 +11,18 @@ pub trait PubKey: + borsh::BorshDeserialize { /// Signature corresponding to this public key type. - type Signature: Signature; + type Signature: Clone + borsh::BorshSerialize + borsh::BorshDeserialize; + + /// Verifies the signature for given message. + fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool; } -/// A cryptographic signature. -pub trait Signature: - Clone + borsh::BorshSerialize + borsh::BorshDeserialize -{ - /// Public key type which can verify the signature. - type PubKey: PubKey; - - /// Verifies that the signature of a given hash is correct. - fn verify( - &self, - message: &lib::hash::CryptoHash, - pk: &Self::PubKey, - ) -> bool; +pub trait Signer { + /// Signature created by this signer. + type Signature: Clone + borsh::BorshSerialize + borsh::BorshDeserialize; + + /// Signs given message. + fn sign(&self, message: &[u8]) -> Self::Signature; } /// A validator @@ -57,9 +53,6 @@ impl Validator { #[cfg(test)] pub(crate) mod test_utils { - - use super::*; - /// A mock implementation of a PubKey. Offers no security; intended for /// tests only. #[derive( @@ -76,7 +69,7 @@ pub(crate) mod test_utils { )] pub struct MockPubKey(pub u32); - /// A mock implementation of a Signature. Offers no security; intended for + /// A mock implementation of a Signer. Offers no security; intended for /// tests only. #[derive( Clone, @@ -88,53 +81,73 @@ pub(crate) mod test_utils { Hash, borsh::BorshSerialize, borsh::BorshDeserialize, + derive_more::From, )] - pub struct MockSignature(pub u32, pub MockPubKey); + pub struct MockSigner(pub MockPubKey); + + /// A mock implementation of a signature. Offers no security; intended for + /// tests only. + #[derive( + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + borsh::BorshSerialize, + borsh::BorshDeserialize, + )] + pub struct MockSignature(pub u64, pub u32, pub MockPubKey); impl core::fmt::Debug for MockPubKey { + #[inline] fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(fmt, "⚷{}", self.0) } } + impl core::fmt::Debug for MockSigner { + #[inline] + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.0.fmt(fmt) + } + } + impl core::fmt::Debug for MockSignature { + #[inline] fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(fmt, "Sig({} by {:?})", self.0, self.1) + write!(fmt, "Sig({}/{:x} by {:?})", self.0, self.1, self.2) } } impl super::PubKey for MockPubKey { type Signature = MockSignature; - } - impl MockSignature { - pub fn new(message: &[u8], pk: MockPubKey) -> Self { - Self(Self::hash_message(message), pk) + fn verify(&self, message: &[u8], signature: &Self::Signature) -> bool { + let (height, hash) = split_message(message); + signature.0 == height && signature.1 == hash && &signature.2 == self } + } - fn hash_message(message: &[u8]) -> u32 { - Self::cut_hash(&lib::hash::CryptoHash::digest(message)) - } + impl super::Signer for MockSigner { + type Signature = MockSignature; - fn cut_hash(hash: &lib::hash::CryptoHash) -> u32 { - let hash = hash.into(); - let (head, _) = stdx::split_array_ref::<4, 28, 32>(&hash); - u32::from_be_bytes(*head) + fn sign(&self, message: &[u8]) -> Self::Signature { + let (height, hash) = split_message(message); + MockSignature(height, hash, self.0) } } - impl Signature for MockSignature { - type PubKey = MockPubKey; - - fn verify( - &self, - message: &lib::hash::CryptoHash, - pk: &Self::PubKey, - ) -> bool { - self.0 == Self::cut_hash(message) && &self.1 == pk - } + fn split_message(message: &[u8]) -> (u64, u32) { + let message: &[u8; 40] = message.try_into().unwrap(); + let (height, hash) = stdx::split_array_ref::<8, 32, 40>(message); + let (hash, _) = stdx::split_array_ref::<4, 28, 32>(hash); + let height = u64::from_be_bytes(*height); + let hash = u32::from_be_bytes(*hash); + (height, hash) } } #[cfg(test)] -pub(crate) use test_utils::{MockPubKey, MockSignature}; +pub(crate) use test_utils::{MockPubKey, MockSignature, MockSigner};