CIS-4: Credential Registry Standard

Created

May 1, 2023

Final

Oct 8, 2023

Supported versions

Smart contract version 1 or newer
(Protocol version 4 or newer)

Standard identifier

CIS-4

Abstract

A standard interface for verifiable credential (VC) registries. A registry keeps track of public VC data and manages the VC lifecycle. The interface provides functionality for the following roles of users:

  • credential issuers can register and revoke credentials, manage revocation keys;

  • credential holders can revoke holder-revocable credentials by signing a revocation message;

  • verifiers can query credential status and data that are used to check a verifiable presentation requested from a holder;

  • revocation authorities can revoke credentials by signing a revocation message.

Each contract instance MUST store public data of VCs of the same type. A credential type is a string that corresponds to the name of the VCs JSON schema, which credentials in the registry are based on. A VCs JSON schema is a JSON schema describing the attributes of a VC. Attributes are sequentially numbered and have their numbers recorded in the additional index field in the schema. See VC schema examples and the corresponding VCs here.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

General types and serialization

Bool

A boolean is serialized as a byte with value 0 for false and 1 for true:

Bool ::= (0: Byte) // False
       | (1: Byte) // True

CredentialHolderId

A credential identifier is a holder’s public key. It is expected that for each issued credential a unique public key is generated. The credential identifier also identifies the credential holder and can be used to prove ownership of the credential by the holder.

It is serialized as PublicKeyEd25519.

MetadataUrl

A URL and optional checksum for metadata stored outside of this contract.

It is serialized as: 2 bytes for the length (n) of the metadata URL in little-endian and then this many bytes for the URL to the metadata (url) followed by an optional checksum. The checksum is serialized by 1 byte to indicate whether a hash of the metadata is included. If its value is 0, then there is no hash; if the value is 1, then 32 bytes for a SHA256 hash (hash) follows:

MetadataChecksum ::= (0: Byte)
                   | (1: Byte) (hash: Byte³²)

MetadataUrl ::= (n: Byte²) (url: Byteⁿ) (checksum: MetadataChecksum)

ContractAddress

An address of a contract instance. It consists of an index and a subindex, both unsigned 64-bit integers.

It is serialized as: First 8 bytes for the index (index) followed by 8 bytes for the subindex (subindex), both little-endian:

ContractAddress ::= (index: Byte⁸) (subindex: Byte⁸)

AccountAddress

An address of an account.

It is serialized as 32 bytes:

AccountAddress ::= (address: Byte³²)

EntrypointName

A name for a smart contract function entrypoint.

It is serialized as: First 2 bytes encode the length (n) of the entrypoint name in little-endian, followed by this many bytes for the entrypoint name (entrypoint):

EntrypointName ::= (n: Byte²) (entrypoint: Byteⁿ)

Timestamp

A timestamp given in milliseconds since Unix epoch. It consists of an unsigned 64-bit integer.

It is serialized as 8 bytes in little-endian:

Timestamp ::= (milliseconds: Byte⁸)

Nonce

An unsigned 64-bit integer number that increases sequentially to protect against replay attacks.

It is serialized as 8 bytes in little-endian:

Nonce ::= (nonce: Byte⁸)

PublicKeyEd25519

A public key is represented as a 32-byte array.

It is serialized as 32 bytes:

PublicKeyEd25519 ::= (key: Byte³²)

SignatureEd25519

Signature for an Ed25519 message.

It is serialized as 64 bytes:

SignatureEd25519 ::= (signature: Byte⁶⁴)

SigningData

Signing data contains metadata for the signature that is used to check whether the signed message is designated for the correct contract and entrypoint, and that it is not expired.

It is serialized as ContractAddress (contract_address), EntrypointName (entrypoint), Nonce (nonce), and Timestamp (timestamp):

SigningData ::= (contract_address: ContractAddress) (entrypoint: EntrypointName) (nonce: Nonce) (timestamp: Timestamp)

SchemaRef

A URL of the credential schema.

