Skip to main content

Verification

NameValue
Repositoryhttps://github.com/anagolay/anagolay-chain/tree/main/pallets/verification
PalletYes
LicenseApache2
Registration Fee1 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 verification
  • MAX_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 the VerificationRequests indexed by their holder AccountId and VerificationContext

Types​

  • Bytes is an alias for BoundedVec<u8, Get<u32>>
  • DomainVerificationContext is an enumeration providing the switch to verify (full URL + breakdown) and implements the VerificationContext 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 a VerificationContext trait, providing the following methods:
    • new_request(AccountId, VerificationContext, VerificationAction) -> VerificationRequest: creates a new VerificationRequest initialized with the verification key
    • supports(VerificationContext) -> bool : defines whether a VerificationContext is supported or not
    • verify() -> 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 supported VerificationContexts
    • status: the VerificationStatus
    • holder: the AccountId issuing the request
    • action: an indication of the action that the holder must perform to pass verification, which is the verification strategy
    • key: a Bytes field containing a challenge string. This is in complete control of the invoked Strategy
    • id: an Option<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 ongoing
  • VerificationSuccessful(AccountId, VerificationRequest) produced upon successful verification
  • VerificationFailed(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 the VerificationContext is submitted twice, no matter the VerificationStatus
  • CannotReserveRegistrationFee the holder can't afford to reserve the amount requested for the verification registration fee
  • VerificationKeyGenerationError the verification key generation failed
  • NoMatchingVerificationStrategy no registered VerificationStrategy could match the request
  • NoSuchVerificationRequest the VerificationRequest is expected to be stored for the given VerificationContext but none could be found
  • OffChainVerificationError the off-chain worker encountered an error while attempting verification
  • InvalidVerificationStatus some processing was attempted on a ['VerificationRequest`] which has an inappropriate status

Extrinsic​

  1. request_verification() accepts a VerificationContext and a VerificationAction and produces the verification key. A VerificationRequest is initialized, iterating through all known VerificationStrategy in order to find the one that supports thegiven context and action and calling its new_request() method. The holder reserves a certain amount as a registration fee and the VerificationRequest is stored in VerificationRequestByAccountIdAndVerificationContext with the status Waiting, as a VerificationRequested event is emitted.
  2. perform_verification() accepts a VerificationRequest and signals that the holder has taken the appropriate action in order for the verification to succeed. The respective VerificationRequest from VerificationRequestByAccountIdAndVerificationContext is stored in the off-chain worker indexing database with the status Pending. 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 to store_verification(), passing the VerificationStatus.
  3. store_verification() updates the VerificationRequest present in VerificationRequestByAccountIdAndVerificationContext storage with the VerificationStatus 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 the VerificationSuccesful or VerificationFailed events is raised according to the status. When the VerificationRequest status translates from Success to Failure, an registration fee is attributed to the account that is the origin of the perform_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.