Decentralized Identity Provider (DIP) provider consumer pallet
This pallet is a core component of the Decentralized Identity Provider protocol. It enables entities with an identity on a connected Substrate-based chain (provider) to use those identities on the chain this pallet is deployed (consumers) without requiring those entities to set up a new identity locally. A consumer chain is connected to a provider if there is a way for the consumer chain to verify state proofs about parts of the state of the provider chain.
A cross-chain transaction with DIP assumes the entity submitting the transaction has already generated a cross-chain identity commitment on the provider chain, by interacting with the DIP provider pallet on the provider chain.
With a generated identity commitment, a cross-chain transaction flow for a generic entity A
works as follows:
A
generates a state proof proving the state of the identity commitment on the provider chain.A
generates any additional information required for an identity proof to be successfully verified by the consumer runtime.A
, using their accountAccC
on the consumer chain, calls thedispatch_as
extrinsic by providing its identifier on the provider chain, the generated proof, and theCall
to be dispatched on the consumer chain.- This pallet verifies if the proof is correct, if not it returns an error.
- This pallet dispatches the provided
Call
with a new origin created by this pallet, returning any errors the dispatch action returns. The origin contains the information revealed in the proof, the identifier of the acting subject and the accountAccC
dispatching the transaction.
The pallet is agnostic over the chain-specific definition of identity proof verifier and identifier, although, when deployed, they must be configured to respect the definition of identity and identity commitment established by the provider this pallet is linked to.
For instance, if the provider establishes that an identity commitment is a Merkle root of a set of public keys, an identity proof for the consumer will most likely be a Merkle proof revealing a subset of those keys. Similarly, if the provider defines an identity commitment as some ZK-commitment, the respective identity proof on the consumer chain will be a ZK-proof verifying the validity of the commitment and therefore of the revealed information.
For identifiers, if the provider establishes that an identifier is a public key, the same definition must be used in the consumer pallet. Other definitions for an identifier, such as a simple integer or a Decentralized Identifier (DID), must also be configured in the same way.
The pallet allows the consumer runtime to define some LocalIdentityInfo
associated with each identifier, which the pallet's proof verifier can access and optionally modify upon proof verification.
Any changes made to the LocalIdentityInfo
will be persisted if the identity proof is verified correctly and the extrinsic executed successfully.
If the consumer does not need to store anything in addition to the information an identity proof conveys, they can use an empty tuple ()
for the local identity info.
Another example could be the use of signatures, which requires a nonce to avoid replay protections.
In this case, a numeric type such as a u64
or a u128
could be used, and increased by the proof verifier when validating each new cross-chain transaction proof.
The Config
trait
Being chain-agnostic, most of the runtime configurations must be passed to the pallet's Config
trait.
Nevertheless, most of the types provided must reflect the definition of identity and identity commitment that the identity provider chain has established.
The trait has the following components:
type DipCallOriginFilter: Contains<RuntimeCallOf<Self>>
: A preliminary filter that checks whether a providedCall
accepts a DIP origin or not. If a call such as a system call does not accept a DIP origin, there is no need to verify the identity proof, hence the execution can bail out early. This does not guarantee that the dispatch call will succeed, but rather than it will mostly not fail with aBadOrigin
error.type DispatchOriginCheck: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin, Success = Self::AccountId>
: The origin check on thedispatch_as
extrinsic to verify that the caller is authorized to call the extrinsic. If successful, the check must return aAccountId
as defined by the consumer runtime.type Identifier: Parameter + MaxEncodedLen
: The type of a subject identifier. This must match the definition ofIdentifier
the identity provider has defined in their deployment of the provider pallet.type LocalIdentityInfo: FullCodec + TypeInfo + MaxEncodedLen
: Any additional information that must be available only to the provider runtime that is required to provide additional context when verifying a cross-chain identity proof.type ProofVerifier: IdentityProofVerifier<Self>
: The core component of this pallet. It takes care of validating an identity proof and optionally update anyLocalIdentityInfo
. It also defines, via its associated type, the structure of the identity proof that must be passed to thedispatch_as
extrinsic. Although not directly, the proof structure depends on the information that goes into the identity commitment on the provider chain, as that defines what information can be revealed as part of the commitment proof. Additional info to satisfy requirements according to theLocalIdentityInfo
(e.g., a signature) must also be provided in the proof.type RuntimeCall: Parameter + Dispatchable<RuntimeOrigin = <Self as Config>::RuntimeOrigin>
: The aggregatedCall
type.type RuntimeOrigin: From<Origin<Self>> + From<<Self as frame_system::Config>::RuntimeOrigin>
: The aggregatedOrigin
type, which must include the origin exposed by this pallet.
Storage
The pallet contains a single storage element, the IdentityEntries
map.
It maps from a subject Identifier
to an instance of LocalIdentityInfo
.
This information is updated by the proof verifier whenever a new cross-chain transaction and its proof is submitted.
Origin
Because the pallet allows other Call
s to be dispatched after an identity proof has been verified, it also exposes a Origin
that can be used for those calls that require indeed a call to be DIP-authorized.
The origin is created after the identity proof has been successfully verified by the proof verifier, and it includes the identifier of the subject, the address of the tx submitter, and the result returned by the proof verifier upon successful verification.
Calls (bullet numbers represent each call's encoded index)
pub fn dispatch_as(origin: OriginFor<T>, identifier: T::Identifier, proof: IdentityProofOf<T>, call: Box<RuntimeCallOf<T>>) -> DispatchResult
: Try to dispatch a new local call only if it passes all the DIP requirements. Specifically, the call will be dispatched if it passes the preliminaryDipCallOriginFilter
and if the proof verifier returns anOk(verification_result)
value. The value is then added to theDipOrigin
and passed down as the origin for the specifiedCall
. If the whole execution terminates successfully, any changes applied to theLocalIdentityInfo
by the proof verifier are persisted to the pallet storage.