Serialized in the same way as MetadataUrl.

CredentialType

A short string (up to 256 characters) in UTF-8 encoding. The string describes the credential type that is used to identify which schema the credential is based on. It corresponds to a value of the name attribute of the credential schema.

It is serialized as: First byte encodes the length (n) of the credential type, followed by this many bytes for the credential type string:

CredentialType ::= (n: Byte) (credential_type: Byteⁿ)

CredentialInfo

Basic data for a verifiable credential.

It is serialized as a credential holder identifier PublicKeyEd25519 (holder_id), a flag whether the credential can be revoked by the holder Bool (holder_revocable), a Timestamp from which the credential is valid (valid_from), an optional Timestamp until which the credential is valid (valid_until), and a reference to the credential metadata MetadataUrl (metadata_url). The optional timestamp is serialized as 1 byte to indicate whether a timestamp is included. If its value is 0, then no timestamp is present; if the value is 1, then the Timestamp bytes follow:

OptionalTimestamp ::= (0: Byte)
                    | (1: Byte) (timestamp: Timestamp)
CredentialInfo ::= (holder_id: CredentialHolderId) (holder_revocable: Bool) (valid_from: Timestamp)
                   (valid_until: OptionTimestamp) (metadata_url: MetadataUrl)

Note

The timestamp valid_until is optional; if it is not included (indicated by the 0 tag), then the credential never expires.

CredentialStatus

The status of a verifiable credential.

It is serialized as 1 byte where 0 correponds to the status Active, 1 corresponds to Revoked, 2 corresponds to Expired, 3 corresponds to NotActivated:

CredentialStatus ::= (0: Byte) // Active
                   | (1: Byte) // Revoked
                   | (2: Byte) // Expired
                   | (3: Byte) // NotActivated

See requirements for credentialStatus for details of how statuses are returned.

Contract functions

A smart contract implementing this standard MUST export the following functions:

credentialEntry

Query a credential entry from the registry by ID.

Parameter

The parameter is the credential ID.

See the serialization rules in CredentialHolderId.

Response

The function returns a registry entry corresponding to the credential ID parameter.

It is serialized as CredentialInfo (credential_info) followed by a credential schema reference SchemaRef (schema_ref), and a credential entry revocation Nonce (revocation_nonce):

CredentialQueryResponse ::= (credential_info: CredentialInfo) (schema_ref: SchemaRef) (revocation_nonce: Nonce)
Requirements
  • The query MUST fail if the credential ID is not present in the registry.

credentialStatus

Query the status of a credential from the credential registry by ID.

Parameter

The parameter is the credential ID.

See the serialization rules in CredentialHolderId.

Response

The function returns the status of a credential.

See the serialization rules in CredentialStatus

Requirements
  • The query MUST fail if the credential ID is not present in the registry.

  • The credential status MUST be Expired if the credential is not revoked, the field valid_until was present in CredentialInfo when registering the credential, and valid_until < now.

  • The credential status MUST NOT be Expired if the field valid_until was not present in CredentialInfo when registering the credential.

  • The credential status MUST be NotActivated if now < valid_from, where valid_from is the corresponding value from CredentialInfo provided when registering the credential.

  • The credential status MUST be Acive if the credential is not revoked, and does not qualify as Expired or NotActivated.

issuer

Query the issuer’s public key. The corresponding private key is used to sign the public part of verifiable credentials issued by this issuer.

Response

The function output is the issuer’s public key. It is serialized as PublicKeyEd25519.

registryMetadata

Query the registry’s metadata.

Response

The function output is the issuer’s metadata URL, the credential type and schema for the credentials stored in the registry.

It is serialized as the issuer’s MetadataUrl (issuer_metadata) followed by the credential type of the registry CredentialType (credential_type) and the corresponding credential JSON schema reference SchemaRef (credential_schema):

MetadataResponse := (issuer_metadata: MetadataUrl) (credential_type: CredentialType) (credential_schema: SchemaRef)

registerCredential

