diff --git a/substrate/core/primitives/src/crypto.rs b/substrate/core/primitives/src/crypto.rs index b7c70e6224..6e46793432 100644 --- a/substrate/core/primitives/src/crypto.rs +++ b/substrate/core/primitives/src/crypto.rs @@ -790,6 +790,9 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { root.derive(path, Some(seed)).map_err(|_| SecretStringError::InvalidPath) } + /// Interprets the string `s` in order to generate a key pair. + /// + /// See [`from_string_with_seed`](Self::from_string_with_seed) for more extensive documentation. fn from_string(s: &str, password_override: Option<&str>) -> Result { Self::from_string_with_seed(s, password_override).map(|x| x.0) } diff --git a/substrate/core/sr-primitives/src/lib.rs b/substrate/core/sr-primitives/src/lib.rs index 624e224ef1..4341e3543a 100644 --- a/substrate/core/sr-primitives/src/lib.rs +++ b/substrate/core/sr-primitives/src/lib.rs @@ -255,18 +255,39 @@ impl From for MultiSigner { } } +impl TryFrom for ed25519::Public { + type Error = (); + fn try_from(m: MultiSigner) -> Result { + if let MultiSigner::Ed25519(x) = m { Ok(x) } else { Err(()) } + } +} + impl From for MultiSigner { fn from(x: sr25519::Public) -> Self { MultiSigner::Sr25519(x) } } +impl TryFrom for sr25519::Public { + type Error = (); + fn try_from(m: MultiSigner) -> Result { + if let MultiSigner::Sr25519(x) = m { Ok(x) } else { Err(()) } + } +} + impl From for MultiSigner { fn from(x: ecdsa::Public) -> Self { MultiSigner::Ecdsa(x) } } +impl TryFrom for ecdsa::Public { + type Error = (); + fn try_from(m: MultiSigner) -> Result { + if let MultiSigner::Ecdsa(x) = m { Ok(x) } else { Err(()) } + } +} + #[cfg(feature = "std")] impl std::fmt::Display for MultiSigner { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index fefc1144ac..3b72ff40fa 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -42,7 +42,7 @@ use sr_primitives::curve::PiecewiseLinear; use sr_primitives::transaction_validity::TransactionValidity; use sr_primitives::weights::Weight; use sr_primitives::traits::{ - BlakeTwo256, Block as BlockT, NumberFor, StaticLookup, + self, BlakeTwo256, Block as BlockT, NumberFor, StaticLookup, SaturatedConversion, }; use version::RuntimeVersion; #[cfg(any(feature = "std", test))] @@ -459,6 +459,36 @@ impl finality_tracker::Trait for Runtime { type ReportLatency = ReportLatency; } +impl system::offchain::CreateTransaction for Runtime { + type Public = ::Signer; + type Signature = Signature; + + fn create_transaction>( + call: Call, + public: Self::Public, + account: AccountId, + index: Index, + ) -> Option<(Call, ::SignaturePayload)> { + let period = 1 << 8; + let current_block = System::block_number().saturated_into::(); + let tip = 0; + let extra: SignedExtra = ( + system::CheckVersion::::new(), + system::CheckGenesis::::new(), + system::CheckEra::::from(generic::Era::mortal(period, current_block)), + system::CheckNonce::::from(index), + system::CheckWeight::::new(), + transaction_payment::ChargeTransactionPayment::::from(tip), + Default::default(), + ); + let raw_payload = SignedPayload::new(call, extra).ok()?; + let signature = F::sign(public, &raw_payload)?; + let address = Indices::unlookup(account); + let (call, extra, _) = raw_payload.deconstruct(); + Some((call, (address, signature, extra))) + } +} + construct_runtime!( pub enum Runtime where Block = Block, @@ -669,3 +699,24 @@ impl_runtime_apis! { } } } +#[cfg(test)] +mod tests { + use super::*; + use system::offchain::SubmitSignedTransaction; + + fn is_submit_signed_transaction(_arg: T) where + T: SubmitSignedTransaction< + Runtime, + Call, + Extrinsic=UncheckedExtrinsic, + CreateTransaction=Runtime, + Signer=ImOnlineId, + >, + {} + + #[test] + fn validate_bounds() { + let x = SubmitTransaction::default(); + is_submit_signed_transaction(x); + } +} diff --git a/substrate/srml/im-online/src/lib.rs b/substrate/srml/im-online/src/lib.rs index 202507bef9..82b866d32b 100644 --- a/substrate/srml/im-online/src/lib.rs +++ b/substrate/srml/im-online/src/lib.rs @@ -96,12 +96,6 @@ pub mod sr25519 { mod app_sr25519 { use app_crypto::{app_crypto, key_types::IM_ONLINE, sr25519}; app_crypto!(sr25519, IM_ONLINE); - - impl From for sr_primitives::AnySignature { - fn from(sig: Signature) -> Self { - sr25519::Signature::from(sig).into() - } - } } /// An i'm online keypair using sr25519 as its crypto. @@ -119,12 +113,6 @@ pub mod ed25519 { mod app_ed25519 { use app_crypto::{app_crypto, key_types::IM_ONLINE, ed25519}; app_crypto!(ed25519, IM_ONLINE); - - impl From for sr_primitives::AnySignature { - fn from(sig: Signature) -> Self { - ed25519::Signature::from(sig).into() - } - } } /// An i'm online keypair using ed25519 as its crypto. diff --git a/substrate/srml/system/src/offchain.rs b/substrate/srml/system/src/offchain.rs index 468c88af39..11f7e234f7 100644 --- a/substrate/srml/system/src/offchain.rs +++ b/substrate/srml/system/src/offchain.rs @@ -17,8 +17,8 @@ //! Module helpers for offchain calls. use codec::Encode; -use sr_primitives::app_crypto::RuntimeAppPublic; -use sr_primitives::traits::Extrinsic as ExtrinsicT; +use sr_primitives::app_crypto::{self, RuntimeAppPublic}; +use sr_primitives::traits::{Extrinsic as ExtrinsicT, IdentifyAccount}; /// A trait responsible for signing a payload using given account. pub trait Signer { @@ -28,17 +28,96 @@ pub trait Signer { fn sign(public: Public, payload: &Payload) -> Option; } +/// A `Signer` implementation for any `AppPublic` type. +/// +/// This implementation additionaly supports conversion to/from multi-signature/multi-signer +/// wrappers. +/// If the wrapped crypto doesn't match `AppPublic`s crypto `None` is returned. impl Signer for AppPublic where - AppPublic: RuntimeAppPublic + From, - AppPublic::Signature: Into, + AppPublic: RuntimeAppPublic + + app_crypto::AppPublic + + From<::Generic>, + ::Signature: app_crypto::AppSignature, + Signature: From< + <::Signature as app_crypto::AppSignature>::Generic + >, + Public: rstd::convert::TryInto<::Generic> { fn sign(public: Public, raw_payload: &Payload) -> Option { raw_payload.using_encoded(|payload| { - AppPublic::from(public).sign(&payload).map(Into::into) + let public = public.try_into().ok()?; + AppPublic::from(public).sign(&payload) + .map( + <::Signature as app_crypto::AppSignature> + ::Generic::from + ) + .map(Signature::from) }) } } +/// Creates runtime-specific signed transaction. +pub trait CreateTransaction { + /// A `Public` key representing a particular `AccountId`. + type Public; + /// A `Signature` generated by the `Signer`. + type Signature; + + /// Attempt to create signed extrinsic data that encodes call from given account. + /// + /// Runtime implementation is free to construct the payload to sign and the signature + /// in any way it wants. + /// Returns `None` if signed extrinsic could not be created (either because signing failed + /// or because of any other runtime-specific reason). + fn create_transaction>( + call: Extrinsic::Call, + public: Self::Public, + account: T::AccountId, + nonce: T::Index, + ) -> Option<(Extrinsic::Call, Extrinsic::SignaturePayload)>; +} + +type PublicOf = < + >::CreateTransaction as CreateTransaction< + T, + >::Extrinsic, + > +>::Public; + +/// A trait to sign and submit transactions in offchain calls. +pub trait SubmitSignedTransaction +where + PublicOf: IdentifyAccount + Clone, +{ + /// Unchecked extrinsic type. + type Extrinsic: ExtrinsicT + codec::Encode; + + /// A runtime-specific type to produce signed data for the extrinsic. + type CreateTransaction: CreateTransaction; + + /// A type used to sign transactions created using `CreateTransaction`. + type Signer: Signer< + PublicOf, + >::Signature, + >; + + /// Sign given call and submit it to the transaction pool. + /// + /// Returns `Ok` if the transaction was submitted correctly + /// and `Err` if the key for given `id` was not found or the + /// transaction was rejected from the pool. + fn sign_and_submit(call: impl Into, public: PublicOf) -> Result<(), ()> { + let call = call.into(); + let id = public.clone().into_account(); + let expected = >::account_nonce(&id); + let (call, signature_data) = Self::CreateTransaction + ::create_transaction::(call, public, id, expected) + .ok_or(())?; + let xt = Self::Extrinsic::new(call, Some(signature_data)).ok_or(())?; + runtime_io::submit_transaction(xt.encode()) + } +} + /// A trait to submit unsigned transactions in offchain calls. pub trait SubmitUnsignedTransaction { /// Unchecked extrinsic type. @@ -67,6 +146,19 @@ impl Default for TransactionSubmitter { } } +/// A blanket implementation to simplify creation of transaction signer & submitter in the runtime. +impl SubmitSignedTransaction for TransactionSubmitter where + T: crate::Trait, + C: CreateTransaction, + S: Signer<>::Public, >::Signature>, + E: ExtrinsicT + codec::Encode, + >::Public: IdentifyAccount + Clone, +{ + type Extrinsic = E; + type CreateTransaction = C; + type Signer = S; +} + /// A blanket impl to use the same submitter for usigned transactions as well. impl SubmitUnsignedTransaction for TransactionSubmitter where T: crate::Trait,