Skip to Content
ConceptsVerifiable Credentials

Verifiable Credentials

A Verifiable Credential (VC) is a cryptographic proof that something is true about a person or entity. Solidus credentials follow the W3C Verifiable Credentials Data Model. Two signature types are supported: Ed25519 (the default, available in production) and BBS+ for selective disclosure (live on testnet as of May 2026 — full issuer flow rolling out next, external audit pending via NLnet NGI Zero).

What Is a Credential

A credential is a digitally signed statement made by an issuer about a subject. In the physical world, a passport is a credential: a government (issuer) states that a person (subject) is a citizen of a country. A Verifiable Credential is the same concept, except it is machine-verifiable, tamper-proof, and can be presented without revealing unnecessary information.

Three parties are involved in every credential:

  • Issuer — the entity that verified the claim and signed the credential
  • Holder — the person or entity the credential is about
  • Verifier — the service that checks the credential when proof is needed

The issuer and the verifier never need to communicate directly. The holder presents the credential, and the verifier checks it against the Solidus blockchain.

Credential Structure

Here is a complete Verifiable Credential:

{ "@context": [ "https://www.w3.org/2018/credentials/v1", "https://solidus.network/credentials/v1" ], "type": ["VerifiableCredential", "KycLevel1Credential"], "issuer": "did:solidus:testnet:4Wp9xKjRvNmQ3eYhT8bC2aFs7gDzLpUk", "issuanceDate": "2026-05-01T10:30:00Z", "expirationDate": "2027-05-01T10:30:00Z", "credentialSubject": { "id": "did:solidus:testnet:7Hk3mRtQZvNxP2bK9wYfJ4eD6cA5sLgR", "kycLevel": 1, "country": "DE", "verificationMethod": "document+liveness" }, "proof": { "type": "BbsBlsSignature2020", "created": "2026-05-01T10:30:00Z", "verificationMethod": "did:solidus:testnet:4Wp9xKjRvNmQ3eYhT8bC2aFs7gDzLpUk#key-1", "proofPurpose": "assertionMethod", "proofValue": "z3FXQqFbc...base58-encoded-signature" } }

Fields

@context — JSON-LD contexts that define the vocabulary. The first is the W3C Verifiable Credentials context; the second is the Solidus-specific context for credential types.

type — an array of credential types. Every credential includes VerifiableCredential as the base type plus a specific type like KycLevel1Credential.

issuer — the DID of the entity that signed the credential.

issuanceDate — when the credential was issued (ISO 8601 timestamp).

expirationDate — when the credential expires. After this date, the credential is no longer valid.

credentialSubject — the claims being made. The id field is the DID of the subject. The remaining fields are the actual claims (KYC level, country, verification method, etc.).

proof — the cryptographic signature. By default this is an Ed25519Signature2020. Credentials issued in the BBS+ variant (live on testnet) carry a BbsBlsSignature2020 instead, which enables selective disclosure.

Credential Types

Solidus defines the following credential types:

EmailCredential

Proves that a user controls an email address.

{ "type": ["VerifiableCredential", "EmailCredential"], "credentialSubject": { "id": "did:solidus:testnet:7Hk3mRtQZvNxP2bK9wYfJ4eD6cA5sLgR", "email": "[email protected]", "verified": true } }

KycLevel1Credential

Basic identity verification — document upload and data extraction.

{ "type": ["VerifiableCredential", "KycLevel1Credential"], "credentialSubject": { "id": "did:solidus:testnet:7Hk3mRtQZvNxP2bK9wYfJ4eD6cA5sLgR", "kycLevel": 1, "country": "DE", "verificationMethod": "document" } }

KycLevel2Credential

Enhanced verification — document upload plus liveness detection.

{ "type": ["VerifiableCredential", "KycLevel2Credential"], "credentialSubject": { "id": "did:solidus:testnet:7Hk3mRtQZvNxP2bK9wYfJ4eD6cA5sLgR", "kycLevel": 2, "country": "DE", "verificationMethod": "document+liveness" } }

KycLevel3Credential

Full verification — document, liveness, and address proof.

{ "type": ["VerifiableCredential", "KycLevel3Credential"], "credentialSubject": { "id": "did:solidus:testnet:7Hk3mRtQZvNxP2bK9wYfJ4eD6cA5sLgR", "kycLevel": 3, "country": "DE", "verificationMethod": "document+liveness+address" } }

Issuance Flow

When a credential is issued, three things happen:

1. Subject completes verification (e.g., KYC on verify.solidus.network) 2. Issuer signs the credential (Ed25519 by default, or BBS+ when the request opts into selective disclosure) 3. Credential hash is anchored on-chain via CredentialIssue transaction

The credential itself — containing personal data — is returned to the holder and stored in their DID wallet. Only the hash goes on-chain. This means the blockchain proves that a credential was issued at a specific time by a specific issuer, without revealing what the credential contains.

Issuing with the SDK

import { createSdk } from '@solidus-network/sdk' const sdk = createSdk({ mode: 'testnet', chain: { rpcUrl: 'https://rpc.solidus.network', signerPrivateKey: issuerPrivateKeyHex, network: 'testnet', }, }) const credential = await sdk.credentials.issue({ type: ['VerifiableCredential', 'KycLevel1Credential'], issuer: 'did:solidus:testnet:4Wp9xKjRvNmQ3eYhT8bC2aFs7gDzLpUk', subject: 'did:solidus:testnet:7Hk3mRtQZvNxP2bK9wYfJ4eD6cA5sLgR', claims: { kycLevel: 1, country: 'DE', verificationMethod: 'document', }, expirationDate: '2027-05-01T10:30:00Z', }) console.log('Credential ID:', credential.id) console.log('On-chain tx:', credential.transactionHash)

Verification Flow

Verifying a credential checks four things:

  1. Signature — is the credential’s signature (Ed25519 or BBS+) valid against the issuer’s public key?
  2. On-chain anchor — does the credential hash exist on-chain via a CredentialIssue transaction?
  3. Expiration — is the current date before the expirationDate?
  4. Revocation — has the issuer submitted a CredentialRevoke transaction for this credential?

All four checks are performed locally by the verifier using the credential and the Solidus RPC endpoint. No contact with the issuer is needed.

Verifying with the SDK

const result = await sdk.credentials.verify(credential) console.log('Valid:', result.valid) // true console.log('Checks:', result.checks) // [ // { check: 'signature', passed: true }, // { check: 'onChainAnchor', passed: true }, // { check: 'expiration', passed: true }, // { check: 'revocation', passed: true } // ]

If any check fails, result.valid is false and the failing check includes a reason field explaining why.

Selective Disclosure

BBS+ signatures allow a credential holder to reveal only specific claims from a credential while proving the entire credential is valid.

How It Works

A standard digital signature covers the entire document. If you change or hide any part of the document, the signature becomes invalid. BBS+ signatures work differently — they sign each claim individually within a single signature, allowing any subset of claims to be disclosed while maintaining cryptographic proof that the undisclosed claims exist and were signed.

Example

A KycLevel2Credential contains these claims:

kycLevel: 2 country: "DE" verificationMethod: "document+liveness"

A verifier only needs to know the KYC level. The holder creates a derived proof that reveals only kycLevel:

const derivedProof = await sdk.credentials.deriveProof(credential, { revealedClaims: ['kycLevel'], }) // The derived proof contains: // - kycLevel: 2 (revealed) // - country: [hidden] // - verificationMethod: [hidden] // - A zero-knowledge proof that the hidden fields exist and were signed

The verifier can confirm that:

  • The credential was signed by the stated issuer
  • The kycLevel value is genuinely 2 (not tampered with)
  • Additional claims exist but are not revealed
  • The original credential has not been revoked

The verifier cannot learn the values of country or verificationMethod.

Revocation

An issuer can revoke a credential at any time by submitting a CredentialRevoke transaction. This records the revocation on-chain. After revocation, any verification of that credential will fail the revocation check.

const revocation = await sdk.credentials.revoke(credential.id) console.log('Revoked:', revocation.revoked) // true console.log('Transaction:', revocation.transactionHash) // 0x5d1a...b3e8

Revocation is irreversible. To re-establish a claim, the issuer must issue a new credential.

On-Chain vs Off-Chain

It is important to understand what is stored where:

DataLocationVisibility
Credential hashOn-chainPublic
Issuance timestampOn-chainPublic
Issuer DIDOn-chainPublic
Subject DIDOn-chainPublic
Revocation statusOn-chainPublic
Credential claims (name, DOB, etc.)Off-chain (user’s wallet)Private
Credential proofOff-chain (user’s wallet)Private

The blockchain serves as a public registry of “credential X was issued by Y for Z at time T” without revealing what the credential actually says. This is the core privacy design of the protocol.

Next Steps

  • Getting Started — issue and verify your first credential
  • DIDs — understand the identifiers that credentials reference
  • Architecture — how credentials are stored in the state tree
  • Network — RPC methods for querying credentials on-chain
Last updated on