Register public data for a new credential.

Note

This standard does not specify how the issuer is authenticated. Implementations can use various mechanisms. For example, the transaction sender’s address is checked against the issuer’s account address stored in the contract. Another option is to use auxiliary_data to implement a signature-based authentication mechanism.

Parameter

The parameter is credential information that is used to create an entry in the registry.

It is serialized as CredentialInfo (credential_info), followed by auxiliary data, which is serialized as 2 bytes to encode the length (n) of the vector of keys in little-endian, followed by this many bytes of data:

AuxData ::= (n: Byte²) (data: Byteⁿ)
RegisterCredentialParameter ::= (credential_info: CredentialInfo) (auxiliary_data: AuxData)
Requirements
  • The credential registration request MUST fail if the credential ID is already present in the registry.

  • After successful registration, querying the credential by its ID with credentialEntry MUST succeed.

revokeCredentialIssuer

Revoke a credential by the issuer’s request.

Note

This standard does not specify how the issuer is authenticated. Implementations can use various mechanisms. For example, the transaction sender’s address is checked against the issuer’s account address stored in the contract. Another option is to use auxiliary_data to implement a signature-based authentication mechanism.

Parameter

The parameter is the credential ID CredentialHolderId and an optional string in the UTF-8 encoding that indicates the revocation reason.

It is serialized as CredentialHolderId followed by 1 byte to indicate whether a reason is included. If its value is 0, then no reason string is present; if the value is 1, then the bytes corresponding to the reason string follow. The optional revocation reason is followed by auxiliary data, which is serialized as 2 bytes to encode the length (n) of the data vector in little-endian, followed by this many bytes of data:

OptionalReason ::= (0: Byte)
                 | (1: Byte) (n: Byte) (reason_string: Byteⁿ)
AuxData ::= (n: Byte²) (data: Byteⁿ)
RevokeCredentialIssuerParam ::= (credential_id: CredentialHolderId) (reason: OptionalReason) (auxiliary_data: AuxData)
Requirements
  • If revoked successfully, the credential status MUST change to Revoked (see credentialStatus).

  • The revocation MUST fail if any of the following conditions are met:
    • The credential ID is not present in the registry.

    • The credential status is not one of Active or NotActivated (see credentialStatus).

revokeCredentialHolder

Revoke a credential by the holders’s request.

The holder is authorized to revoke a credential by verifying the signature with the holder’s public key. The public key is part of CredentialInfo that is used when registering a credential with the registerCredential entrypoint.

Parameter

It is serialized as SignatureEd25519 (signature) and data, for which the signature is computed RevocationDataHolder (message), consisting of CredentialHolderId (credential_id), metadata about the signature SigningData (signing_data), and an optional revocation reason (reason), serialized similarly to revokeCredentialIssuer:

RevocationDataHolder ::= (credential_id: CredentialHolderId) (signing_data: SigningData) (reason: OptionalReason)
RevokeCredentialHolderParam ::= (signature: SignatureEd25519) (message : RevocationDataHolder)
Requirements
  • If revoked successfully, the credential status MUST change to Revoked (see credentialStatus).

  • The message to be signed MUST be produced in the following way:
    • Start with the bytes of the domain separation string WEB3ID:REVOKE.

    • Append RevocationDataHolder bytes from the input parameter.

  • The RevokeCredentialHolderParam’s signing_data MUST include a nonce to protect against replay attacks. The holders’s nonce is sequentially increased every time a revocation request is successfully executed. The function MUST only accept a RevokeCredentialHolderParam if it has the next nonce following the sequential order.

  • The revocation MUST fail if any of the following conditions are met:
    • The credential ID is not present in the registry.

    • The credential status is not one of Active or NotActivated (see credentialStatus).

    • The credential is not holder-revocable.

    • The signature was intended for a different contract.

    • The signature was intended for a different entrypoint.

    • The signature is expired.

    • The signature cannot be validated. The smart contract logic SHOULD use all possible efforts to ensure that only the holder can generate and authorize a revocation request with a valid signature.

