ERC-5851 - On-Chain Verifiable Credentials

Created 2022-10-18
Status Stagnant
Category ERC
Type Standards Track
Authors
Requires

Abstract

This proposal introduces a method of certifying that a particular address meets a claim, and a method of verifying those certifications using on-chain metadata. Claims are assertions or statements made about a subject having certain properties that may be met conditions (for example: age >= 18), and are certified by issuers using a Soundbound Token (SBT).

Motivation

On-chain issuance of verifiable attestations are essential for use-case like:

We are proposing a standard claims structure for Decentralized Identity (DID) issuers and verifier entities to create smart contracts in order to provide on-chain commitment of the off-chain verification process, and once the given address is associated with the given attestation of the identity verification off-chain, the issuers can then onboard other verifiers (i.e. governance, financial institution, non-profit organization, web3 related cooperation) to define the condition of the ownership of the user in order to reduce the technical barriers and overhead of current implementations.

The motivation behind this proposal is to create a standard for verifier and issuer smart contracts to communicate with each other in a more efficient way. This will reduce the cost of KYC processes, and provide the possibility for on-chain KYC checks. By creating a standard for communication between verifiers and issuers, it will create an ecosystem in which users can be sure their data is secure and private. This will ultimately lead to more efficient KYC processes and help create a more trustful environment for users. It will also help to ensure that all verifier and issuer smart contracts are up-to-date with the most recent KYC regulations.

Specification

The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Definitions

Metadata Standard

Claims MUST be exposed in the following structures:

1. Metadata information

Each claim requirement MUST be exposed using the following structure:

    /** Metadata
    * 
    * @param title defines the name of the claim field
    * @param _type is the type of the data (bool,string,address,bytes,..)
    * @param description additional information about claim details.
     */
    struct Metadata {
        string title;
        string _type;
        string description;
    }

2. Values Information

This following structure will be used to define the actual claim information, based on the description of the Metadata structure, the structure is the same as Values structure of EIP-3475.

   struct Values{
       string stringValue;
       uint uintValue;
       address addressValue;
       bool boolValue;
  }

3. Claim structure

Claims (eg. age >= 18, jurisdiction in allowlist, etc.) are represented by one or many instances of the Claim structure below:

    /** Claims
    * 
    * Claims structure consist of the conditions and value that holder claims to associate and verifier has to validate them.
    * @notice the below given parameters are for reference purposes only, developers can optimize the fields that are needed to be represented on-chain by using schemes like TLV, encoding into base64 etc.
    * @dev structure that defines the parameters for specific claims of the SBT certificate
    * @notice this structure is used for the verification process, it contains the metadata, logic and expectation
    * @notice logic can represent either the enum format for defining the different operations, or they can be logic operators (stored in form of ASCII figure based on unicode standard). like  e.g: 
("⊄" = U+2284, "⊂" = U+2282,  "<" = U+003C , "<=" = U + 2265,"==" = U + 003D, "!="U + 2260, ">=" = U + 2265,">" =  U + 2262).
    */
    struct Claim {
        Metadata metadata;
        string logic;
        Values expectation;

    }

description of some logic functions that can be used are as follows:

Symbol Description
does not belong to the set of values (or range) defined by the corresponding Values
condition that the parameter belongs to one of values defined by the Values
< condition that the parameter is greater than value defined by the Values
== condition that the parameter is strictly equal to the value defined by the Values structure

Claim Example

{
   "title":"age",
   "type":"unit",
   "description":"age of the person based on the birth date on the legal document",
   "logic":">=",
   "value":"18"
}

Defines the condition encoded for the index 1 (i.e the holder must be equal or more than 18 years old).

Interface specification

Verifier

    /// @notice getter function to validate if the address `claimer` is the holder of the claim defined by the tokenId `SBTID`
    /// @dev it MUST be defining the conditional operator (logic explained below) to allow the application to convert it into code logic 
    /// @dev logic given here MUST be the conditiaonl operator, MUST be one of ("⊄", "⊂", "<", "<=", "==", "!=", ">=", ">")
    /// @param claimer is the EOA address that wants to validate the SBT issued to it by the issuer. 
    /// @param SBTID is the Id of the SBT that user is the claimer.
    /// @return true if the assertion is valid, else false
    /**
    example ifVerified(0xfoo, 1) => true will mean that 0xfoo is the holder of the SBT identity token defined by tokenId of the given collection. 
    */
    function ifVerified(address claimer, uint256 SBTID) external view returns (bool);

