Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Enable signature verification in background task #10946

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1d9df92
Enable signature verification in background task
librelois Nov 23, 2021
9a73a44
optimize : spawn all background checks before extrinsics execution
librelois Nov 29, 2021
77a767e
Merge branch 'master' into elois-background-sig-verif
librelois Dec 16, 2021
1ded1ba
Merge branch 'master' into elois-background-sig-verif
librelois Feb 28, 2022
9b974d8
fix warning: fn execute_extrinsics_with_book_keeping not used
librelois Feb 28, 2022
37fcf55
Merge branch 'master' into elois-background-sig-verif
librelois Apr 6, 2022
a222e60
fmt
librelois Apr 6, 2022
80baeb1
refactor in a more generic way
librelois Apr 13, 2022
4a02772
add ci job test-frame-executive-features-compile-to-wasm
librelois Apr 13, 2022
9409c4c
apply review suggestions
librelois May 12, 2022
a4765f4
apply review suggestions 2
librelois May 12, 2022
f3f30d0
Merge branch 'master' into elois-background-sig-verif
librelois May 12, 2022
787db84
Merge branch 'master' into elois-background-sig-verif
librelois Jun 2, 2022
0c4b002
rename SignatureBatching -> BackgroundVerifyContext
librelois Jun 2, 2022
f3557c4
Merge branch 'master' into elois-background-sig-verif
librelois Jul 1, 2022
1bde334
fix try runtime build
librelois Jul 21, 2022
f5a85b0
Merge branch 'master' into elois-background-sig-verif
librelois Jul 21, 2022
5727533
fix tests
librelois Jul 21, 2022
625ce06
fmt
librelois Jul 21, 2022
df3636e
fix warnings
librelois Jul 21, 2022
38a5d82
Merge branch 'master' into elois-background-sig-verif
librelois Aug 25, 2022
6062fa1
Merge branch 'master' into elois-background-sig-verif
librelois Sep 19, 2022
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
5 changes: 4 additions & 1 deletion frame/executive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ sp-version = { version = "5.0.0", path = "../../primitives/version" }

[features]
default = ["std"]
with-tracing = ["sp-tracing/with-tracing"]

# Enable signature verification in background task
background-signature-verification = []
std = [
"codec/std",
"frame-support/std",
Expand All @@ -50,3 +52,4 @@ std = [
"sp-tracing/std",
]
try-runtime = ["frame-support/try-runtime", "frame-try-runtime" ]
with-tracing = ["sp-tracing/with-tracing"]
96 changes: 75 additions & 21 deletions frame/executive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,21 @@ use frame_support::{
},
weights::Weight,
};
#[cfg(not(feature = "background-signature-verification"))]
use sp_runtime::traits::Checkable;
#[cfg(feature = "background-signature-verification")]
use sp_runtime::traits::{BackgroundCheckable as Checkable, Checkable as _};
use sp_runtime::{
generic::Digest,
traits::{
self, Applyable, CheckEqual, Checkable, Dispatchable, Header, NumberFor, One,
ValidateUnsigned, Zero,
self, Applyable, CheckEqual, Dispatchable, Header, NumberFor, One, ValidateUnsigned, Zero,
},
transaction_validity::{TransactionSource, TransactionValidity},
ApplyExtrinsicResult,
};
use sp_std::{marker::PhantomData, prelude::*};

pub type CheckedOf<E, C> = <E as Checkable<C>>::Checked;
pub type CheckedOf<E, C> = <E as sp_runtime::traits::Checkable<C>>::Checked;
pub type CallOf<E, C> = <CheckedOf<E, C> as Applyable>::Call;
pub type OriginOf<E, C> = <CallOf<E, C> as Dispatchable>::Origin;

Expand Down Expand Up @@ -241,9 +244,19 @@ where
Self::initialize_block(block.header());
Self::initial_checks(&block);

#[cfg(feature = "background-signature-verification")]
let signature_batching = sp_runtime::BackgroundVerifyContext::start();