revokeCredentialOther

Revoke a credential by a revocation authority request. A revocation authority is any entity that holds a private key corresponding to the public key registered by the issuer. A revocation authority is authorized to revoke a credential by verifying the signature with the public key of the given identifier.

This entrypoint gives a general way of adding revocation rights to external entities. It replaces the authorization checks conducted on the sender/invoker variable with signature verification. In particular, it enables the issuer to provide a service for selected entities to revoke credentials without paying for revocation transactions.

Parameter

It is serialized as SignatureEd25519 (signature) and data, for which the signature is computed RevocationDataHolder (message) consisting of CredentialHolderId (credential_id), metadata about the signature SigningData (signing_data), a revocation public key PublicKeyEd25519 , and an optional revocation reason (reason), serialized similarly to revokeCredentialIssuer:

RevocationDataOther ::= (credential_id: CredentialHolderId) (signing_data: SigningData) (revocation_key: PublicKeyEd25519) (reason: OptionalReason)
RevokeCredentialHolderParam ::= (signature: SignatureEd25519) (message : RevocationDataOther)
Requirements
  • If revoked successfully, the credential status MUST change to Revoked (see credentialStatus).

  • The message to be signed MUST be produced in the following way:
    • Start with the bytes of the domain separation string WEB3ID:REVOKE.

    • Append RevocationDataOther bytes from the input parameter.

  • The RevokeCredentialOtherParam’s signing_data MUST include a nonce to protect against replay attacks. The revocation authority’s nonce is sequentially increased every time a revocation request is successfully executed. The function MUST only accept a RevokeCredentialOtherParam if it has the next nonce following the sequential order.

  • The revocation MUST fail if any of the following conditions are met:
    • The credential ID is not present in the registry.

    • The revocation key in not present in the registry.

    • The credential status is not one of Active or NotActivated (see credentialStatus).

    • The signature was intended for a different contract.

    • The signature was intended for a different entrypoint.

    • The signature is expired.

    • The signature can not be validated. The smart contract logic SHOULD use all possible efforts to ensure that only the revocation authority can generate and authorize a revocation request with a valid signature.

registerRevocationKeys

Register public keys that can be used by revocation authorities.

Note

This standard does not specify how the issuer is authenticated. Implementations can use various mechanisms. For example, the transaction sender’s address is checked against the issuer’s account address stored in the contract. Another option is to use auxiliary_data to implement a signature-based authentication mechanism.

Parameter

It is serialized as First 2 bytes encode the length (n) of the vector of keys in little-endian, followed by this many PublicKeyEd25519 keys. The revocation keys are followed by auxiliary data, which is serialized as 2 bytes to encode the length (m) of the data vector in little-endian, followed by this many bytes of data:

AuxData ::= (m: Byte²) (data: Byteᵐ)
RegisterPublicKeyParameters ::= (n: Byte²) (key: PublicKeyEd25519)ⁿ (auxiliary_data: AuxData)
Requirements
  • The revocation MUST fail if some of the keys are already registered.

  • The smart contract MUST prevent resetting the nonce associated with a public key. For example, the contract logic could keep track of all keys seen by the contract and avoid reusing the same keys even after the keys were made unavailable by calling removeRevocationKeys.

removeRevocationKeys

Make a list of public keys unavailable to revocation authorities.

Note

This standard does not specify how the issuer is authenticated. Implementations can use various mechanisms. For example, the transaction sender’s address is checked against the issuer’s account address stored in the contract. Another option is to use auxiliary_data to implement a signature-based authentication mechanism.

Parameter

It is serialized as: First 2 bytes encode the length (n) of the vector of keys in little-endian, followed by this many PublicKeyEd25519 keys. The revocation keys are followed by auxiliary data, which is serialized as 2 bytes to encode the length (m) of the data vector in little-endian, followed by this many bytes of data:

