use crate::{chain::chain_information, header};
use core::{num::NonZero, time::Duration};
pub struct VerifyConfig<'a> {
pub header: header::HeaderRef<'a>,
pub block_number_bytes: usize,
pub parent_block_header: header::HeaderRef<'a>,
pub now_from_unix_epoch: Duration,
pub slots_per_epoch: NonZero<u64>,
pub parent_block_epoch: Option<chain_information::BabeEpochInformationRef<'a>>,
pub parent_block_next_epoch: chain_information::BabeEpochInformationRef<'a>,
}
#[derive(Debug)]
pub struct VerifySuccess {
pub slot_number: u64,
pub is_primary_slot: bool,
pub epoch_transition_target: Option<chain_information::BabeEpochInformation>,
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum VerifyError {
MissingSeal,
MissingPreRuntimeDigest,
ParentIsntBabeConsensus,
SlotNumberNotIncreasing,
UnexpectedEpochChangeLog,
MissingEpochChangeLog,
#[display("Invalid Babe epoch change found in header: {_0}")]
InvalidBabeParametersChange(chain_information::BabeValidityError),
InvalidAuthorityIndex,
BadPublicKey,
BadSignature,
BadVrfProof,
BadSecondarySlotAuthor,
OverPrimaryClaimThreshold,
ForbiddenSlotType,
NextEpochStartSlotNumberOverflow,
EpochIndexOverflow,
InvalidChainConfiguration(InvalidChainConfiguration),
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum InvalidChainConfiguration {
ParentEpochStartSlotWithBlockMismatch,
NoCurrentEpochButNextEpochNonZero,
NonZeroNextEpochYetHasStartSlot,
NonGenesisBlockNoCurrentEpoch,
}
pub fn verify_header(config: VerifyConfig) -> Result<VerifySuccess, VerifyError> {
let (authority_index, slot_number, is_primary_slot, vrf_output_and_proof) =
match config.header.digest.babe_pre_runtime() {
Some(header::BabePreDigestRef::Primary(digest)) => (
digest.authority_index,
digest.slot_number,
true,
Some((*digest.vrf_output, *digest.vrf_proof)),
),
Some(header::BabePreDigestRef::SecondaryPlain(digest)) => {
(digest.authority_index, digest.slot_number, false, None)
}
Some(header::BabePreDigestRef::SecondaryVRF(digest)) => (
digest.authority_index,
digest.slot_number,
false,
Some((*digest.vrf_output, *digest.vrf_proof)),
),
None => return Err(VerifyError::MissingPreRuntimeDigest),
};
let parent_slot_number = if config.parent_block_header.number != 0 {
let parent_slot_number = match config.parent_block_header.digest.babe_pre_runtime() {
Some(pr) => pr.slot_number(),
None => return Err(VerifyError::ParentIsntBabeConsensus),
};
if slot_number <= parent_slot_number {
return Err(VerifyError::SlotNumberNotIncreasing);
}
Some(parent_slot_number)
} else {
None
};
if let Some(curr) = &config.parent_block_epoch {
if curr.start_slot_number.map_or(true, |epoch_start| {
parent_slot_number.map_or(true, |parent_slot_number| epoch_start > parent_slot_number)
}) {
return Err(VerifyError::InvalidChainConfiguration(
InvalidChainConfiguration::ParentEpochStartSlotWithBlockMismatch,
));
}
} else if config.parent_block_next_epoch.epoch_index != 0 {
return Err(VerifyError::InvalidChainConfiguration(
InvalidChainConfiguration::NoCurrentEpochButNextEpochNonZero,
));
} else if config.parent_block_header.number != 0 {
return Err(VerifyError::InvalidChainConfiguration(
InvalidChainConfiguration::NonGenesisBlockNoCurrentEpoch,
));
}
if (config.parent_block_next_epoch.epoch_index == 0)
!= config.parent_block_next_epoch.start_slot_number.is_none()
{
return Err(VerifyError::InvalidChainConfiguration(
InvalidChainConfiguration::NonZeroNextEpochYetHasStartSlot,
));
}
let block_epoch_info = match (
&config.parent_block_epoch,
config.header.digest.babe_epoch_information().is_some(),
) {
(Some(parent_epoch), false) => parent_epoch,
(None, false) => {
return Err(VerifyError::MissingEpochChangeLog);
}
(Some(_), true)
if config
.parent_block_next_epoch
.start_slot_number
.map_or(true, |n| n <= slot_number) =>
{
&config.parent_block_next_epoch
}
(Some(_), true) => {
return Err(VerifyError::UnexpectedEpochChangeLog);
}
(None, true) => {
&config.parent_block_next_epoch
}
};
let skipped_epochs = if let Some(epoch_start_slot) = block_epoch_info.start_slot_number {
(slot_number - epoch_start_slot) / config.slots_per_epoch } else {
0
};
let block_epoch_index = block_epoch_info
.epoch_index
.checked_add(skipped_epochs)
.ok_or(VerifyError::EpochIndexOverflow)?;
match (
block_epoch_info.allowed_slots,
is_primary_slot,
vrf_output_and_proof,
) {
(_, true, None) => unreachable!(),
(_, true, Some(_)) => {}
(header::BabeAllowedSlots::PrimaryAndSecondaryPlainSlots, false, None) => {}
(header::BabeAllowedSlots::PrimaryAndSecondaryVrfSlots, false, Some(_)) => {}
_ => return Err(VerifyError::ForbiddenSlotType),
}
let seal_signature = match config.header.digest.babe_seal() {
Some(seal) => {
schnorrkel::Signature::from_bytes(seal).map_err(|_| VerifyError::BadSignature)?
}
None => return Err(VerifyError::MissingSeal),
};
let epoch_transition_target =
if let Some((info, maybe_config)) = config.header.digest.babe_epoch_information() {
let start_slot_number = Some(
block_epoch_info
.start_slot_number
.unwrap_or(slot_number)
.checked_add(config.slots_per_epoch.get())
.ok_or(VerifyError::NextEpochStartSlotNumberOverflow)?
.checked_add(
skipped_epochs
.checked_mul(config.slots_per_epoch.get())
.ok_or(VerifyError::NextEpochStartSlotNumberOverflow)?,
)
.ok_or(VerifyError::NextEpochStartSlotNumberOverflow)?,
);
Some(chain_information::BabeEpochInformation {
epoch_index: block_epoch_index
.checked_add(1)
.ok_or(VerifyError::EpochIndexOverflow)?,
start_slot_number,
authorities: info.authorities.map(Into::into).collect(),
randomness: *info.randomness,
c: maybe_config.map_or(block_epoch_info.c, |config| config.c),
allowed_slots: maybe_config.map_or(block_epoch_info.allowed_slots, |config| {
config.allowed_slots
}),
})
} else {
None
};
if let Some(epoch_transition_target) = &epoch_transition_target {
if let Err(err) = epoch_transition_target.validate() {
return Err(VerifyError::InvalidBabeParametersChange(err));
}
}
let pre_seal_hash = {
let mut unsealed_header = config.header;
let _popped = unsealed_header.digest.pop_seal();
debug_assert!(matches!(_popped, Some(header::Seal::Babe(_))));
unsealed_header.hash(config.block_number_bytes)
};
let signing_authority = block_epoch_info
.authorities
.clone()
.nth(usize::try_from(authority_index).map_err(|_| VerifyError::InvalidAuthorityIndex)?)
.ok_or(VerifyError::InvalidAuthorityIndex)?;
let signing_public_key = schnorrkel::PublicKey::from_bytes(signing_authority.public_key)
.map_err(|_| VerifyError::BadPublicKey)?;
signing_public_key
.verify_simple(b"substrate", &pre_seal_hash, &seal_signature)
.map_err(|_| VerifyError::BadSignature)?;
if let Some((vrf_output, vrf_proof)) = vrf_output_and_proof {
let transcript = {
let mut transcript = merlin::Transcript::new(&b"BABE"[..]);
transcript.append_u64(b"slot number", slot_number);
transcript.append_u64(b"current epoch", block_epoch_index);
transcript.append_message(b"chain randomness", &block_epoch_info.randomness[..]);
transcript
};
let vrf_output = schnorrkel::vrf::VRFPreOut::from_bytes(&vrf_output[..]).unwrap();
let vrf_proof = schnorrkel::vrf::VRFProof::from_bytes(&vrf_proof[..]).unwrap();
let (vrf_in_out, _) = signing_public_key
.vrf_verify(transcript, &vrf_output, &vrf_proof)
.map_err(|_| VerifyError::BadVrfProof)?;
if is_primary_slot {
let threshold = calculate_primary_threshold(
block_epoch_info.c,
block_epoch_info.authorities.clone().map(|a| a.weight),
signing_authority.weight,
);
if u128::from_le_bytes(vrf_in_out.make_bytes::<[u8; 16]>(b"substrate-babe-vrf"))
>= threshold
{
return Err(VerifyError::OverPrimaryClaimThreshold);
}
}
} else {
debug_assert!(!is_primary_slot);
}
if !is_primary_slot {
let hash = {
let mut hash = blake2_rfc::blake2b::Blake2b::new(32);
hash.update(block_epoch_info.randomness);
hash.update(&slot_number.to_le_bytes());
hash.finalize()
};
let expected_authority_index = {
let hash = num_bigint::BigUint::from_bytes_be(hash.as_bytes());
let authorities_len = num_bigint::BigUint::from(block_epoch_info.authorities.len());
debug_assert_ne!(block_epoch_info.authorities.len(), 0);
hash % authorities_len
};
if num_traits::cast::ToPrimitive::to_u32(&expected_authority_index)
.map_or(true, |v| v != authority_index)
{
return Err(VerifyError::BadSecondarySlotAuthor);
}
}
Ok(VerifySuccess {
slot_number,
is_primary_slot,
epoch_transition_target,
})
}
macro_rules! gen_calculate_primary_threshold {
($name:ident, $powf:expr) => {
fn $name(
c: (u64, u64),
authorities_weights: impl Iterator<Item = u64>,
authority_weight: u64, ) -> u128 {
use libm as _;
let c = c.0 as f64 / c.1 as f64;
assert!(c.is_finite());
let theta = authority_weight as f64 / authorities_weights.sum::<u64>() as f64;
assert!(theta > 0.0);
let p = num_rational::BigRational::from_float(1f64 - $powf(1f64 - c, theta)).unwrap();
let numer = p.numer().to_biguint().unwrap();
let denom = p.denom().to_biguint().unwrap();
num_traits::cast::ToPrimitive::to_u128(
&((<num_bigint::BigUint as num_traits::One>::one() << 128u32) * numer / denom),
)
.unwrap()
}
};
}
#[cfg(feature = "std")]
gen_calculate_primary_threshold!(calculate_primary_threshold, f64::powf);
#[cfg(not(feature = "std"))]
gen_calculate_primary_threshold!(calculate_primary_threshold, libm::pow);
#[cfg(test)]
mod tests {
use core::iter;
gen_calculate_primary_threshold!(calculate_primary_threshold1, f64::powf);
gen_calculate_primary_threshold!(calculate_primary_threshold2, libm::pow);
#[test]
fn calculate_primary_threshold_tests() {
assert_eq!(
calculate_primary_threshold1((2, 9), [1, 1, 1, 1, 1, 1, 1, 1].into_iter(), 1),
10523572781773471998586657386560225280u128
);
assert_eq!(
calculate_primary_threshold2((2, 9), [1, 1, 1, 1, 1, 1, 1, 1].into_iter(), 1),
10523572781773471998586657386560225280u128
);
assert_eq!(
calculate_primary_threshold1(
(103, 971),
iter::successors(Some(2u64), |n| Some(n + 1)).take(900),
11
),
1032931305829506557190946382938112u128
);
assert_eq!(
calculate_primary_threshold2(
(103, 971),
iter::successors(Some(2u64), |n| Some(n + 1)).take(900),
11
),
1032931305829506557190946382938112u128
);
assert_eq!(
calculate_primary_threshold1(
(89, 91),
iter::successors(Some(2u64), |n| Some(n * 2 - 1)).take(50),
63
),
72686664904329579129208832u128
);
assert_eq!(
calculate_primary_threshold2(
(89, 91),
iter::successors(Some(2u64), |n| Some(n * 2 - 1)).take(50),
63
),
72686664904329579129208832u128
);
}
}