// Copyright 2021 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use application_crypto::AppKey; #[cfg(feature = "std")] use sp_keystore::{CryptoStore, Error as KeystoreError, SyncCryptoStorePtr}; use sp_std::prelude::Vec; use primitives::RuntimeDebug; use runtime_primitives::traits::AppVerify; use super::{SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature}; /// Signed data with signature already verified. /// /// NOTE: This type does not have an Encode/Decode instance, as this would cancel out our /// valid signature guarantees. If you need to encode/decode you have to convert into an /// `UncheckedSigned` first. /// /// `Signed` can easily be converted into `UncheckedSigned` and conversion back via `into_signed` /// enforces a valid signature again. #[derive(Clone, PartialEq, Eq, RuntimeDebug)] pub struct Signed(UncheckedSigned); impl Signed { /// Convert back to an unchecked type. pub fn into_unchecked(self) -> UncheckedSigned { self.0 } } /// Unchecked signed data, can be converted to `Signed` by checking the signature. #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo)] pub struct UncheckedSigned { /// The payload is part of the signed data. The rest is the signing context, /// which is known both at signing and at validation. payload: Payload, /// The index of the validator signing this statement. validator_index: ValidatorIndex, /// The signature by the validator of the signed payload. signature: ValidatorSignature, /// This ensures the real payload is tracked at the typesystem level. real_payload: sp_std::marker::PhantomData, } impl, RealPayload: Encode> Signed { /// Used to create a `Signed` from already existing parts. /// /// The signature is checked as part of the process. #[cfg(feature = "std")] pub fn new( payload: Payload, validator_index: ValidatorIndex, signature: ValidatorSignature, context: &SigningContext, key: &ValidatorId, ) -> Option { let s = UncheckedSigned { payload, validator_index, signature, real_payload: std::marker::PhantomData, }; s.check_signature(context, key).ok()?; Some(Self(s)) } /// Create a new `Signed` by signing data. #[cfg(feature = "std")] pub async fn sign( keystore: &SyncCryptoStorePtr, payload: Payload, context: &SigningContext, validator_index: ValidatorIndex, key: &ValidatorId, ) -> Result, KeystoreError> { let r = UncheckedSigned::sign(keystore, payload, context, validator_index, key).await?; Ok(r.map(Self)) } /// Try to convert from `UncheckedSigned` by checking the signature. pub fn try_from_unchecked( unchecked: UncheckedSigned, context: &SigningContext, key: &ValidatorId, ) -> Result> { if unchecked.check_signature(context, key).is_ok() { Ok(Self(unchecked)) } else { Err(unchecked) } } /// Get a reference to data as unchecked. pub fn as_unchecked(&self) -> &UncheckedSigned { &self.0 } /// Immutably access the payload. #[inline] pub fn payload(&self) -> &Payload { &self.0.payload } /// Immutably access the validator index. #[inline] pub fn validator_index(&self) -> ValidatorIndex { self.0.validator_index } /// Immutably access the signature. #[inline] pub fn signature(&self) -> &ValidatorSignature { &self.0.signature } /// Discard signing data, get the payload #[inline] pub fn into_payload(self) -> Payload { self.0.payload } /// Convert `Payload` into `RealPayload`. pub fn convert_payload(&self) -> Signed where for<'a> &'a Payload: Into, { Signed(self.0.unchecked_convert_payload()) } /// Convert `Payload` into some claimed `SuperPayload` if the encoding matches. /// /// Succeeds if and only if the super-payload provided actually encodes as /// the expected payload. pub fn convert_to_superpayload( self, claimed: SuperPayload, ) -> Result, (Self, SuperPayload)> where SuperPayload: EncodeAs, Payload: Encode, { if claimed.encode_as() == self.0.payload.encode_as() { Ok(Signed(UncheckedSigned { payload: claimed, validator_index: self.0.validator_index, signature: self.0.signature, real_payload: sp_std::marker::PhantomData, })) } else { Err((self, claimed)) } } } // We can't bound this on `Payload: Into` because that conversion consumes // the payload, and we don't want that. We can't bound it on `Payload: AsRef` // because there's no blanket impl of `AsRef for T`. In the end, we just invent our // own trait which does what we need: EncodeAs. impl, RealPayload: Encode> UncheckedSigned { /// Used to create a `UncheckedSigned` from already existing parts. /// /// Signature is not checked here, hence `UncheckedSigned`. #[cfg(feature = "std")] pub fn new( payload: Payload, validator_index: ValidatorIndex, signature: ValidatorSignature, ) -> Self { Self { payload, validator_index, signature, real_payload: std::marker::PhantomData } } /// Check signature and convert to `Signed` if successful. pub fn try_into_checked( self, context: &SigningContext, key: &ValidatorId, ) -> Result, Self> { Signed::try_from_unchecked(self, context, key) } /// Immutably access the payload. #[inline] pub fn unchecked_payload(&self) -> &Payload { &self.payload } /// Immutably access the validator index. #[inline] pub fn unchecked_validator_index(&self) -> ValidatorIndex { self.validator_index } /// Immutably access the signature. #[inline] pub fn unchecked_signature(&self) -> &ValidatorSignature { &self.signature } /// Discard signing data, get the payload #[inline] pub fn unchecked_into_payload(self) -> Payload { self.payload } /// Convert `Payload` into `RealPayload`. pub fn unchecked_convert_payload(&self) -> UncheckedSigned where for<'a> &'a Payload: Into, { UncheckedSigned { signature: self.signature.clone(), validator_index: self.validator_index, payload: (&self.payload).into(), real_payload: sp_std::marker::PhantomData, } } fn payload_data(payload: &Payload, context: &SigningContext) -> Vec { // equivalent to (`real_payload`, context).encode() let mut out = payload.encode_as(); out.extend(context.encode()); out } /// Sign this payload with the given context and key, storing the validator index. #[cfg(feature = "std")] async fn sign( keystore: &SyncCryptoStorePtr, payload: Payload, context: &SigningContext, validator_index: ValidatorIndex, key: &ValidatorId, ) -> Result, KeystoreError> { let data = Self::payload_data(&payload, context); let signature = CryptoStore::sign_with(&**keystore, ValidatorId::ID, &key.into(), &data).await?; let signature = match signature { Some(sig) => sig.try_into().map_err(|_| KeystoreError::KeyNotSupported(ValidatorId::ID))?, None => return Ok(None), }; Ok(Some(Self { payload, validator_index, signature, real_payload: std::marker::PhantomData, })) } /// Validate the payload given the context and public key /// without creating a `Signed` type. pub fn check_signature( &self, context: &SigningContext, key: &ValidatorId, ) -> Result<(), ()> { let data = Self::payload_data(&self.payload, context); if self.signature.verify(data.as_slice(), key) { Ok(()) } else { Err(()) } } /// Sign this payload with the given context and pair. #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] pub fn benchmark_sign( public: &super::ValidatorId, payload: Payload, context: &SigningContext, validator_index: ValidatorIndex, ) -> Self { use application_crypto::RuntimeAppPublic; let data = Self::payload_data(&payload, context); let signature = public.sign(&data).unwrap(); Self { payload, validator_index, signature, real_payload: sp_std::marker::PhantomData } } /// Immutably access the signature. #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] pub fn benchmark_signature(&self) -> ValidatorSignature { self.signature.clone() } /// Set the signature. Only should be used for creating testing mocks. #[cfg(feature = "std")] pub fn set_signature(&mut self, signature: ValidatorSignature) { self.signature = signature } } impl From> for UncheckedSigned { fn from(signed: Signed) -> Self { signed.0 } } /// This helper trait ensures that we can encode `Statement` as `CompactStatement`, /// and anything as itself. /// /// This resembles `parity_scale_codec::EncodeLike`, but it's distinct: /// `EncodeLike` is a marker trait which asserts at the typesystem level that /// one type's encoding is a valid encoding for another type. It doesn't /// perform any type conversion when encoding. /// /// This trait, on the other hand, provides a method which can be used to /// simultaneously convert and encode one type as another. pub trait EncodeAs { /// Convert Self into T, then encode T. /// /// This is useful when T is a subset of Self, reducing encoding costs; /// its signature also means that we do not need to clone Self in order /// to retain ownership, as we would if we were to do /// `self.clone().into().encode()`. fn encode_as(&self) -> Vec; } impl EncodeAs for T { fn encode_as(&self) -> Vec { self.encode() } }