AuxData ::= (m: Byte²) (data: Byteᵐ)
RegisterPublicKeyParameters ::= (n: Byte²) (key: PublicKeyEd25519)ⁿ (auxiliary_data: AuxData)
Requirements
  • The revocation MUST fail if some of the keys are not present in the registry.

revocationKeys

Query revocation keys.

Response

The function outputs a list of available revocation keys. Valid signatures with the corresponding private keys can be used to revoke any credential in the registry.

It is serialized as: First 2 bytes encode the length (n) of the vector of keys in little-endian, followed by this many PublicKeyEd25519 keys:

RegisterPublicKeyParameters ::= (n: Byte²) (key: PublicKeyEd25519)ⁿ

Logged events

The events defined by this specification are serialized using one byte to discriminate the different events. A custom event SHOULD NOT have a first byte colliding with any of the events defined by this specification.

RegisterCredentialEvent

A RegisterCredentialEvent event MUST be logged when a new credential is registered. The event records the credential identifier, the credential type, and the corresponding schema reference.

The RegisterCredentialEvent event is serialized as: first a byte with the value of 249, followed by CredentialHolderId (crednetial_id), a reference to the credential schema SchemaRef (schema_ref), a credential type CredentialType (credential_type), and a reference to the credential metadata MetadataUrl (metadata_url)

CredentialEventData ::= (credential_id: CredentialHolderId) (schema_ref: SchemaRef) (credential_type: CredentialType) (metadata_url: MetadataUrl)
RegisterCredentialEvent ::= (249: Byte) (data: CredentialEventData)

RevokeCredentialEvent

A RevokeCredentialEvent event MUST be logged when a credential is revoked. The event records the credential identifier who requested the revocation (the holder, the issuer, or a revocation authority), and an optional string with a short comment on the revocation reason.

The RevokeCredentialEvent event is serialized as: first a byte with the value of 248, followed by CredentialHolderId (crednetial_id), a revoker, and an optional revocation reason (reason), serialized similarly to revokeCredentialIssuer. revoker is serialized as 1 byte to indicate who sent the revocation request ( 0 - issuer, 1 - holder, 2 -revocation authority); if the first byte is 2, then it is followed by a public key PublicKeyEd25519 of the revoker:

Revoker ::= (0: Byte)                         // Issuer
          | (1: Byte)                         // Holder
          | (2: Byte) (key: PublicKeyEd25519) // Other
RevokeCredentialEvent ::= (248: Byte) (credential_id: CredentialHolderId) (revoker: Revoker) (reason: OptionalReason)

IssuerMetadata

A IssuerMetadata event MUST be logged when setting the metadata URL of the issuer. It consists of a URL for the location of the metadata for the issuer with an optional SHA256 checksum of the content.

The IssuerMetadata event is serialized as: first a byte with the value of 247, followed by MetadataUrl (metadata):

IssuerMetadata ::= (247: Byte) (metadata: MetadataUrl)

CredentialMetadataEvent

A CredentialMetadataEvent event MUST be logged when updating the credential metadata. It consist of a credential ID and a URL for the location of the metadata for this credential with an optional SHA256 checksum of the content.

The CredentialMetadataEvent event is serialized as: first a byte with the value of 246, followed by CredentialHolderId (id), and then a MetadataUrl (metadata):

CredentialMetadataEvent ::= (246: Byte) (id: CredentialHolderId) (metadata: MetadataUrl)

CredentialSchemaRefEvent

A CredentialSchemaRefEvent event MUST be logged when updating the credential schema reference for a credential type. It consist of a credential type and a URL for the location of the schema for this credential with an optional SHA256 checksum of the content.

The CredentialSchemaRefEvent event is serialized as: first a byte with the value of 245, followed by CredentialType (type), and then a SchemaRef (schema_ref):

CredentialSchemaRefEvent ::= (245: Byte) (type: CredentialType) (schema_ref: SchemaRef)

RevocationKeyEvent

A RevocationKeyEvent event MUST be logged when registering a new or removing an existing revocation key. It consists of the key and the action performed with the key (registration or removal).

