Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blockchain: change signing schema #29

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/blockchain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
46 changes: 30 additions & 16 deletions common/blockchain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, E = borsh::maybestd::io::Error> = core::result::Result<T, E>;

Expand Down Expand Up @@ -71,18 +71,31 @@ impl<PK: PubKey> Block<PK> {
builder.build()
}

/// Sign the block using provided signer function.
pub fn sign(
&self,
// TODO(mina86): Consider using signature::Signer.
signer: impl FnOnce(&[u8]) -> Result<PK::Signature>,
) -> Result<PK::Signature> {
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<S>(&self, signer: &S) -> PK::Signature
where
S: Signer<Signature = PK::Signature>,
{
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.
Expand Down Expand Up @@ -150,7 +163,7 @@ impl<PK: PubKey> Block<PK> {

#[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=";
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 8 additions & 8 deletions common/blockchain/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PK> {
Expand Down Expand Up @@ -45,12 +45,12 @@ pub struct ChainManager<PK> {
struct PendingBlock<PK> {
/// The block that waits for signatures.
next_block: block::Block<PK>,
/// 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<PK>,
/// Sum of stake of validators who have signed the block.
Expand Down Expand Up @@ -138,7 +138,7 @@ impl<PK: PubKey> ChainManager<PK> {
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,
Expand Down Expand Up @@ -196,7 +196,7 @@ impl<PK: PubKey> ChainManager<PK> {
if pending.signers.contains(&pubkey) {
return Ok(false);
}
if !signature.verify(&pending.hash, &pubkey) {
if !pubkey.verify(&pending.fingerprint[..], signature) {
return Err(AddSignatureError::BadSignature);
}

Expand Down
97 changes: 55 additions & 42 deletions common/blockchain/src/validators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,18 @@ pub trait PubKey:
+ borsh::BorshDeserialize
{
/// Signature corresponding to this public key type.
type Signature: Signature<PubKey = Self>;
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<Signature = Self>;

/// 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
Expand Down Expand Up @@ -57,9 +53,6 @@ impl<PK> Validator<PK> {

#[cfg(test)]
pub(crate) mod test_utils {

use super::*;

/// A mock implementation of a PubKey. Offers no security; intended for
/// tests only.
#[derive(
Expand All @@ -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,
Expand All @@ -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};
Loading