let (header, extrinsics) = block.deconstruct();
let checked_extrinsics = Self::check_extrinsics(extrinsics);

Self::execute_checked_extrinsics_with_book_keeping(checked_extrinsics, *header.number());

Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number());
// ensure that all background checks have been completed successfully.
#[cfg(feature = "background-signature-verification")]
if !signature_batching.verify() {
panic!("Signature verification failed.");
}

// run the try-state checks of all pallets.
<AllPalletsWithSystem as TryState<System::BlockNumber>>::try_state(
Expand Down Expand Up @@ -405,6 +418,27 @@ where
}
}

fn check_extrinsics(
extrinsics: Vec<Block::Extrinsic>,
) -> impl Iterator<Item = (CheckedOf<Block::Extrinsic, Context>, Vec<u8>)> {
extrinsics.into_iter().map(|extrinsic| {
let encoded = extrinsic.encode();

#[cfg(feature = "background-signature-verification")]
let checked_extrinsic = extrinsic.background_check(&Default::default());
#[cfg(not(feature = "background-signature-verification"))]
let checked_extrinsic = extrinsic.check(&Default::default());

match checked_extrinsic {
Ok(checked_extrinsic) => (checked_extrinsic, encoded),
Err(e) => {
let err: &'static str = e.into();
panic!("{}", err)
},
}
})
}

/// Actually execute all transitions for `block`.
pub fn execute_block(block: Block) {
sp_io::init_tracing();
Expand All @@ -416,12 +450,18 @@ where
// any initial checks
Self::initial_checks(&block);

let signature_batching = sp_runtime::SignatureBatching::start();
#[cfg(feature = "background-signature-verification")]
let signature_batching = sp_runtime::BackgroundVerifyContext::start();

// execute extrinsics
// check extrinsics in background
let (header, extrinsics) = block.deconstruct();
Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number());
let checked_extrinsics = Self::check_extrinsics(extrinsics);

// execute extrinsics
Self::execute_checked_extrinsics_with_book_keeping(checked_extrinsics, *header.number());

// ensure that all background checks have been completed successfully.
#[cfg(feature = "background-signature-verification")]
if !signature_batching.verify() {
panic!("Signature verification failed.");
}
Expand All @@ -431,13 +471,15 @@ where
}
}

