use alloc::vec::Vec;
use crate::account::AccountId;
use crate::block::{BlockNumber, BlockSignatures, SignatureVerificationError, ValidatorKeys};
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{Felt, Hasher, Word, ZERO};
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct BlockHeader {
version: u32,
prev_block_commitment: Word,
block_num: BlockNumber,
chain_commitment: Word,
account_root: Word,
nullifier_root: Word,
note_root: Word,
tx_commitment: Word,
tx_kernel_commitment: Word,
validator_keys: ValidatorKeys,
fee_parameters: FeeParameters,
timestamp: u32,
sub_commitment: Word,
commitment: Word,
}
impl BlockHeader {
#[allow(clippy::too_many_arguments)]
pub fn new(
version: u32,
prev_block_commitment: Word,
block_num: BlockNumber,
chain_commitment: Word,
account_root: Word,
nullifier_root: Word,
note_root: Word,
tx_commitment: Word,
tx_kernel_commitment: Word,
validator_keys: ValidatorKeys,
fee_parameters: FeeParameters,
timestamp: u32,
) -> Self {
let sub_commitment = Self::compute_sub_commitment(
version,
prev_block_commitment,
chain_commitment,
account_root,
nullifier_root,
tx_commitment,
tx_kernel_commitment,
&validator_keys,
&fee_parameters,
timestamp,
block_num,
);
let commitment = Hasher::merge(&[sub_commitment, note_root]);
Self {
version,
prev_block_commitment,
block_num,
chain_commitment,
account_root,
nullifier_root,
note_root,
tx_commitment,
tx_kernel_commitment,
validator_keys,
fee_parameters,
timestamp,
sub_commitment,
commitment,
}
}
pub fn version(&self) -> u32 {
self.version
}
pub fn commitment(&self) -> Word {
self.commitment
}
pub fn sub_commitment(&self) -> Word {
self.sub_commitment
}
pub fn prev_block_commitment(&self) -> Word {
self.prev_block_commitment
}
pub fn block_num(&self) -> BlockNumber {
self.block_num
}
pub fn block_epoch(&self) -> u16 {
self.block_num.block_epoch()
}
pub fn chain_commitment(&self) -> Word {
self.chain_commitment
}
pub fn account_root(&self) -> Word {
self.account_root
}
pub fn nullifier_root(&self) -> Word {
self.nullifier_root
}
pub fn note_root(&self) -> Word {
self.note_root
}
pub fn validator_keys(&self) -> &ValidatorKeys {
&self.validator_keys
}
pub fn tx_commitment(&self) -> Word {
self.tx_commitment
}
pub fn tx_kernel_commitment(&self) -> Word {
self.tx_kernel_commitment
}
pub fn fee_parameters(&self) -> &FeeParameters {
&self.fee_parameters
}
pub fn timestamp(&self) -> u32 {
self.timestamp
}
pub fn epoch_block_num(&self) -> BlockNumber {
BlockNumber::from_epoch(self.block_epoch())
}
pub(crate) fn validate_against_parent(
&self,
parent: &BlockHeader,
signatures: &BlockSignatures,
) -> Result<(), ParentValidationError> {
let Some(expected_parent_num) = self.block_num().checked_sub(1) else {
return Err(ParentValidationError::GenesisBlockHasNoParent {
parent: parent.block_num(),
});
};
if expected_parent_num != parent.block_num() {
return Err(ParentValidationError::ParentNumberMismatch {
expected: expected_parent_num,
parent: parent.block_num(),
});
}
let expected_prev_commitment = self.prev_block_commitment();
if expected_prev_commitment != parent.commitment() {
return Err(ParentValidationError::ParentCommitmentMismatch {
expected: expected_prev_commitment,
parent: parent.commitment(),
});
}
signatures
.verify_against(self.commitment(), parent.validator_keys())
.map_err(|err| match err {
SignatureVerificationError::SignatureCountMismatch { expected, actual } => {
ParentValidationError::SignatureCountMismatch { expected, actual }
},
SignatureVerificationError::InvalidSignatureAtPosition { position } => {
ParentValidationError::InvalidSignatureAtPosition { position }
},
})?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn compute_sub_commitment(
version: u32,
prev_block_commitment: Word,
chain_commitment: Word,
account_root: Word,
nullifier_root: Word,
tx_commitment: Word,
tx_kernel_commitment: Word,
validator_keys: &ValidatorKeys,
fee_parameters: &FeeParameters,
timestamp: u32,
block_num: BlockNumber,
) -> Word {
let mut elements: Vec<Felt> = Vec::with_capacity(40);
elements.extend_from_slice(prev_block_commitment.as_elements());
elements.extend_from_slice(chain_commitment.as_elements());
elements.extend_from_slice(account_root.as_elements());
elements.extend_from_slice(nullifier_root.as_elements());
elements.extend_from_slice(tx_commitment.as_elements());
elements.extend_from_slice(tx_kernel_commitment.as_elements());
elements.extend(validator_keys.commitment());
elements.extend([block_num.into(), Felt::from(version), Felt::from(timestamp), ZERO]);
elements.extend([
ZERO,
Felt::from(fee_parameters.verification_base_fee()),
fee_parameters.fee_faucet_id().suffix(),
fee_parameters.fee_faucet_id().prefix().as_felt(),
]);
elements.extend([ZERO, ZERO, ZERO, ZERO]);
Hasher::hash_elements(&elements)
}
#[cfg(test)]
pub(crate) fn new_dummy(
block_num: u32,
prev_block_commitment: Word,
validator_keys: ValidatorKeys,
) -> Self {
use crate::block::{BlockBody, FeeParameters};
use crate::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET;
use crate::transaction::OrderedTransactionHeaders;
let body = BlockBody::new_unchecked(
Vec::new(),
Vec::new(),
Vec::new(),
OrderedTransactionHeaders::new_unchecked(Vec::new()),
);
let note_root = body.compute_block_note_tree().root();
let tx_commitment = body.transactions().commitment();
let fee_parameters =
FeeParameters::new(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap(), 500);
BlockHeader::new(
0,
prev_block_commitment,
BlockNumber::from(block_num),
Word::empty(),
Word::empty(),
Word::empty(),
note_root,
tx_commitment,
Word::empty(),
validator_keys,
fee_parameters,
0,
)
}
}
impl Serializable for BlockHeader {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
let Self {
version,
prev_block_commitment,
block_num,
chain_commitment,
account_root,
nullifier_root,
note_root,
tx_commitment,
tx_kernel_commitment,
validator_keys,
fee_parameters,
timestamp,
sub_commitment: _,
commitment: _,
} = self;
version.write_into(target);
prev_block_commitment.write_into(target);
block_num.write_into(target);
chain_commitment.write_into(target);
account_root.write_into(target);
nullifier_root.write_into(target);
note_root.write_into(target);
tx_commitment.write_into(target);
tx_kernel_commitment.write_into(target);
validator_keys.write_into(target);
fee_parameters.write_into(target);
timestamp.write_into(target);
}
}
impl Deserializable for BlockHeader {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let version = source.read()?;
let prev_block_commitment = source.read()?;
let block_num = source.read()?;
let chain_commitment = source.read()?;
let account_root = source.read()?;
let nullifier_root = source.read()?;
let note_root = source.read()?;
let tx_commitment = source.read()?;
let tx_kernel_commitment = source.read()?;
let validator_keys = source.read()?;
let fee_parameters = source.read()?;
let timestamp = source.read()?;
Ok(Self::new(
version,
prev_block_commitment,
block_num,
chain_commitment,
account_root,
nullifier_root,
note_root,
tx_commitment,
tx_kernel_commitment,
validator_keys,
fee_parameters,
timestamp,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FeeParameters {
fee_faucet_id: AccountId,
verification_base_fee: u32,
}
impl FeeParameters {
pub fn new(fee_faucet_id: AccountId, verification_base_fee: u32) -> Self {
Self { fee_faucet_id, verification_base_fee }
}
pub fn fee_faucet_id(&self) -> AccountId {
self.fee_faucet_id
}
pub fn verification_base_fee(&self) -> u32 {
self.verification_base_fee
}
}
impl Serializable for FeeParameters {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.fee_faucet_id.write_into(target);
self.verification_base_fee.write_into(target);
}
}
impl Deserializable for FeeParameters {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let fee_faucet_id = source.read()?;
let verification_base_fee = source.read()?;
Ok(Self::new(fee_faucet_id, verification_base_fee))
}
}
#[derive(Debug)]
pub(crate) enum ParentValidationError {
SignatureCountMismatch {
expected: usize,
actual: usize,
},
InvalidSignatureAtPosition {
position: usize,
},
ParentNumberMismatch {
expected: BlockNumber,
parent: BlockNumber,
},
ParentCommitmentMismatch {
expected: Word,
parent: Word,
},
GenesisBlockHasNoParent {
parent: BlockNumber,
},
}
#[cfg(test)]
mod tests {
use miden_core::Word;
use miden_crypto::rand::test_utils::rand_value;
use super::*;
use crate::testing::validator_keys::{random_validator_set, sign_all};
#[test]
fn test_serde() {
let chain_commitment = rand_value::<Word>();
let note_root = rand_value::<Word>();
let tx_kernel_commitment = rand_value::<Word>();
let header = BlockHeader::mock(
0,
Some(chain_commitment),
Some(note_root),
&[],
tx_kernel_commitment,
);
let serialized = header.to_bytes();
let deserialized = BlockHeader::read_from_bytes(&serialized).unwrap();
assert_eq!(deserialized, header);
}
fn child_of(parent: &BlockHeader, child_num: u32, next_count: usize) -> BlockHeader {
let (_, next_keys) = random_validator_set(next_count);
BlockHeader::new_dummy(child_num, parent.commitment(), next_keys)
}
#[test]
fn validate_against_parent_accepts_all_signatures() {
let (signers, keys) = random_validator_set(5);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 1, 5);
let signatures = sign_all(&keys, &signers, child.commitment());
child.validate_against_parent(&parent, &signatures).unwrap();
}
#[test]
fn validate_against_parent_accepts_single_validator() {
let (signers, keys) = random_validator_set(1);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 1, 1);
let signatures = sign_all(&keys, &signers, child.commitment());
child.validate_against_parent(&parent, &signatures).unwrap();
}
#[test]
fn validate_against_parent_rejects_incomplete_signatures() {
let (signers, keys) = random_validator_set(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 1, 3);
let signatures =
BlockSignatures::new(alloc::vec![signers[0].sign(child.commitment())]).unwrap();
let result = child.validate_against_parent(&parent, &signatures);
assert!(matches!(
result,
Err(ParentValidationError::SignatureCountMismatch { expected: 3, actual: 1 })
));
}
#[test]
fn validate_against_parent_rejects_signature_count_mismatch() {
let (_, keys) = random_validator_set(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 1, 3);
let (other_signers, other_keys) = random_validator_set(4);
let signatures = sign_all(&other_keys, &other_signers, child.commitment());
let bytes = signatures.to_bytes();
let deserialized = BlockSignatures::read_from_bytes(&bytes).unwrap();
let result = child.validate_against_parent(&parent, &deserialized);
assert!(matches!(
result,
Err(ParentValidationError::SignatureCountMismatch { expected: 3, actual: 4 })
));
}
#[test]
fn validate_against_parent_rejects_uncommitted_signatures() {
let (_, keys) = random_validator_set(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 1, 3);
let (impostor_signers, impostor_keys) = random_validator_set(3);
let signatures = sign_all(&impostor_keys, &impostor_signers, child.commitment());
let result = child.validate_against_parent(&parent, &signatures);
assert!(matches!(result, Err(ParentValidationError::InvalidSignatureAtPosition { .. })));
}
#[test]
fn validate_against_parent_rejects_genesis() {
let (signers, keys) = random_validator_set(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = BlockHeader::new_dummy(0, parent.commitment(), random_validator_set(3).1);
let signatures = sign_all(&keys, &signers, child.commitment());
let result = child.validate_against_parent(&parent, &signatures);
assert!(matches!(result, Err(ParentValidationError::GenesisBlockHasNoParent { .. })));
}
#[test]
fn validate_against_parent_rejects_wrong_parent_number() {
let (signers, keys) = random_validator_set(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 2, 3);
let signatures = sign_all(&keys, &signers, child.commitment());
let result = child.validate_against_parent(&parent, &signatures);
assert!(matches!(result, Err(ParentValidationError::ParentNumberMismatch { .. })));
}
#[test]
fn validate_against_parent_rejects_wrong_parent_commitment() {
let (signers, keys) = random_validator_set(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = BlockHeader::new_dummy(1, Word::empty(), random_validator_set(3).1);
let signatures = sign_all(&keys, &signers, child.commitment());
let result = child.validate_against_parent(&parent, &signatures);
assert!(matches!(result, Err(ParentValidationError::ParentCommitmentMismatch { .. })));
}
}