Verification
Name | Value |
---|---|
Repository | https://github.com/anagolay/anagolay-chain/tree/main/pallets/verification |
Pallet | Yes |
License | Apache2 |
Registration Fee | 1 IDI |
Verification
Overview​
From Anagolay's point of view, verified creators are the users that have verified on the chain their online identity; being it a domain, subdomain, social account, or repository. This approach was partially pioneered by Keybase and became a trusted way to determine that a specific user or an organization has correct rights to a given resource. However, centralized solution as Keybase force people to trust that it will not go down, shut down, or get sold.
We are creating a transparent and trusted process for verifying internet identities without relying on a centralized solution. After a verification process thanks to which Proof of identity ownership is created, a Claim containing such Proof is cryptographically signed and the Statement containing such claim will be the legal binding of the identity ownership until the verification is valid.
Definition​
We keep records of the verified items and their proofs to know how to handle different types of verification processes and how to store them. There can be any number of Strategies implemented to handle several different verification scenarios. In the following description, we’ll speak of DNS verification, but the same procedure applies similarly to other verification strategies as well.
Alice, the verification holder, requests the verification providing the appropriate context (domain, subdomain…) and action (update DNS TXT record, well-known ACME challenge...), and the request is stored on the chain with Waiting
status. A registration fee is also reserved on Alice's account: she can claim it back later updating the verification status to Failed
when the identity ownership is revoked, or else this will be the bounty that other users can claim if, at any point in time, they verify that Alice’s domain no longer contains the correct DNS TXT and she neglected to update the verification status herself. Meanwhile, Alice has received the instructions for the verification challenge, for example: putting a specific key in a DNS TXT record and being sure it stays there as long as the verification needs to be valid.
Having done so, due to DNS propagation, the process can halt and DoH (DNS over HTTPS) queries can be performed off-chain before the perform_verification
extrinsic is called, because this call will incur transaction costs. When the DNS propagation happened, process can resume. Other verification strategies may be more or less immediate.
Any verifier account, even different from the holder, can call perform_verification
at any time to update the state of the request to Pending
, signaling to the off-chain worker that, on its next execution, the challenge must be verified. If the verification status is already Failed
, however, the call to perform verification will result in an error since the verification must be requested again from the holder in order to pay the registration fee.
At execution of the off-chain worker, the appropriate verification strategy is instantiated, DNSVerificationStrategy
in our case. It performs a call to the DNS resolve provider to verify the presence and the exactness of the aforementioned key. The VerificationRequest
is then updated on chain with the call to a local unsigned extrinsic to store the appropriate status; Success
or Failure
. If the verification fails, the registration fee is attributed to the verifier account, which is the origin of the call to perform verification, in appreciation of the behavior of external actors that validate that VerificationRequest
validity is not expired, or for the holder to claim back the registration fee.
Configuration​
The runtime need to configure the verification pallet as follows:
impl verification::Config for Runtime {
// The overarching event type
type Event = Event;
// The generator used to produce verification keys. The pallet provide [`NaiveVerificationKeyGenerator`] to use by default
type VerificationKeyGenerator = verification::types::NaiveVerificationKeyGenerator<Runtime>;
// The weights generated by runtime benchmarking
type WeightInfo = verification::weights::AnagolayWeight<Runtime>;
// A reservable currency used to reserve the registration fee
type Currency = Balances;
// The amount to reserve as registration fee
const REGISTRATION_FEE: u128 = 1 * UNITS;
// The maximum number of accounts requesting verification of the same context
const MAX_REQUESTS_PER_CONTEXT: u32 = 1000;
}
There are two constants available for configuration:
REGISTRATION_FEE
: allow to specify the bounty for invalidating a verification request. Should be high enough to motivate actors to look for invalid verification request, but not so high to discourage requesting verificationMAX_REQUESTS_PER_CONTEXT
: allow more than one verification request per context which is useful in the scenario where a malicious actor controlling several accounts wants to prevent the rightful verification holder to request verification by doing it beforehand (even if the request fails) and saturating the requests-per-context buffer; should be high enough to discourage such attempt
Also, the standard definitions to allow unsigned local transactions from off-chain worker must be present:
impl frame_system::offchain::SigningTypes for Runtime {
type Public = <Signature as sp_runtime::traits::Verify>::Signer;
type Signature = Signature;
}
impl<C> frame_system::offchain::SendTransactionTypes<C> for Runtime
where
Call: From<C>,
{
type OverarchingCall = Call;
type Extrinsic = UncheckedExtrinsic;
}
Off-chain security​
Since the off-chain worker submits unsigned transactions with signed payload, a local account using the verification pallet account key type (ver!
) must be inserted into production environment either through chain specification or through other means of invoking the extrinsics, like polkadot-js app.
Local account must be whitelisted as only reliable signer, so a good choice is to use a validator (or collator) account. In the example below Alice account is inserted in the keystore in order to sign verification pallet unsigned transactions.
For more information, refer to the off-chain worker how-to guide.
curl --location --request POST 'http://localhost:9933' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc": "2.0",
"method": "author_insertKey",
"params": ["ver!","clip organ olive upper oak void inject side suit toilet stick narrow","0xb48004c6e1625282313b07d1c9950935e86894a2e4f21fb1ffee9854d180c781"],
"id": 1
}'
Please note that in dev environment Alice may be inserted in the keystore through configuration of the service.rs
in order not to repeat this step at every execution, as suggested in the off-chain worker how-to guide.
Storage​
VerificationRequestByAccountIdVerificationContext
provides theVerificationRequest
s indexed by their holderAccountId
andVerificationContext
Types​
Bytes
is an alias forBoundedVec<u8, Get<u32>>
DomainVerificationContext
is an enumeration providing the switch to verify (full URL + breakdown) and implements theVerificationContext
trait- a domain (ex: anagolay.dev)
- a domain with a username (ex: github.com/anagolay)
- a domain with a subdomain (ex: adriano.anagolay.network)
- a domain with a username and a repository (ex: github.com/woss/git-gpg-remote-sign)
VerificationStrategy
is a trait that mimics the behavior of verification strategies on aVerificationContext
trait, providing the following methods:new_request(AccountId, VerificationContext, VerificationAction) -> VerificationRequest
: creates a newVerificationRequest
initialized with the verification keysupports(VerificationContext) -> bool
: defines whether aVerificationContext
is supported or notverify() -> VerificationStatus
: performs an HTTP call to check the required criterion to pass the verification
VerificationKeyGenerator
is a trait that mimics the behavior of a key generator. The default implementation uses an Anagolay workflow to generate a cid out of an identifier (usually the concatenation of some strategy-related information and the verification holder account). However, the pallet configuration allow to define another implementation of this trait so that the key generation can be tweaked. It provide the following method:generate(AccountId, VerificationContext, Vec<u8>) -> Vec<u8>
: produces the verification key out of the input arguments
VerificationStatus
is an enumeration representing the status of the verification:Waiting
Pending
Failure
Success
VerificationRequest
a structure representing the request to verify:context
: one of the supportedVerificationContext
sstatus
: theVerificationStatus
holder
: theAccountId
issuing the requestaction
: an indication of the action that the holder must perform to pass verification, which is the verification strategykey
: aBytes
field containing a challenge string. This is in complete control of the invoked Strategyid
: anOption<Bytes>
the feedback from the holder pointing at the exact place where the verification should happen (TweetId, etc…)
Events​
VerificationRequested(AccountId, VerificationRequest)
produced upon newly requested verification to communicate to the holder the key to use for the agreed action or that the verification is ongoingVerificationSuccessful(AccountId, VerificationRequest)
produced upon successful verificationVerificationFailed(AccountId, AccountId, VerificationRequest, Bytes)
produced upon failed verification, intended to be received by both the verifier and the holder, also provides a textual explaination of what went wrong
Errors​
VerificationAlreadyIssued
whether theVerificationContext
is submitted twice, no matter theVerificationStatus
CannotReserveRegistrationFee
the holder can't afford to reserve the amount requested for the verification registration feeVerificationKeyGenerationError
the verification key generation failedNoMatchingVerificationStrategy
no registeredVerificationStrategy
could match the requestNoSuchVerificationRequest
theVerificationRequest
is expected to be stored for the givenVerificationContext
but none could be foundOffChainVerificationError
the off-chain worker encountered an error while attempting verificationInvalidVerificationStatus
some processing was attempted on a ['VerificationRequest`] which has an inappropriate status
Extrinsic​
request_verification()
accepts aVerificationContext
and aVerificationAction
and produces the verification key. AVerificationRequest
is initialized, iterating through all knownVerificationStrategy
in order to find the one that supports thegiven context and action and calling itsnew_request()
method. The holder reserves a certain amount as a registration fee and theVerificationRequest
is stored inVerificationRequestByAccountIdAndVerificationContext
with the statusWaiting
, as aVerificationRequested
event is emitted.perform_verification()
accepts aVerificationRequest
and signals that the holder has taken the appropriate action in order for the verification to succeed. The respectiveVerificationRequest
fromVerificationRequestByAccountIdAndVerificationContext
is stored in the off-chain worker indexing database with the statusPending
. As soon as the off-chain worker runs, it finds the pending request in the off-chain worker indexing database and instantiates the required strategy to perform the verification, which depends on the specific implementation. At this point, an unsigned local transaction is submitted tostore_verification()
, passing theVerificationStatus
.store_verification()
updates theVerificationRequest
present inVerificationRequestByAccountIdAndVerificationContext
storage with theVerificationStatus
coming from the off-chain worker. This extrinsic accepts to be called only as an unsigned local transaction, thus not from the external world. One of theVerificationSuccesful
orVerificationFailed
events is raised according to the status. When theVerificationRequest
status
translates fromSuccess
toFailure
, an registration fee is attributed to the account that is the origin of theperform_verification()
. The amount is paid by the verification holder, who has either failed to notify the invalidation of the domain or is claiming the amount back.
Strategies​
DNSVerificationStrategy​
Contexts:
UrlForDomain
, UrlForDomainWithSubdomain
Actions:
DnsTxtRecord
Key generation:
A string where the value is concatenation of the context values.
Example: anagolay-domain-verification=WfCid([context].join())
Verification
This strategy depends on setting the correct TXT record that is checked in an off-chain worker using the HTTP call to a DoH (DNS over HTTPS) service at the moment the verification is performed, so the TXT record must be in place for verification to succeed.
The DNS resolve provider used is https://cloudflare-dns.com/dns-query. A request to retrieve all TXT records is submitted, and the json response is parsed according to the Cloudflare API docs. The API invocation may fail or timeout due to network conditions and can be retried in such case.
The verification key obtained from the API is wrapped in quotes ("), so the strategy take care of unwrapping the key in order to compare it with the one contained in the VerificationRequest
.