# Pallet Pass Allows dispatching calls on behalf of a keyless account using an authenticator that resolves alternative signing methods. ## Overview Historically, having an account on the Web3 Ecosystem and using it to send transactions over a network has been as easy as having a private key which can sign those. Now, this _ease_ comes with some tradeoffs. One of the most important is securely storing that key, to support signature operations. Nonetheless, transactions are endorsed by accounts, not keys. A key is merely one means to get an `Account`. This pallet provides mechanisms for an `Account` to exist without necesarily hold a private key as traditionally known. This is done by introducing the concept of an `Authenticator`, very well known in the Web2 Ecosystem. ### Terminology - An `Authenticator` is a **handler** that allows processing `Challenge`s to either gather access to the `Account`, or to register a new `Device` against the `Account`. - A `Challenge` is the process where an `Authenticator` gathers a cryptographically verifiable input from a `Device`, and decides whether to provide access to an `Account`. - The `Gas` is an unique **handler** that enables an `Account` to dispatch calls without it paying fees. - A `Recovery` is a **handler** that allows registering a new `Device` when an `User` loses access to all their `Device`s. - A `Registrar` is a **handler** to create a new `Account`. The rules for registering a new `Account` are defined by an `Origin`. - A `Session` is the result of being granted to use an `Account` via using a `Device` on an `Authenticator`. Can be either: - A short-lived session, where the access is granted a single time via a dispatchable that processes the authentication and dispatches the call, or - A long-lived session, where an ephemeral key that is registered against the pallet and can function as a valid authentication method to gather access to an `Account` meanwhile the `duration` is not exceeded. The `duration` will be at most `MaxDuration`. - An `User` is an entity that owns `Device`s to gather access to an `Account`. `User`s can recover an `Account` whenever a `Recovery` is enabled. Additionally, there are some concepts bound to implementation mechanisms, like: - A **handler** is a structure that implements a trait to enable one of the functions that the pallet must provide. - An `Account` is an `AccountId` derived from a registering process (typically, an username), that can be used to dispatch calls on its behalf. ### Goals This pallet supports four main purposes: - Registering an `Account`, and providing access to it via an `Authenticator`. - Dispatching calls to either unsigned extrinsics that pass the challenge for a given `Authenticator` (we call this short-live `Session`), or signed extrinsics previously registered on behalf of the pallet to control the `Account` for a limited time (we'll call this extended-sessions, and these signing keys `Device`s). - Determining whether calling an extrinsic won't pay fees via the `Gas` handler. - Recovering access to an `Account` via a `Recovery`. ## Interface ### Dispatchable Functions - `register`/`claim`: Given the data requested by the `Registrar`, generates a unique hash for derivating an `Account`. - `account_name`: An unique name for identifying the `Account`. Generally, an username. - `authenticator`: An `Authenticator` registered on the pallet. - `device_id`: An unique identification for the first `Device` attached to the `Account` against which to validate the authentication information. - `maybe_session`: An optional `(ChallengePayload, SessionKey)` - `authenticate`: Opens a long-lived `Session` using a `Device` and an `Authenticator`. Once it's opened, registers a `Session` against the `Account`. - `account_name`: An unique name of the `Account`. Generally, an username. - `authenticator`: An `Authenticator` registered on the pallet. - `device_id`: An descriptor for the `Device` attached to the `Account` against which to validate the authentication information, that decodes to the type of device accepted by the `Authenticator`. - `challenge_payload`: An encoded `BoundedVec<u8, T::MaxPayloadSize>` with the information required by the authenticator to validate the device. - `session_key`: An `AccountId` to identify the `Session`. - `add_device`: Requires being signed by an `AccountId` registered as a valid `Session` for the `Account`. Receives the information of a new device. - `authenticator`: An `Authenticator` registered on the pallet. - `device`: An descriptor for the `Device` attached to the `Account` against which to validate the authentication information, that decodes to the type of device accepted by the `Authenticator`. - `dispatch`: Dispatches a call on behalf of an `Account` if the signer is a valid `Session` or the authentication details are valid. The fees, if any, will be paid by the `Account`, or by the signer in case there's not an `Account` tied to a signed origin. - `call`: A valid `RuntimeCall` that can be dispatched on the runtime. - `maybe_authentication`: An optional `(AccountName, Authenticator, DeviceId, )` tuple, sent to authenticate a device on-the-fly, producing a short-lived session. - `maybe_next_session_key`: An optional `AccountId` for the next `Session` key (`AccountId`) that should be registered on behalf of the `Account`. ## Usage ### Registrar ```rs= pub trait Registrar<AccountId, AccountName> { // Required methods fn is_claimable(account_name: &AccountName, claimer: &AccountId) -> types::RegistrarResult; fn claim(account_name: &AccountName, claimer: &AccountId) -> types::RegistrarResult; fn claimer_pays_fees(account_name: &AccountName, claimer: &AccountId) -> bool; } ``` ### Authenticator ```rs= pub trait Authenticator<AccountId, AccountName, DeviceId> { type Error: Parameter; type Device: Parameter; type Challenge: Parameter; fn register_device( &self, account_name: AccountName, device: &Device, ) -> DeviceId; fn authenticate( &self, account_name: AccountName, device_id: &DeviceId, challenge: &Challenge, ) -> Result<(), Error>; } ``` ### Gas Handling ```rs= pub trait GasHandler<AccountId> { fn pays_fees(account_id: AccountId) -> bool; } ``` ```rs= // ... #[pallet::feeless_if(|origin: &OriginFor<T>, | -> bool { let account_id = Self::authenticate(origin, ...); !T::GasHandler::pays_fees(account_id) })] fn dispatch( origin: OriginFor<T>, call: Box<RuntimeCallFor<T>>, ) -> DispatchResult { // ... } // ... ``` More info: [`#[pallet:feeless_if]`][attr:feeless_if] ```rs= pub struct MembershipGasHandler<T>(PhantomData<T>); impl<T: pallet_communities::Config> GasHandler<AccountIdOf<T>> for MembershipGasHandler<T> { fn pays_fees(account_id: AccountIdOf<T>) -> bool { // bring the membership for this account, if any match T::Memberships::memberships_of(account_id) { Some(membership) => { // check the membership has a fe... } None => false } } } ``` ```rs= impl pallet_pass::Config for Runtime { // ... type GasHandler = MembershipGasHandler<Self>; // ... } ``` ### Prerequisites ## Assumptions ## Related Modules [attr:feeless_if]: https://docs.rs/frame-support/latest/frame_support/pallet_macros/attr.feeless_if.html