#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;
use ark_serialize::CanonicalSerialize;
use frame_support::pallet_prelude::*;
use frame_support::traits::{FindAuthor, Randomness};
use frame_system::pallet_prelude::BlockNumberFor;
use sp_consensus_randomness_beacon::types::{OpaquePublicKey, OpaqueSignature, RoundNumber};
use sp_core::H256;
use sp_idn_crypto::{
bls12_381::zero_on_g1, drand::compute_round_on_g1, verifier::SignatureVerifier,
};
use sp_idn_traits::{
pulse::{Dispatcher, Pulse as TPulse},
Hashable,
};
use sp_runtime::traits::Verify;
use sp_std::fmt::Debug;
extern crate alloc;
use alloc::{vec, vec::Vec};
pub mod types;
pub mod weights;
pub use weights::*;
pub use types::*;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
const LOG_TARGET: &str = "pallet-randomness-beacon";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::ensure;
use frame_system::pallet_prelude::*;
use sp_runtime::traits::{IdentifyAccount, Verify};
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type WeightInfo: WeightInfo;
type SignatureVerifier: SignatureVerifier;
type MaxSigsPerBlock: Get<u8>;
type Pulse: TPulse
+ Encode
+ Decode
+ Debug
+ Clone
+ TypeInfo
+ PartialEq
+ From<Accumulation>;
type Dispatcher: Dispatcher<Self::Pulse>;
type FallbackRandomness: Randomness<Self::Hash, BlockNumberFor<Self>>;
type Signature: Verify<Signer = Self::AccountIdentifier>
+ Parameter
+ Encode
+ Decode
+ Send
+ Sync;
type AccountIdentifier: IdentifyAccount<AccountId = Self::AccountId>;
type FindAuthor: FindAuthor<Self::AccountId>;
}
#[pallet::storage]
pub type BeaconConfig<T: Config> = StorageValue<_, OpaquePublicKey, OptionQuery>;
#[pallet::storage]
pub type NextRound<T: Config> = StorageValue<_, RoundNumber, ValueQuery>;
#[pallet::storage]
pub type SparseAccumulation<T: Config> = StorageValue<_, Accumulation, OptionQuery>;
#[pallet::storage]
pub(super) type DidUpdate<T: Config> = StorageValue<_, bool, ValueQuery>;
#[pallet::genesis_config]
#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub beacon_pubkey_hex: Vec<u8>,
_phantom: core::marker::PhantomData<T>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
Pallet::<T>::initialize_beacon_pubkey(&self.beacon_pubkey_hex)
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
BeaconConfigSet,
SignatureVerificationSuccess,
}
#[pallet::error]
pub enum Error<T> {
BeaconConfigNotSet,
ExcessiveHeightProvided,
InvalidSignature,
SignatureAlreadyVerified,
SerializationFailed,
StartExpired,
VerificationFailed,
ZeroHeightProvided,
}
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
if !matches!(source, TransactionSource::Local | TransactionSource::InBlock) {
return InvalidTransaction::Call.into();
}
match call {
Call::try_submit_asig { asig, start, end, .. } => {
let next_round = NextRound::<T>::get();
if *start < next_round {
log::info!(
"Invalidating transation early: start = {:?} is less than {:?}",
start,
next_round
);
return InvalidTransaction::Call.into();
}
ValidTransaction::with_tag_prefix("RandomnessBeacon")
.priority(TransactionPriority::MAX)
.and_provides(vec![(b"beacon_pulse", asig, start, end).encode()])
.longevity(5)
.propagate(false)
.build()
},
_ => InvalidTransaction::Call.into(),
}
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
<T as pallet::Config>::WeightInfo::on_finalize()
}
fn on_finalize(n: BlockNumberFor<T>) {
if !DidUpdate::<T>::take() && BeaconConfig::<T>::get().is_some() {
log::error!(target: LOG_TARGET, "Failed to ingest pulses during lifetime of block {:?}", n);
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight((<T as pallet::Config>::WeightInfo::try_submit_asig(
T::MaxSigsPerBlock::get().into())
.saturating_add(
T::Dispatcher::dispatch_weight()),
DispatchClass::Operational
))]
#[allow(clippy::useless_conversion)]
pub fn try_submit_asig(
origin: OriginFor<T>,
asig: OpaqueSignature,
start: RoundNumber,
end: RoundNumber,
signature: T::Signature,
) -> DispatchResult {
ensure_none(origin)?;
let payload = (asig.to_vec().clone(), start, end).encode();
Self::verify_signature(payload, signature)?;
ensure!(!DidUpdate::<T>::exists(), Error::<T>::SignatureAlreadyVerified);
let pk = BeaconConfig::<T>::get().ok_or(Error::<T>::BeaconConfigNotSet)?;
let height = end.saturating_sub(start);
if height == 0 {
ensure!(start == end, Error::<T>::ZeroHeightProvided);
}
ensure!(
height <= T::MaxSigsPerBlock::get() as u64,
Error::<T>::ExcessiveHeightProvided
);
let next_round: RoundNumber = NextRound::<T>::get();
if next_round > 0 {
ensure!(start >= next_round, Error::<T>::StartExpired);
}
Self::verify_beacon_signature(pk, asig, start, end)?;
NextRound::<T>::set(end.saturating_add(1));
let sacc = Accumulation::new(asig, start, end);
SparseAccumulation::<T>::set(Some(sacc.clone()));
DidUpdate::<T>::put(true);
let runtime_pulse = T::Pulse::from(sacc);
T::Dispatcher::dispatch(runtime_pulse);
Self::deposit_event(Event::<T>::SignatureVerificationSuccess);
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::set_beacon_config())]
#[allow(clippy::useless_conversion)]
pub fn set_beacon_config(
origin: OriginFor<T>,
pk: OpaquePublicKey,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
BeaconConfig::<T>::set(Some(pk));
Self::deposit_event(Event::<T>::BeaconConfigSet);
Ok(Pays::No.into())
}
}
}
impl<T: Config> Pallet<T> {
pub fn initialize_beacon_pubkey(beacon_pubkey_hex: &[u8]) {
if !beacon_pubkey_hex.is_empty() {
assert!(<BeaconConfig<T>>::get().is_none(), "Beacon config is already initialized!");
let bytes = hex::decode(beacon_pubkey_hex)
.expect("The beacon public key must be hex-encoded and 96 bytes.");
let bpk: OpaquePublicKey =
bytes.try_into().expect("The beacon public key must be exactly 96 bytes.");
BeaconConfig::<T>::set(Some(bpk));
}
}
fn verify_beacon_signature(
pk: OpaquePublicKey,
asig: OpaqueSignature,
start: RoundNumber,
end: RoundNumber,
) -> DispatchResult {
let mut amsg = zero_on_g1();
for r in start..=end {
let msg = compute_round_on_g1(r).map_err(|_| Error::<T>::SerializationFailed)?;
amsg = (amsg + msg).into();
}
let mut amsg_bytes = Vec::new();
amsg.serialize_compressed(&mut amsg_bytes)
.map_err(|_| Error::<T>::SerializationFailed)?;
T::SignatureVerifier::verify(
pk.as_ref().to_vec(),
asig.clone().as_ref().to_vec(),
amsg_bytes,
)
.map_err(|_| {
log::info!("asig verification failed for rounds: {} - {}", start, end);
Error::<T>::VerificationFailed
})?;
Ok(())
}
fn verify_signature(payload: Vec<u8>, signature: T::Signature) -> DispatchResult {
let digest = <frame_system::Pallet<T>>::digest();
let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
let author_id = T::FindAuthor::find_author(pre_runtime_digests)
.ok_or(DispatchError::Other("No block author found"))?;
ensure!(signature.verify(&payload[..], &author_id), Error::<T>::InvalidSignature);
Ok(())
}
pub fn next_round() -> RoundNumber {
NextRound::<T>::get()
}
pub fn max_rounds() -> u8 {
T::MaxSigsPerBlock::get()
}
}
impl<T: Config> Randomness<T::Hash, BlockNumberFor<T>> for Pallet<T>
where
T::Hash: From<H256>,
{
fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
match SparseAccumulation::<T>::get() {
Some(accumulation) => {
let randomness_hash = accumulation.signature.hash(subject).into();
(randomness_hash, frame_system::Pallet::<T>::block_number())
},
None => {
log::warn!(
target: LOG_TARGET,
"Randomness requested but no sparse accumulation available. Returning fallback values."
);
T::FallbackRandomness::random(subject)
},
}
}
}
sp_api::decl_runtime_apis! {
pub trait RandomnessBeaconApi {
fn next_round() -> sp_consensus_randomness_beacon::types::RoundNumber;
fn max_rounds() -> u8;
fn build_extrinsic(
asig: Vec<u8>,
start: u64,
end: u64,
signature: Vec<u8>,
) -> Block::Extrinsic;
}
}