referrerpolicy=no-referrer-when-downgrade

pallet_people/
extension.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! People transaction extensions.
19
20use crate::*;
21use codec::{Decode, DecodeWithMemTracking, Encode};
22use core::fmt;
23use frame_support::{
24	ensure, pallet_prelude::TransactionSource, traits::reality::Context, weights::Weight,
25	CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound,
26};
27use frame_system::{CheckNonce, ValidNonceInfo};
28use scale_info::TypeInfo;
29use sp_core::twox_64;
30use sp_runtime::{
31	traits::{DispatchInfoOf, TransactionExtension, ValidateResult},
32	transaction_validity::{InvalidTransaction, TransactionValidityError, ValidTransaction},
33	Saturating,
34};
35
36/// Information required to transform an origin into a personal alias or personal identity.
37#[derive(
38	Encode, Decode, TypeInfo, EqNoBound, CloneNoBound, PartialEqNoBound, DecodeWithMemTracking,
39)]
40#[scale_info(skip_type_params(T))]
41pub enum AsPersonInfo<T: Config + Send + Sync> {
42	/// The signed origin will be transformed using account to alias.
43	AsPersonalAliasWithAccount(T::Nonce),
44	/// The none origin will be transformed using proof.
45	///
46	/// This can only dispatch the call `set_alias_account`.
47	///
48	/// Replay is only protected against resetting the same account during the tolerance period
49	/// after `call_valid_at` parameter.
50	/// If 2 transaction that set 2 different account are sent for an overlapping validity period,
51	/// then those 2 transactions can be replayed indefinitely for the duration of the overlapping
52	/// period.
53	AsPersonalAliasWithProof(<T::Crypto as GenerateVerifiable>::Proof, RingIndex, Context),
54	/// The none origin will be transformed using signature.
55	///
56	/// This can only dispatch the call `set_personal_id_account`.
57	///
58	/// Replay is only protected against resetting the same account during the tolerance period
59	/// after `call_valid_at` parameter.
60	/// If 2 transaction that set 2 different account are sent for an overlapping validity period,
61	/// then those 2 transactions can be replayed indefinitely for the duration of the overlapping
62	/// period.
63	AsPersonalIdentityWithProof(<T::Crypto as GenerateVerifiable>::Signature, PersonalId),
64	/// The signed origin will be transformed using account to personal id.
65	AsPersonalIdentityWithAccount(T::Nonce),
66}
67
68/// Transaction extension to transform an origin into a personal alias or personal identity.
69#[derive(
70	Encode,
71	Decode,
72	TypeInfo,
73	EqNoBound,
74	CloneNoBound,
75	PartialEqNoBound,
76	DefaultNoBound,
77	DecodeWithMemTracking,
78)]
79#[scale_info(skip_type_params(T))]
80pub struct AsPerson<T: Config + Send + Sync>(Option<AsPersonInfo<T>>);
81
82impl<T: Config + Send + Sync> fmt::Debug for AsPerson<T> {
83	#[cfg(feature = "std")]
84	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85		write!(f, "AsPerson")
86	}
87
88	#[cfg(not(feature = "std"))]
89	fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
90		Ok(())
91	}
92}
93
94impl<T: Config + Send + Sync> AsPerson<T> {
95	pub fn new(explicit: Option<AsPersonInfo<T>>) -> Self {
96		Self(explicit)
97	}
98}
99
100/// Info returned by validate to prepare in the [`AsPerson`] transaction extension.
101pub enum Val<T: Config + Send + Sync> {
102	NotUsing,
103	UsingProof,
104	UsingAccount(T::AccountId, T::Nonce),
105}
106
107impl<T: Config + Send + Sync> TransactionExtension<<T as frame_system::Config>::RuntimeCall>
108	for AsPerson<T>
109{
110	const IDENTIFIER: &'static str = "AsPerson";
111	type Implicit = ();
112
113	type Val = Val<T>;
114	type Pre = ();
115
116	fn weight(&self, _call: &<T as frame_system::Config>::RuntimeCall) -> Weight {
117		match self.0 {
118			// Extension is passthrough
119			None => Weight::zero(),
120			// Alias with existing account
121			Some(AsPersonInfo::AsPersonalAliasWithAccount(_)) =>
122				T::WeightInfo::as_person_alias_with_account(),
123			// Alias with proof
124			Some(AsPersonInfo::AsPersonalAliasWithProof(_, _, _)) =>
125				T::WeightInfo::as_person_alias_with_proof(),
126			// Personal Identity with proof
127			Some(AsPersonInfo::AsPersonalIdentityWithProof(_, _)) =>
128				T::WeightInfo::as_person_identity_with_proof(),
129			// Personal Identity with existing account
130			Some(AsPersonInfo::AsPersonalIdentityWithAccount(_)) =>
131				T::WeightInfo::as_person_identity_with_account(),
132		}
133	}
134
135	fn validate(
136		&self,
137		origin: <T as frame_system::Config>::RuntimeOrigin,
138		call: &<T as frame_system::Config>::RuntimeCall,
139		_info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
140		_len: usize,
141		_self_implicit: Self::Implicit,
142		inherited_implication: &impl Encode,
143		_source: TransactionSource,
144	) -> ValidateResult<Self::Val, <T as frame_system::Config>::RuntimeCall> {
145		match &self.0 {
146			Some(AsPersonInfo::AsPersonalAliasWithAccount(nonce)) => {
147				let Some(frame_system::Origin::<T>::Signed(who)) = origin.as_system_ref() else {
148					return Err(InvalidTransaction::BadSigner.into());
149				};
150				let who = who.clone();
151
152				let rev_ca = AccountToAlias::<T>::get(&who).ok_or(InvalidTransaction::BadSigner)?;
153				ensure!(
154					Root::<T>::get(rev_ca.ring)
155						.is_some_and(|ring| ring.revision == rev_ca.revision),
156					InvalidTransaction::BadSigner,
157				);
158
159				let local_origin = Origin::PersonalAlias(rev_ca);
160				let mut origin = origin;
161				origin.set_caller_from(local_origin);
162
163				let ValidNonceInfo { requires, provides } =
164					CheckNonce::<T>::validate_nonce_for_account(&who, *nonce)?;
165				let validity = ValidTransaction { requires, provides, ..Default::default() };
166
167				Ok((validity, Val::UsingAccount(who, *nonce), origin))
168			},
169			Some(AsPersonInfo::AsPersonalIdentityWithAccount(nonce)) => {
170				let Some(frame_system::Origin::<T>::Signed(who)) = origin.as_system_ref() else {
171					return Err(InvalidTransaction::BadSigner.into());
172				};
173				let who = who.clone();
174
175				let id =
176					AccountToPersonalId::<T>::get(&who).ok_or(InvalidTransaction::BadSigner)?;
177				let local_origin = Origin::PersonalIdentity(id);
178				let mut origin = origin;
179				origin.set_caller_from(local_origin);
180
181				let ValidNonceInfo { requires, provides } =
182					CheckNonce::<T>::validate_nonce_for_account(&who, *nonce)?;
183				let validity = ValidTransaction { requires, provides, ..Default::default() };
184
185				Ok((validity, Val::UsingAccount(who, *nonce), origin))
186			},
187			Some(AsPersonInfo::AsPersonalAliasWithProof(proof, ring_index, context)) => {
188				ensure!(
189					matches!(origin.as_system_ref(), Some(frame_system::RawOrigin::None)),
190					InvalidTransaction::BadSigner
191				);
192
193				let Some(Call::<T>::set_alias_account { account, call_valid_at }) =
194					call.is_sub_type()
195				else {
196					return Err(InvalidTransaction::Call.into());
197				};
198
199				let ring = Root::<T>::get(ring_index).ok_or(InvalidTransaction::Call)?;
200				let now = frame_system::Pallet::<T>::block_number();
201				if now < *call_valid_at {
202					return Err(InvalidTransaction::Future.into());
203				}
204				let time_tolerance = Pallet::<T>::account_setup_time_tolerance();
205				if now > call_valid_at.saturating_add(time_tolerance) {
206					return Err(InvalidTransaction::Stale.into());
207				}
208
209				let msg = inherited_implication.using_encoded(sp_io::hashing::blake2_256);
210
211				let alias = T::Crypto::validate(proof, &ring.root, &context[..], &msg[..])
212					.map_err(|_| InvalidTransaction::BadProof)?;
213
214				let rev_ca = RevisedContextualAlias {
215					revision: ring.revision,
216					ring: *ring_index,
217					ca: ContextualAlias { alias, context: *context },
218				};
219
220				// This protects again replay attack.
221				if AccountToAlias::<T>::get(account)
222					.is_some_and(|stored_rev_ca| stored_rev_ca == rev_ca)
223				{
224					return Err(InvalidTransaction::Stale.into());
225				}
226
227				// The extrinsic provides the setup of the account for the alias.
228				let provides = twox_64(&("setup", &rev_ca, &account).encode()[..]);
229				let valid_transaction =
230					ValidTransaction::with_tag_prefix("Ppl:Alias").and_provides(provides).into();
231
232				// We transmute the origin.
233				let local_origin = Origin::PersonalAlias(rev_ca);
234				let mut origin = origin;
235				origin.set_caller_from(local_origin);
236
237				Ok((valid_transaction, Val::UsingProof, origin))
238			},
239			Some(AsPersonInfo::AsPersonalIdentityWithProof(signature, index)) => {
240				ensure!(
241					matches!(origin.as_system_ref(), Some(frame_system::RawOrigin::None)),
242					InvalidTransaction::BadSigner
243				);
244
245				let Some(Call::<T>::set_personal_id_account { account, call_valid_at }) =
246					call.is_sub_type()
247				else {
248					return Err(InvalidTransaction::Call.into());
249				};
250
251				let now = frame_system::Pallet::<T>::block_number();
252				if now < *call_valid_at {
253					return Err(InvalidTransaction::Future.into());
254				}
255				let time_tolerance = Pallet::<T>::account_setup_time_tolerance();
256				if now > call_valid_at.saturating_add(time_tolerance) {
257					return Err(InvalidTransaction::Stale.into());
258				}
259
260				let key = People::<T>::get(index)
261					.map(|record| record.key)
262					.ok_or(InvalidTransaction::BadSigner)?;
263
264				let msg = inherited_implication.using_encoded(sp_io::hashing::blake2_256);
265
266				if !T::Crypto::verify_signature(signature, &msg[..], &key) {
267					return Err(InvalidTransaction::BadProof.into());
268				}
269
270				// This protects again replay attack.
271				if People::<T>::get(index).is_some_and(|record| {
272					record.account.is_some_and(|stored_account| stored_account == *account)
273				}) {
274					return Err(InvalidTransaction::Stale.into());
275				}
276
277				// The extrinsic provides the setup of the account for the personal id.
278				let provides = twox_64(&("setup", index, &account).encode()[..]);
279				let valid_transaction =
280					ValidTransaction::with_tag_prefix("Ppl:Id").and_provides(provides).into();
281
282				// We transmute the origin.
283				let local_origin = Origin::PersonalIdentity(*index);
284				let mut origin = origin;
285				origin.set_caller_from(local_origin);
286
287				Ok((valid_transaction, Val::UsingProof, origin))
288			},
289			None => Ok((ValidTransaction::default(), Val::NotUsing, origin)),
290		}
291	}
292
293	fn prepare(
294		self,
295		val: Self::Val,
296		_origin: &<T as frame_system::Config>::RuntimeOrigin,
297		_call: &<T as frame_system::Config>::RuntimeCall,
298		_info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
299		_len: usize,
300	) -> Result<Self::Pre, TransactionValidityError> {
301		match val {
302			Val::UsingAccount(who, nonce) =>
303				CheckNonce::<T>::prepare_nonce_for_account(&who, nonce)?,
304			Val::NotUsing | Val::UsingProof => (),
305		}
306
307		Ok(())
308	}
309}