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_crypto_hashing::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			},
124			// Alias with proof
125			Some(AsPersonInfo::AsPersonalAliasWithProof(_, _, _)) => {
126				T::WeightInfo::as_person_alias_with_proof()
127			},
128			// Personal Identity with proof
129			Some(AsPersonInfo::AsPersonalIdentityWithProof(_, _)) => {
130				T::WeightInfo::as_person_identity_with_proof()
131			},
132			// Personal Identity with existing account
133			Some(AsPersonInfo::AsPersonalIdentityWithAccount(_)) => {
134				T::WeightInfo::as_person_identity_with_account()
135			},
136		}
137	}
138
139	fn validate(
140		&self,
141		origin: <T as frame_system::Config>::RuntimeOrigin,
142		call: &<T as frame_system::Config>::RuntimeCall,
143		_info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
144		_len: usize,
145		_self_implicit: Self::Implicit,
146		inherited_implication: &impl Encode,
147		_source: TransactionSource,
148	) -> ValidateResult<Self::Val, <T as frame_system::Config>::RuntimeCall> {
149		match &self.0 {
150			Some(AsPersonInfo::AsPersonalAliasWithAccount(nonce)) => {
151				let Some(frame_system::Origin::<T>::Signed(who)) = origin.as_system_ref() else {
152					return Err(InvalidTransaction::BadSigner.into());
153				};
154				let who = who.clone();
155
156				let rev_ca = AccountToAlias::<T>::get(&who).ok_or(InvalidTransaction::BadSigner)?;
157				ensure!(
158					Root::<T>::get(rev_ca.ring)
159						.is_some_and(|ring| ring.revision == rev_ca.revision),
160					InvalidTransaction::BadSigner,
161				);
162
163				let local_origin = Origin::PersonalAlias(rev_ca);
164				let mut origin = origin;
165				origin.set_caller_from(local_origin);
166
167				let ValidNonceInfo { requires, provides } =
168					CheckNonce::<T>::validate_nonce_for_account(&who, *nonce)?;
169				let validity = ValidTransaction { requires, provides, ..Default::default() };
170
171				Ok((validity, Val::UsingAccount(who, *nonce), origin))
172			},
173			Some(AsPersonInfo::AsPersonalIdentityWithAccount(nonce)) => {
174				let Some(frame_system::Origin::<T>::Signed(who)) = origin.as_system_ref() else {
175					return Err(InvalidTransaction::BadSigner.into());
176				};
177				let who = who.clone();
178
179				let id =
180					AccountToPersonalId::<T>::get(&who).ok_or(InvalidTransaction::BadSigner)?;
181				let local_origin = Origin::PersonalIdentity(id);
182				let mut origin = origin;
183				origin.set_caller_from(local_origin);
184
185				let ValidNonceInfo { requires, provides } =
186					CheckNonce::<T>::validate_nonce_for_account(&who, *nonce)?;
187				let validity = ValidTransaction { requires, provides, ..Default::default() };
188
189				Ok((validity, Val::UsingAccount(who, *nonce), origin))
190			},
191			Some(AsPersonInfo::AsPersonalAliasWithProof(proof, ring_index, context)) => {
192				ensure!(
193					matches!(origin.as_system_ref(), Some(frame_system::RawOrigin::None)),
194					InvalidTransaction::BadSigner
195				);
196
197				let Some(Call::<T>::set_alias_account { account, call_valid_at }) =
198					call.is_sub_type()
199				else {
200					return Err(InvalidTransaction::Call.into());
201				};
202
203				let ring = Root::<T>::get(ring_index).ok_or(InvalidTransaction::Call)?;
204				let now = frame_system::Pallet::<T>::block_number();
205				if now < *call_valid_at {
206					return Err(InvalidTransaction::Future.into());
207				}
208				let time_tolerance = Pallet::<T>::account_setup_time_tolerance();
209				if now > call_valid_at.saturating_add(time_tolerance) {
210					return Err(InvalidTransaction::Stale.into());
211				}
212
213				let msg = inherited_implication.using_encoded(sp_io::hashing::blake2_256);
214
215				let alias = T::Crypto::validate(proof, &ring.root, &context[..], &msg[..])
216					.map_err(|_| InvalidTransaction::BadProof)?;
217
218				let rev_ca = RevisedContextualAlias {
219					revision: ring.revision,
220					ring: *ring_index,
221					ca: ContextualAlias { alias, context: *context },
222				};
223
224				// This protects again replay attack.
225				if AccountToAlias::<T>::get(account)
226					.is_some_and(|stored_rev_ca| stored_rev_ca == rev_ca)
227				{
228					return Err(InvalidTransaction::Stale.into());
229				}
230
231				// The extrinsic provides the setup of the account for the alias.
232				let provides = twox_64(&("setup", &rev_ca, &account).encode()[..]);
233				let valid_transaction =
234					ValidTransaction::with_tag_prefix("Ppl:Alias").and_provides(provides).into();
235
236				// We transmute the origin.
237				let local_origin = Origin::PersonalAlias(rev_ca);
238				let mut origin = origin;
239				origin.set_caller_from(local_origin);
240
241				Ok((valid_transaction, Val::UsingProof, origin))
242			},
243			Some(AsPersonInfo::AsPersonalIdentityWithProof(signature, index)) => {
244				ensure!(
245					matches!(origin.as_system_ref(), Some(frame_system::RawOrigin::None)),
246					InvalidTransaction::BadSigner
247				);
248
249				let Some(Call::<T>::set_personal_id_account { account, call_valid_at }) =
250					call.is_sub_type()
251				else {
252					return Err(InvalidTransaction::Call.into());
253				};
254
255				let now = frame_system::Pallet::<T>::block_number();
256				if now < *call_valid_at {
257					return Err(InvalidTransaction::Future.into());
258				}
259				let time_tolerance = Pallet::<T>::account_setup_time_tolerance();
260				if now > call_valid_at.saturating_add(time_tolerance) {
261					return Err(InvalidTransaction::Stale.into());
262				}
263
264				let key = People::<T>::get(index)
265					.map(|record| record.key)
266					.ok_or(InvalidTransaction::BadSigner)?;
267
268				let msg = inherited_implication.using_encoded(sp_io::hashing::blake2_256);
269
270				if !T::Crypto::verify_signature(signature, &msg[..], &key) {
271					return Err(InvalidTransaction::BadProof.into());
272				}
273
274				// This protects again replay attack.
275				if People::<T>::get(index).is_some_and(|record| {
276					record.account.is_some_and(|stored_account| stored_account == *account)
277				}) {
278					return Err(InvalidTransaction::Stale.into());
279				}
280
281				// The extrinsic provides the setup of the account for the personal id.
282				let provides = twox_64(&("setup", index, &account).encode()[..]);
283				let valid_transaction =
284					ValidTransaction::with_tag_prefix("Ppl:Id").and_provides(provides).into();
285
286				// We transmute the origin.
287				let local_origin = Origin::PersonalIdentity(*index);
288				let mut origin = origin;
289				origin.set_caller_from(local_origin);
290
291				Ok((valid_transaction, Val::UsingProof, origin))
292			},
293			None => Ok((ValidTransaction::default(), Val::NotUsing, origin)),
294		}
295	}
296
297	fn prepare(
298		self,
299		val: Self::Val,
300		_origin: &<T as frame_system::Config>::RuntimeOrigin,
301		_call: &<T as frame_system::Config>::RuntimeCall,
302		_info: &DispatchInfoOf<<T as frame_system::Config>::RuntimeCall>,
303		_len: usize,
304	) -> Result<Self::Pre, TransactionValidityError> {
305		match val {
306			Val::UsingAccount(who, nonce) => {
307				CheckNonce::<T>::prepare_nonce_for_account(&who, nonce)?
308			},
309			Val::NotUsing | Val::UsingProof => (),
310		}
311
312		Ok(())
313	}
314}