Issuer

    /// @notice getter function to fetch the on-chain identification logic for the given identity holder.
    /// @dev it MUST not be defined for address(0). 
    /// @param SBTID is the Id of the SBT that the user is the claimer.
    /// @return the struct array of all the descriptions of condition metadata that is defined by the administrator for the given KYC provider.
    /**
    ex: standardClaim(1) --> {
    { "title":"age",
        "type": "uint",
        "description": "age of the person based on the birth date on the legal document",
        },
       "logic": ">=",
    "value":"18"  
    }
    Defines the condition encoded for the identity index 1, defining the identity condition that holder must be equal or more than 18 years old.
    **/

    function standardClaim(uint256 SBTID) external view returns (Claim[] memory);

    /// @notice function for setting the claim requirement logic (defined by Claims metadata) details for the given identity token defined by SBTID.
    /// @dev it should only be called by the admin address.
    /// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims.
    /// @param `claims` is the struct array of all the descriptions of condition metadata that is defined by the administrator. check metadata section for more information.
    /**
    example: changeStandardClaim(1, { "title":"age",
            "type": "uint",
            "description": "age of the person based on the birth date on the legal document",
            },
        "logic": ">=",
        "value":"18"  
    }); 
    will correspond to the functionality that admin needs to adjust the standard claim for the identification SBT with tokenId = 1, based on the conditions described in the Claims array struct details.
    **/

    function changeStandardClaim(uint256 SBTID, Claim[] memory _claims) external returns (bool);

    /// @notice function which uses the ZKProof protocol to validate the identity based on the given 
    /// @dev it should only be called by the admin address.
    /// @param SBTID is the Id of the SBT-based identity certificate for which admin wants to define the Claims.
    /// @param claimer is the address that needs to be proven as the owner of the SBT defined by the tokenID.
    /**
    example: certify(0xA....., 10) means that admin assigns the DID badge with id 10 to the address defined by the `0xA....` wallet.
    */
    function certify(address claimer, uint256 SBTID) external returns (bool);

    /// @notice function which uses the ZKProof protocol to validate the identity based on the given 
    /// @dev it should only be called by the admin address.
    /// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims.
    /// @param claimer is the address that needs to be proven as the owner of the SBT defined by the tokenID.
    /* eg: revoke(0xfoo,1): means that KYC admin revokes the SBT certificate number 1 for the address '0xfoo'. */
    function revoke(address certifying, uint256 SBTID) external returns (bool);

Events

    /** 
    * standardChanged
    * @notice standardChanged MUST be triggered when claims are changed by the admin. 
    * @dev standardChanged MUST also be triggered for the creation of a new SBTID.
    e.g : emit StandardChanged(1, Claims(Metadata('age', 'uint', 'age of the person based on the birth date on the legal document' ), ">=", "18");
    is emitted when the Claim condition is changed which allows the certificate holder to call the functions with the modifier, claims that the holder must be equal or more than 18 years old.
    */
    event StandardChanged(uint256 SBTID, Claim[] _claims);

    /** 
    * certified
    * @notice certified MUST be triggered when the SBT certificate is given to the certifying address. 
    * eg: Certified(0xfoo,2); means that wallet holder address `0xfoo` is certified to hold a certificate issued with id 2, and thus can satisfy all the conditions defined by the required interface.
    */
    event Certified(address claimer, uint256 SBTID);

    /** 
    * revoked
    * @notice revoked MUST be triggered when the SBT certificate is revoked. 
    * eg: Revoked( 0xfoo,1); means that entity user 0xfoo has been revoked to all the function access defined by the SBT ID 1.
    */
    event Revoked(address claimer, uint256 SBTID);
}

Rationale

TBD

Backwards Compatibility

Test Cases

Test cases for the minimal reference implementation can be found here for using transaction verification regarding whether the users hold the tokens or not. Use Remix IDE to compile and test the contracts.

Reference Implementation

The interface is divided into two separate implementations:

Security Considerations

  1. Implementation of functional interfaces for creating KYC on SBT (i.e changeStandardClaim(), certify() and revoke()) are dependent on the admin role. Thus the developer must insure security of admin role and rotation of this role to the entity entrusted by the KYC attestation service provider and DeFI protocols that are using this attestation service.

Copyright

Copyright and related rights waived via CC0.