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 transactionThe 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:
- Signature — is the credential’s signature (Ed25519 or BBS+) valid against the issuer’s public key?
- On-chain anchor — does the credential hash exist on-chain via a
CredentialIssuetransaction? - Expiration — is the current date before the
expirationDate? - Revocation — has the issuer submitted a
CredentialRevoketransaction 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 signedThe verifier can confirm that:
- The credential was signed by the stated issuer
- The
kycLevelvalue is genuinely2(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...b3e8Revocation 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:
| Data | Location | Visibility |
|---|---|---|
| Credential hash | On-chain | Public |
| Issuance timestamp | On-chain | Public |
| Issuer DID | On-chain | Public |
| Subject DID | On-chain | Public |
| Revocation status | On-chain | Public |
| Credential claims (name, DOB, etc.) | Off-chain (user’s wallet) | Private |
| Credential proof | Off-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