The RevocationKeyEvent event is serialized as: first a byte with the value of 244, followed by the key bytes PublicKeyEd25519 and 1 byte encoding the action (0 for Register, 1 for Remove):

RevocationKeyAction ::= (0: Byte)    // Register
                      | (1: Byte)    // Remove
RevocationKeyEvent ::= (244: Byte) (public_key: PublicKeyEd25519) (action: RevocationKeyAction)

Issuer metadata JSON

The issuer metadata is stored off-chain and MUST be a JSON (RFC 8259) file.

This specification reserves a number of field names, shown in the table below. Fields not marked with optional MUST be included.

Issuer metadata JSON Object

Property

JSON value type [JSON-Schema]

Description

name

string

The name to display for the issuer.

icon

URL JSON object

An image URL for displaying the issuer.

description (optional)

string

A description for the issuer.

url (optional)

string (RFC 3986) [uri-reference]

A URL of the issuer’s website.

Optionally a SHA256 hash of the JSON file can be logged with the IssuerMetadata event for checking integrity. Since the metadata JSON file could contain URLs, a SHA256 hash can optionally be associated with the URL. To associate a hash with a URL the JSON value is an object:

URL JSON Object

Property

JSON value type [JSON-Schema]

Description

url

string (RFC 3986) [uri-reference]

A URL.

hash (optional)

string

A SHA256 hash of the URL content encoded as a hex string.

Example issuer metadata

{
  "name": "Concordium",
  "icon" : {
    "url":  "https://concordium.com/wp-content/uploads/2022/07/Concordium-1.png",
    "hash": "1c74f7eb1b3343a5834e60e9a8fce277f2c7553112accd42e63fae7a09e0caf8"
    }
  "description": "A public-layer 1, science-backed blockchain",
  "url": "https://concordium.com"
}

Credential metadata JSON

The credential metadata is stored off-chain and MUST be a JSON (RFC 8259) file.

Credential metadata JSON Object

Property

JSON value type [JSON-Schema]

Description

title

string

The name to display for the credential.

logo

URL JSON object

An image URL for displaying the credential. The RECOMMENDED size of the image is 40x40.

backgroundColor

string

A hex code of the background color for displaying the credential.

image (optional)

URL JSON object

A background image URL for displaying the credential. The RECOMMENDED size of the image is 327x120.

localization (optional)

JSON object with locales as field names (RFC 5646) and field values are URL JSON objects linking to JSON files. Credential issuers SHOULD provide localization files for all the languages they want to support.

URLs to JSON files with localized token metadata.

Where the URL JSON object is the same as in IssuerMetadata.

Optionally a SHA256 hash of the JSON file can be logged with the CredentialMetadataEvent event for checking integrity.

Example credential metadata

{
   "title": "Concordium Employment",
   "logo" : {
     "url":  "https://concordium.com/wp-content/uploads/2022/07/Concordium-1.png",
     "hash": "1c74f7eb1b3343a5834e60e9a8fce277f2c7553112accd42e63fae7a09e0caf8"
     }
   "backgroundColor": "#000000",
   "image": {
     "url": "https://concordium.com/employment/vc-background.png",
   }
   "localization": {
     "da-DK": {
       "url": "https://location.of/the/danish/metadata.json",
       "hash": "624a1a7e51f7a87effbf8261426cb7d436cf597be327ebbf113e62cb7814a34b"
     }
   }
 }

Note that that URL addresses for images can come with or without the hash attribute. In the example, the image attribute value does not specify hash. In this case, no content integrity check will be performed.

The Danish localization JSON file could be:

{
  "employer": "Arbejdsgiver",
  "employedFrom": "Ansat fra",
  "employedUntil": "Ansat indtil"
}

Smart contract limitations

A number of limitations are important to be aware of:

  • The byte size of smart contract function parameters are limited to at most 65535 B.

  • Each logged event is limited to 0.5 KiB.

  • The total size of the smart contract module is limited to 512 KiB.