referrerpolicy=no-referrer-when-downgrade

polkadot_primitives/v9/
signed.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17use codec::{Decode, DecodeWithMemTracking, Encode};
18use scale_info::TypeInfo;
19
20use alloc::vec::Vec;
21#[cfg(feature = "std")]
22use sp_application_crypto::AppCrypto;
23#[cfg(feature = "std")]
24use sp_keystore::{Error as KeystoreError, KeystorePtr};
25
26use sp_core::RuntimeDebug;
27use sp_runtime::traits::AppVerify;
28
29use super::{SigningContext, ValidatorId, ValidatorIndex, ValidatorSignature};
30
31/// Signed data with signature already verified.
32///
33/// NOTE: This type does not have an Encode/Decode instance, as this would cancel out our
34/// valid signature guarantees. If you need to encode/decode you have to convert into an
35/// `UncheckedSigned` first.
36///
37/// `Signed` can easily be converted into `UncheckedSigned` and conversion back via `into_signed`
38/// enforces a valid signature again.
39#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
40pub struct Signed<Payload, RealPayload = Payload>(UncheckedSigned<Payload, RealPayload>);
41
42impl<Payload, RealPayload> Signed<Payload, RealPayload> {
43	/// Convert back to an unchecked type.
44	pub fn into_unchecked(self) -> UncheckedSigned<Payload, RealPayload> {
45		self.0
46	}
47}
48
49/// Unchecked signed data, can be converted to `Signed` by checking the signature.
50#[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
51pub struct UncheckedSigned<Payload, RealPayload = Payload> {
52	/// The payload is part of the signed data. The rest is the signing context,
53	/// which is known both at signing and at validation.
54	payload: Payload,
55	/// The index of the validator signing this statement.
56	validator_index: ValidatorIndex,
57	/// The signature by the validator of the signed payload.
58	signature: ValidatorSignature,
59	/// This ensures the real payload is tracked at the typesystem level.
60	real_payload: core::marker::PhantomData<RealPayload>,
61}
62
63impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> Signed<Payload, RealPayload> {
64	/// Used to create a `Signed` from already existing parts.
65	///
66	/// The signature is checked as part of the process.
67	#[cfg(feature = "std")]
68	pub fn new<H: Encode>(
69		payload: Payload,
70		validator_index: ValidatorIndex,
71		signature: ValidatorSignature,
72		context: &SigningContext<H>,
73		key: &ValidatorId,
74	) -> Option<Self> {
75		let s = UncheckedSigned {
76			payload,
77			validator_index,
78			signature,
79			real_payload: std::marker::PhantomData,
80		};
81
82		s.check_signature(context, key).ok()?;
83
84		Some(Self(s))
85	}
86
87	/// Create a new `Signed` by signing data.
88	#[cfg(feature = "std")]
89	pub fn sign<H: Encode>(
90		keystore: &KeystorePtr,
91		payload: Payload,
92		context: &SigningContext<H>,
93		validator_index: ValidatorIndex,
94		key: &ValidatorId,
95	) -> Result<Option<Self>, KeystoreError> {
96		let r = UncheckedSigned::sign(keystore, payload, context, validator_index, key)?;
97		Ok(r.map(Self))
98	}
99
100	/// Try to convert from `UncheckedSigned` by checking the signature.
101	pub fn try_from_unchecked<H: Encode>(
102		unchecked: UncheckedSigned<Payload, RealPayload>,
103		context: &SigningContext<H>,
104		key: &ValidatorId,
105	) -> Result<Self, UncheckedSigned<Payload, RealPayload>> {
106		if unchecked.check_signature(context, key).is_ok() {
107			Ok(Self(unchecked))
108		} else {
109			Err(unchecked)
110		}
111	}
112
113	/// Get a reference to data as unchecked.
114	pub fn as_unchecked(&self) -> &UncheckedSigned<Payload, RealPayload> {
115		&self.0
116	}
117
118	/// Immutably access the payload.
119	#[inline]
120	pub fn payload(&self) -> &Payload {
121		&self.0.payload
122	}
123
124	/// Immutably access the validator index.
125	#[inline]
126	pub fn validator_index(&self) -> ValidatorIndex {
127		self.0.validator_index
128	}
129
130	/// Immutably access the signature.
131	#[inline]
132	pub fn signature(&self) -> &ValidatorSignature {
133		&self.0.signature
134	}
135
136	/// Discard signing data, get the payload
137	#[inline]
138	pub fn into_payload(self) -> Payload {
139		self.0.payload
140	}
141
142	/// Convert `Payload` into `RealPayload`.
143	pub fn convert_payload(&self) -> Signed<RealPayload>
144	where
145		for<'a> &'a Payload: Into<RealPayload>,
146	{
147		Signed(self.0.unchecked_convert_payload())
148	}
149
150	/// Convert `Payload` into some claimed `SuperPayload` if the encoding matches.
151	///
152	/// Succeeds if and only if the super-payload provided actually encodes as
153	/// the expected payload.
154	pub fn convert_to_superpayload<SuperPayload>(
155		self,
156		claimed: SuperPayload,
157	) -> Result<Signed<SuperPayload, RealPayload>, (Self, SuperPayload)>
158	where
159		SuperPayload: EncodeAs<RealPayload>,
160	{
161		if claimed.encode_as() == self.0.payload.encode_as() {
162			Ok(Signed(UncheckedSigned {
163				payload: claimed,
164				validator_index: self.0.validator_index,
165				signature: self.0.signature,
166				real_payload: core::marker::PhantomData,
167			}))
168		} else {
169			Err((self, claimed))
170		}
171	}
172
173	/// Convert `Payload` into some converted `SuperPayload` if the encoding matches.
174	///
175	/// This invokes the closure on the current payload, which is irreversible.
176	///
177	/// Succeeds if and only if the super-payload provided actually encodes as
178	/// the expected payload.
179	pub fn convert_to_superpayload_with<F, SuperPayload>(
180		self,
181		convert: F,
182	) -> Result<Signed<SuperPayload, RealPayload>, SuperPayload>
183	where
184		F: FnOnce(Payload) -> SuperPayload,
185		SuperPayload: EncodeAs<RealPayload>,
186	{
187		let expected_encode_as = self.0.payload.encode_as();
188		let converted = convert(self.0.payload);
189		if converted.encode_as() == expected_encode_as {
190			Ok(Signed(UncheckedSigned {
191				payload: converted,
192				validator_index: self.0.validator_index,
193				signature: self.0.signature,
194				real_payload: core::marker::PhantomData,
195			}))
196		} else {
197			Err(converted)
198		}
199	}
200}
201
202// We can't bound this on `Payload: Into<RealPayload>` because that conversion consumes
203// the payload, and we don't want that. We can't bound it on `Payload: AsRef<RealPayload>`
204// because there's no blanket impl of `AsRef<T> for T`. In the end, we just invent our
205// own trait which does what we need: EncodeAs.
206impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> UncheckedSigned<Payload, RealPayload> {
207	/// Used to create a `UncheckedSigned` from already existing parts.
208	///
209	/// Signature is not checked here, hence `UncheckedSigned`.
210	#[cfg(feature = "std")]
211	pub fn new(
212		payload: Payload,
213		validator_index: ValidatorIndex,
214		signature: ValidatorSignature,
215	) -> Self {
216		Self { payload, validator_index, signature, real_payload: std::marker::PhantomData }
217	}
218
219	/// Check signature and convert to `Signed` if successful.
220	pub fn try_into_checked<H: Encode>(
221		self,
222		context: &SigningContext<H>,
223		key: &ValidatorId,
224	) -> Result<Signed<Payload, RealPayload>, Self> {
225		Signed::try_from_unchecked(self, context, key)
226	}
227
228	/// Immutably access the payload.
229	#[inline]
230	pub fn unchecked_payload(&self) -> &Payload {
231		&self.payload
232	}
233
234	/// Immutably access the validator index.
235	#[inline]
236	pub fn unchecked_validator_index(&self) -> ValidatorIndex {
237		self.validator_index
238	}
239
240	/// Immutably access the signature.
241	#[inline]
242	pub fn unchecked_signature(&self) -> &ValidatorSignature {
243		&self.signature
244	}
245
246	/// Discard signing data, get the payload
247	#[inline]
248	pub fn unchecked_into_payload(self) -> Payload {
249		self.payload
250	}
251
252	/// Convert `Payload` into `RealPayload`.
253	pub fn unchecked_convert_payload(&self) -> UncheckedSigned<RealPayload>
254	where
255		for<'a> &'a Payload: Into<RealPayload>,
256	{
257		UncheckedSigned {
258			signature: self.signature.clone(),
259			validator_index: self.validator_index,
260			payload: (&self.payload).into(),
261			real_payload: core::marker::PhantomData,
262		}
263	}
264
265	fn payload_data<H: Encode>(payload: &Payload, context: &SigningContext<H>) -> Vec<u8> {
266		// equivalent to (`real_payload`, context).encode()
267		let mut out = payload.encode_as();
268		out.extend(context.encode());
269		out
270	}
271
272	/// Sign this payload with the given context and key, storing the validator index.
273	#[cfg(feature = "std")]
274	fn sign<H: Encode>(
275		keystore: &KeystorePtr,
276		payload: Payload,
277		context: &SigningContext<H>,
278		validator_index: ValidatorIndex,
279		key: &ValidatorId,
280	) -> Result<Option<Self>, KeystoreError> {
281		let data = Self::payload_data(&payload, context);
282		let signature =
283			keystore.sr25519_sign(ValidatorId::ID, key.as_ref(), &data)?.map(|sig| Self {
284				payload,
285				validator_index,
286				signature: sig.into(),
287				real_payload: std::marker::PhantomData,
288			});
289		Ok(signature)
290	}
291
292	/// Validate the payload given the context and public key
293	/// without creating a `Signed` type.
294	pub fn check_signature<H: Encode>(
295		&self,
296		context: &SigningContext<H>,
297		key: &ValidatorId,
298	) -> Result<(), ()> {
299		let data = Self::payload_data(&self.payload, context);
300		if self.signature.verify(data.as_slice(), key) {
301			Ok(())
302		} else {
303			Err(())
304		}
305	}
306
307	/// Sign this payload with the given context and pair.
308	#[cfg(any(feature = "runtime-benchmarks", feature = "std"))]
309	pub fn benchmark_sign<H: Encode>(
310		public: &super::ValidatorId,
311		payload: Payload,
312		context: &SigningContext<H>,
313		validator_index: ValidatorIndex,
314	) -> Self {
315		use sp_application_crypto::RuntimeAppPublic;
316		let data = Self::payload_data(&payload, context);
317		let signature = public.sign(&data).unwrap();
318
319		Self { payload, validator_index, signature, real_payload: core::marker::PhantomData }
320	}
321
322	/// Immutably access the signature.
323	#[cfg(any(feature = "runtime-benchmarks", feature = "std"))]
324	pub fn benchmark_signature(&self) -> ValidatorSignature {
325		self.signature.clone()
326	}
327
328	/// Set the signature. Only should be used for creating testing mocks.
329	#[cfg(feature = "std")]
330	pub fn set_signature(&mut self, signature: ValidatorSignature) {
331		self.signature = signature
332	}
333}
334
335impl<Payload, RealPayload> From<Signed<Payload, RealPayload>>
336	for UncheckedSigned<Payload, RealPayload>
337{
338	fn from(signed: Signed<Payload, RealPayload>) -> Self {
339		signed.0
340	}
341}
342
343/// This helper trait ensures that we can encode `Statement` as `CompactStatement`,
344/// and anything as itself.
345///
346/// This resembles `codec::EncodeLike`, but it's distinct:
347/// `EncodeLike` is a marker trait which asserts at the typesystem level that
348/// one type's encoding is a valid encoding for another type. It doesn't
349/// perform any type conversion when encoding.
350///
351/// This trait, on the other hand, provides a method which can be used to
352/// simultaneously convert and encode one type as another.
353pub trait EncodeAs<T> {
354	/// Convert Self into T, then encode T.
355	///
356	/// This is useful when T is a subset of Self, reducing encoding costs;
357	/// its signature also means that we do not need to clone Self in order
358	/// to retain ownership, as we would if we were to do
359	/// `self.clone().into().encode()`.
360	fn encode_as(&self) -> Vec<u8>;
361}
362
363impl<T: Encode> EncodeAs<T> for T {
364	fn encode_as(&self) -> Vec<u8> {
365		self.encode()
366	}
367}