/// Execute given extrinsics and take care of post-extrinsics book-keeping.
fn execute_extrinsics_with_book_keeping(
extrinsics: Vec<Block::Extrinsic>,
/// Execute given checked extrinsics and take care of post-extrinsics book-keeping.
fn execute_checked_extrinsics_with_book_keeping(
checked_extrinsics: impl Iterator<Item = (CheckedOf<Block::Extrinsic, Context>, Vec<u8>)>,
block_number: NumberFor<Block>,
) {
extrinsics.into_iter().for_each(|e| {
if let Err(e) = Self::apply_extrinsic(e) {
checked_extrinsics.for_each(|(checked_extrinsic, unchecked_extrinsic_encoded)| {
if let Err(e) =
Self::apply_checked_extrinsic(checked_extrinsic, unchecked_extrinsic_encoded)
{
let err: &'static str = e.into();
panic!("{}", err)
}
Expand Down Expand Up @@ -485,25 +527,37 @@ where
///
/// This doesn't attempt to validate anything regarding the block, but it builds a list of uxt
/// hashes.
pub fn apply_extrinsic(uxt: Block::Extrinsic) -> ApplyExtrinsicResult {
pub fn apply_extrinsic(unchecked_extrinsic: Block::Extrinsic) -> ApplyExtrinsicResult {
sp_io::init_tracing();
let encoded = uxt.encode();
let encoded_len = encoded.len();
sp_tracing::enter_span!(sp_tracing::info_span!("apply_extrinsic",
ext=?sp_core::hexdisplay::HexDisplay::from(&encoded)));
let unchecked_extrinsic_encoded = unchecked_extrinsic.encode();

// Verify that the signature is good.
let xt = uxt.check(&Default::default())?;
let checked_extrinsic = unchecked_extrinsic.check(&Default::default())?;

Self::apply_checked_extrinsic(checked_extrinsic, unchecked_extrinsic_encoded)
}

/// Actually apply an extrinsic given its `encoded_len`; this doesn't note its hash.
fn apply_checked_extrinsic(
checked_extrinsic: CheckedOf<Block::Extrinsic, Context>,
unchecked_extrinsic_encoded: Vec<u8>,
) -> ApplyExtrinsicResult {
sp_tracing::enter_span!(sp_tracing::info_span!("apply_extrinsic",
ext=?sp_core::hexdisplay::HexDisplay::from(&unchecked_extrinsic_encoded)));

let encoded_len = unchecked_extrinsic_encoded.len();

// We don't need to make sure to `note_extrinsic` only after we know it's going to be
// executed to prevent it from leaking in storage since at this point, it will either
// execute or panic (and revert storage changes).
<frame_system::Pallet<System>>::note_extrinsic(encoded);
<frame_system::Pallet<System>>::note_extrinsic(unchecked_extrinsic_encoded);

// AUDIT: Under no circumstances may this function panic from here onwards.

// Decode parameters and dispatch
let dispatch_info = xt.get_dispatch_info();
let r = Applyable::apply::<UnsignedValidator>(xt, &dispatch_info, encoded_len)?;
let dispatch_info = checked_extrinsic.get_dispatch_info();
let r =
Applyable::apply::<UnsignedValidator>(checked_extrinsic, &dispatch_info, encoded_len)?;

<frame_system::Pallet<System>>::note_applied_extrinsic(&r, dispatch_info);

Expand Down
34 changes: 32 additions & 2 deletions primitives/runtime/src/generic/unchecked_extrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
use crate::{
generic::CheckedExtrinsic,
traits::{
self, Checkable, Extrinsic, ExtrinsicMetadata, IdentifyAccount, MaybeDisplay, Member,
SignedExtension,
self, BackgroundCheckable, Checkable, Extrinsic, ExtrinsicMetadata, IdentifyAccount,
MaybeDisplay, Member, SignedExtension,
},
transaction_validity::{InvalidTransaction, TransactionValidityError},
OpaqueExtrinsic,
Expand Down Expand Up @@ -163,6 +163,36 @@ where
}
}

impl<Address, AccountId, Call, Signature, Extra, Lookup> BackgroundCheckable<Lookup>
for UncheckedExtrinsic<Address, Call, Signature, Extra>
where
Address: Member + MaybeDisplay,
Call: Encode + Member,
Signature: Member + traits::BackgroundVerify,
<Signature as traits::Verify>::Signer: IdentifyAccount<AccountId = AccountId>,
Extra: SignedExtension<AccountId = AccountId>,
AccountId: Member + MaybeDisplay,
Lookup: traits::Lookup<Source = Address, Target = AccountId>,
{
fn background_check(self, lookup: &Lookup) -> Result<Self::Checked, TransactionValidityError> {
Ok(match self.signature {
Some((signed, signature, extra)) => {
let signed = lookup.lookup(signed)?;
let raw_payload = SignedPayload::new(self.function, extra)?;
if !raw_payload
.using_encoded(|payload| signature.background_verify(payload, &signed))
{
return Err(InvalidTransaction::BadProof.into())
}

let (function, extra, _) = raw_payload.deconstruct();
CheckedExtrinsic { signed: Some((signed, extra)), function }
},
None => CheckedExtrinsic { signed: None, function: self.function },
})
}
}

impl<Address, Call, Signature, Extra> ExtrinsicMetadata
for UncheckedExtrinsic<Address, Call, Signature, Extra>
where
Expand Down
52 changes: 42 additions & 10 deletions primitives/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,36 @@ impl Verify for MultiSignature {
}
}

impl crate::traits::BackgroundVerify for MultiSignature {
fn background_verify<L: Lazy<[u8]>>(&self, mut msg: L, signer: &AccountId32) -> bool {
match (self, signer) {
(Self::Ed25519(ref sig), who) =>
if let Ok(pubkey) = ed25519::Public::from_slice(who.as_ref()) {
sig.background_verify(msg, &pubkey)
} else {
false
},
(Self::Sr25519(ref sig), who) =>
if let Ok(pubkey) = sr25519::Public::from_slice(who.as_ref()) {
sig.background_verify(msg, &pubkey)
} else {
false
},
(Self::Ecdsa(ref sig), who) => {
// Unfortunatly, ecdsa signature can't be verified in a background task
// because we don't known the public key.
let m = sp_io::hashing::blake2_256(msg.get());
match sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m) {
Ok(pubkey) =>
&sp_io::hashing::blake2_256(pubkey.as_ref()) ==
<dyn AsRef<[u8; 32]>>::as_ref(who),
_ => false,
}
},
}
}
}

/// Signature verify that can work with any known signature types..
#[derive(Eq, PartialEq, Clone, Default, Encode, Decode, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -912,18 +942,18 @@ pub fn print(print: impl traits::Printable) {
print.print();
}

/// Batching session.
/// Background verification context.
///
/// To be used in runtime only. Outside of runtime, just construct
/// `BatchVerifier` directly.
#[must_use = "`verify()` needs to be called to finish batch signature verification!"]
pub struct SignatureBatching(bool);
#[must_use = "`verify()` needs to be called to finish background verifications!"]
pub struct BackgroundVerifyContext(bool);

impl SignatureBatching {
/// Start new batching session.
impl BackgroundVerifyContext {
/// Start new background verification context.
pub fn start() -> Self {
sp_io::crypto::start_batch_verify();
SignatureBatching(false)
BackgroundVerifyContext(false)
}

/// Verify all signatures submitted during the batching session.
Expand All @@ -934,14 +964,16 @@ impl SignatureBatching {
}
}

impl Drop for SignatureBatching {
impl Drop for BackgroundVerifyContext {
fn drop(&mut self) {
// Sanity check. If user forgets to actually call `verify()`.
//
// We should not panic if the current thread is already panicking,
// because Rust otherwise aborts the process.
if !self.0 && !sp_std::thread::panicking() {
panic!("Signature verification has not been called before `SignatureBatching::drop`")
panic!(
"Signature verification has not been called before `BackgroundVerifyContext::drop`"
)
}
}
}
Expand Down Expand Up @@ -1062,7 +1094,7 @@ mod tests {
));

ext.execute_with(|| {
let _batching = SignatureBatching::start();
let _batching = BackgroundVerifyContext::start();
let dummy = UncheckedFrom::unchecked_from([1; 32]);
let dummy_sig = UncheckedFrom::unchecked_from([1; 64]);
sp_io::crypto::sr25519_verify(&dummy_sig, &Vec::new(), &dummy);
Expand All @@ -1078,7 +1110,7 @@ mod tests {
));

ext.execute_with(|| {
let _batching = SignatureBatching::start();
let _batching = BackgroundVerifyContext::start();
panic!("Hey, I'm an error");
});
}
Expand Down
11 changes: 11 additions & 0 deletions primitives/runtime/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,17 @@ impl<Call: Codec + Sync + Send, Context, Extra> Checkable<Context> for TestXt<Ca
}
}

impl<Call: Codec + Sync + Send, Context, Extra> crate::traits::BackgroundCheckable<Context>
for TestXt<Call, Extra>
{
fn background_check(
self,
_: &Context,
) -> Result<<Self as Checkable<Context>>::Checked, TransactionValidityError> {
Ok(self)
}
}

impl<Call: Codec + Sync + Send, Extra> traits::Extrinsic for TestXt<Call, Extra> {
type Call = Call;
type SignaturePayload = (u64, Extra);
Expand Down
Loading