Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-5851: On-Chain Verifiable Credentials

Interface for contracts that manage verifiable claims and identifiers as Soulbound tokens.

Authors Yu Liu (@yuliu-debond), Junyi Zhong (@Jooeys)
Created 2022-10-18
Discussion Link https://ethereum-magicians.org/t/eip-5815-kyc-certification-issuer-and-verifier-standard/11513
Requires EIP-721, EIP-1155, EIP-1167, EIP-1967, EIP-3475

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:

  • Avoiding Sybil attacks with one person one vote
  • Participation in certain events with credentials
  • Compliance to government financial regulations etc.

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

  • Zero-Knowledge Proof (ZKP): a cryptographic device that can convince a verifier that an assertion is correct without revealing all of the inputs to the assertion.

  • Soulbound Token (SBT): A non-fungible and non-transferrable token that is used for defining the identity of the users.

  • SBT Certificate: An SBT that represents the ownership of ID signatures corresponding to the claims defined in function standardClaim().

  • Verifiable Credential (VC): A collection of claims made by an issuer. These are temper evident credentials that allow the holders to prove that they posses certain characteristics (for example, passport verification, constraints like value of tokens in your wallet, etc) as demanded by the verifier entity.

  • Claim: An assertion that the DID Holder must fulfill to be verified.

  • Holder: The entity that stores the claim, such as a digital identity provider or a DID registry. The holder is responsible for validating the claim and providing verifiable evidence of the claim.

  • Claimer: The party making a claim, such as in an identity verification process.

  • Issuer: The entity that creates a verifiable credential from claims about one or more subjects to a holder. Example issuers include governments, corporations, non-profit organizations, trade associations, and individuals.

  • Verifier: An entity that validates data provided by an issuer of verifiable credentials, determining its accuracy, origin, currency and trustworthiness.

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

  • This EIP is backward compliant for the contracts that keep intact the metadata structure of previous issued SBT’s with their ID and claim requirement details.
    • For e.g if the DeFI provider (using the modifiers to validate the ownership of required SBT by owner) wants the admin to change the logic of verification or remove certain claim structure, the previous holders of the certificates will be affected by these changes.

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:

  • EIP-5851 Verifier is a simple modifier that needs to be imported by functions that are to be only called by holders of the SBT certificates. Then the modifier will call the issuer contract to verifiy if the claimer has the SBT certifcate in question.

  • EIP-5851 Issuer is an example of an identity certificate that can be assigned by a KYC controller contract. This is a full implementation of the standard interface.

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 and related rights waived via CC0.

Citation

Please cite this document as:

Yu Liu (@yuliu-debond), Junyi Zhong (@Jooeys), "ERC-5851: On-Chain Verifiable Credentials [DRAFT]," Ethereum Improvement Proposals, no. 5851, October 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5851.