use alloc::vec::Vec;
use crate::block::{BlockNumber, BlockSignatures, SignatureVerificationError, ValidatorConfig};
use crate::protocol_config::NextProtocolConfig;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{Felt, Hasher, Word, ZERO};
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct BlockHeader {
version: u8,
timestamp: u32,
block_num: BlockNumber,
prev_block_commitment: Word,
chain_commitment: Word,
account_root: Word,
nullifier_root: Word,
note_root: Word,
tx_commitment: Word,
validator_config: ValidatorConfig,
fee_parameters: FeeParameters,
protocol_config_commitment: Word,
next_protocol_config: Option<NextProtocolConfig>,
sub_commitment: Word,
commitment: Word,
}
impl BlockHeader {
const VERSION_1: u8 = 1;
const NUM_SUB_COMMITMENT_ELEMENTS: usize = 40;
#[allow(clippy::too_many_arguments)]
pub fn new(
prev_block_commitment: Word,
block_num: BlockNumber,
chain_commitment: Word,
account_root: Word,
nullifier_root: Word,
note_root: Word,
tx_commitment: Word,
validator_config: ValidatorConfig,
fee_parameters: FeeParameters,
protocol_config_commitment: Word,
next_protocol_config: Option<NextProtocolConfig>,
timestamp: u32,
) -> Self {
let version = Self::VERSION_1;
let sub_elements = Self::to_sub_elements(
version,
prev_block_commitment,
block_num,
chain_commitment,
account_root,
nullifier_root,
tx_commitment,
&validator_config,
&fee_parameters,
protocol_config_commitment,
next_protocol_config.as_ref(),
timestamp,
);
let sub_commitment = Hasher::hash_elements(&sub_elements);
let commitment = Hasher::merge(&[sub_commitment, note_root]);
Self {
version,
timestamp,
block_num,
prev_block_commitment,
chain_commitment,
account_root,
nullifier_root,
note_root,
tx_commitment,
validator_config,
fee_parameters,
protocol_config_commitment,
next_protocol_config,
sub_commitment,
commitment,
}
}
pub fn version(&self) -> u8 {
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_config(&self) -> &ValidatorConfig {
&self.validator_config
}
pub fn tx_commitment(&self) -> Word {
self.tx_commitment
}
pub fn fee_parameters(&self) -> &FeeParameters {
&self.fee_parameters
}
pub fn protocol_config_commitment(&self) -> Word {
self.protocol_config_commitment
}
pub fn next_protocol_config(&self) -> Option<&NextProtocolConfig> {
self.next_protocol_config.as_ref()
}
pub fn next_protocol_config_commitment(&self) -> Word {
Self::compute_next_protocol_config_commitment(self.next_protocol_config.as_ref())
}
pub fn timestamp(&self) -> u32 {
self.timestamp
}
pub fn epoch_block_num(&self) -> BlockNumber {
BlockNumber::from_epoch(self.block_epoch())
}
pub fn to_elements(&self) -> Vec<Felt> {
let mut elements = Self::to_sub_elements(
self.version,
self.prev_block_commitment,
self.block_num,
self.chain_commitment,
self.account_root,
self.nullifier_root,
self.tx_commitment,
&self.validator_config,
&self.fee_parameters,
self.protocol_config_commitment,
self.next_protocol_config.as_ref(),
self.timestamp,
);
elements.extend_from_slice(self.note_root.as_elements());
elements
}
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_config())
.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 to_sub_elements(
version: u8,
prev_block_commitment: Word,
block_num: BlockNumber,
chain_commitment: Word,
account_root: Word,
nullifier_root: Word,
tx_commitment: Word,
validator_config: &ValidatorConfig,
fee_parameters: &FeeParameters,
protocol_config_commitment: Word,
next_protocol_config: Option<&NextProtocolConfig>,
timestamp: u32,
) -> Vec<Felt> {
let mut elements: Vec<Felt> = Vec::with_capacity(Self::NUM_SUB_COMMITMENT_ELEMENTS);
elements.extend([Felt::from(version), block_num.into(), Felt::from(timestamp), ZERO]);
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(protocol_config_commitment.as_elements());
elements.extend(validator_config.to_commitment());
elements.extend(Self::compute_next_protocol_config_commitment(next_protocol_config));
elements.extend([Felt::from(fee_parameters.verification_base_fee()), ZERO, ZERO, ZERO]);
elements
}
fn compute_next_protocol_config_commitment(
next_protocol_config: Option<&NextProtocolConfig>,
) -> Word {
next_protocol_config.map_or(Word::empty(), NextProtocolConfig::to_commitment)
}
#[cfg(test)]
pub(crate) fn new_dummy(
block_num: u32,
prev_block_commitment: Word,
validator_config: ValidatorConfig,
) -> Self {
use crate::block::{BlockBody, FeeParameters};
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();
BlockHeader::new(
prev_block_commitment,
BlockNumber::from(block_num),
Word::empty(),
Word::empty(),
Word::empty(),
note_root,
tx_commitment,
validator_config,
FeeParameters::new(500),
Word::empty(),
None,
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,
validator_config,
fee_parameters,
protocol_config_commitment,
next_protocol_config,
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);
validator_config.write_into(target);
fee_parameters.write_into(target);
protocol_config_commitment.write_into(target);
next_protocol_config.write_into(target);
timestamp.write_into(target);
}
}
impl Deserializable for BlockHeader {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let version = u8::read_from(source)?;
if version != Self::VERSION_1 {
return Err(DeserializationError::InvalidValue(format!(
"block version is {} but only version {} is supported",
version,
Self::VERSION_1,
)));
}
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 validator_config = source.read()?;
let fee_parameters = source.read()?;
let protocol_config_commitment = source.read()?;
let next_protocol_config = <Option<NextProtocolConfig>>::read_from(source)?;
let timestamp = source.read()?;
Ok(Self::new(
prev_block_commitment,
block_num,
chain_commitment,
account_root,
nullifier_root,
note_root,
tx_commitment,
validator_config,
fee_parameters,
protocol_config_commitment,
next_protocol_config,
timestamp,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FeeParameters {
verification_base_fee: u32,
}
impl FeeParameters {
pub fn new(verification_base_fee: u32) -> Self {
Self { verification_base_fee }
}
pub fn verification_base_fee(&self) -> u32 {
self.verification_base_fee
}
}
impl Serializable for FeeParameters {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
let Self { verification_base_fee } = self;
verification_base_fee.write_into(target);
}
}
impl Deserializable for FeeParameters {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let verification_base_fee = source.read()?;
Ok(Self::new(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 assert_matches::assert_matches;
use miden_core::Word;
use miden_crypto::rand::test_utils::rand_value;
use super::*;
#[test]
fn block_header_deserialization_rejects_unsupported_version() {
let error = BlockHeader::read_from_bytes(&[0]).unwrap_err();
assert_matches!(error, DeserializationError::InvalidValue(message) => {
assert!(message.contains("block version is 0"));
});
}
#[test]
fn test_serde() {
let chain_commitment = rand_value::<Word>();
let note_root = rand_value::<Word>();
let header = BlockHeader::mock(0, Some(chain_commitment), Some(note_root), &[]);
let serialized = header.to_bytes();
let deserialized = BlockHeader::read_from_bytes(&serialized).unwrap();
assert_eq!(deserialized, header);
}
fn with_scheduled_upgrade(header: &BlockHeader) -> BlockHeader {
let next_protocol_config =
NextProtocolConfig::new(BlockNumber::from(42u32), rand_value::<Word>()).unwrap();
BlockHeader::new(
header.prev_block_commitment(),
header.block_num(),
header.chain_commitment(),
header.account_root(),
header.nullifier_root(),
header.note_root(),
header.tx_commitment(),
header.validator_config().clone(),
header.fee_parameters().clone(),
header.protocol_config_commitment(),
Some(next_protocol_config),
header.timestamp(),
)
}
#[test]
fn scheduled_upgrade_round_trips_and_changes_the_commitment() {
let without_upgrade = BlockHeader::mock(0, None, None, &[]);
let with_upgrade = with_scheduled_upgrade(&without_upgrade);
assert_eq!(without_upgrade.next_protocol_config_commitment(), Word::empty());
assert_ne!(with_upgrade.commitment(), without_upgrade.commitment());
let deserialized = BlockHeader::read_from_bytes(&with_upgrade.to_bytes()).unwrap();
assert_eq!(deserialized, with_upgrade);
}
#[test]
fn to_elements_is_the_sub_commitment_preimage_plus_the_note_root() {
let header = BlockHeader::mock(0, None, None, &[]);
let elements = header.to_elements();
let (sub_elements, note_root) = elements.split_at(BlockHeader::NUM_SUB_COMMITMENT_ELEMENTS);
assert_eq!(Hasher::hash_elements(sub_elements), header.sub_commitment());
assert_eq!(note_root, header.note_root().as_elements());
}
fn child_of(parent: &BlockHeader, child_num: u32, next_count: usize) -> BlockHeader {
let (_, next_keys) = ValidatorConfig::random_with_signers(next_count);
BlockHeader::new_dummy(child_num, parent.commitment(), next_keys)
}
#[test]
fn validate_against_parent_accepts_all_signatures() {
let (signers, keys) = ValidatorConfig::random_with_signers(5);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 1, 5);
let signatures = keys.sign_all(&signers, child.commitment());
child.validate_against_parent(&parent, &signatures).unwrap();
}
#[test]
fn validate_against_parent_accepts_single_validator() {
let (signers, keys) = ValidatorConfig::random_with_signers(1);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 1, 1);
let signatures = keys.sign_all(&signers, child.commitment());
child.validate_against_parent(&parent, &signatures).unwrap();
}
#[test]
fn validate_against_parent_rejects_incomplete_signatures() {
let (signers, keys) = ValidatorConfig::random_with_signers(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) = ValidatorConfig::random_with_signers(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 1, 3);
let (other_signers, other_keys) = ValidatorConfig::random_with_signers(4);
let signatures = other_keys.sign_all(&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) = ValidatorConfig::random_with_signers(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 1, 3);
let (impostor_signers, impostor_keys) = ValidatorConfig::random_with_signers(3);
let signatures = impostor_keys.sign_all(&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) = ValidatorConfig::random_with_signers(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = BlockHeader::new_dummy(
0,
parent.commitment(),
ValidatorConfig::random_with_signers(3).1,
);
let signatures = keys.sign_all(&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) = ValidatorConfig::random_with_signers(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child = child_of(&parent, 2, 3);
let signatures = keys.sign_all(&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) = ValidatorConfig::random_with_signers(3);
let parent = BlockHeader::new_dummy(0, Word::empty(), keys.clone());
let child =
BlockHeader::new_dummy(1, Word::empty(), ValidatorConfig::random_with_signers(3).1);
let signatures = keys.sign_all(&signers, child.commitment());
let result = child.validate_against_parent(&parent, &signatures);
assert!(matches!(result, Err(ParentValidationError::ParentCommitmentMismatch { .. })));
}
}