bk-verify
v0.1.1
Published
Trustless CLI verifier for BK Credential System (W3C VC + SD-JWT + Sepolia anchors).
Maintainers
Readme
bk-verify — Trustless CLI Verifier
Standalone CLI for the BK Credential System (W3C VC + SD-JWT + Sepolia Merkle anchors). Verifies a credential file with no HCMUT backend in the trust path — only a public Sepolia RPC endpoint is required.
This is the canonical demonstration of Decision 1 (trustless verification) and Decision 22 (one shared verify SDK, multiple distributions). Same six checks as the Web Verify Portal; identical results.
Install / Run
No global install needed:
npx bk-verify <file.vc.json>
npx bk-verify <credId> --api-base https://issuer.example.comRequires Node.js ≥ 20.
Usage
Verify a downloaded .vc.json
npx bk-verify ./alice.vc.jsonAccepts either a raw SD-JWT compact string or a JSON wrapper with one of
{ sdJwt | compact | presentation }.
Verify by credential ID (via issuer API)
npx bk-verify bk_2024_abc123 --api-base https://issuer.bk.edu.vnThe CLI fetches the public canonical view (PCV + Merkle proof, no
disclosures) and runs the same on-chain checks. Disclosed claim values still
require the holder to share the full .vc.json.
Output
Human-readable (default, TTY-detected color):
✓ Issuer signature
✓ Issuer registered on-chain
✓ Merkle proof
✓ Batch anchored on-chain
✓ Not revoked
✓ Not expired
Verified: did:ethr:0xabc… credId: bk_2024_abc123Machine-readable (--json):
npx bk-verify ./alice.vc.json --jsonEmits the full VerifyResult shape:
{
"valid": true,
"checks": {
"issuerSignature": true,
"issuerRegistered": true,
"merkleProof": true,
"anchored": true,
"notRevoked": true,
"notExpired": true
},
"credential": {
"credId": "bk_2024_abc123",
"vct": "BKISC_MEMBER",
"issuer": { "did": "did:ethr:0x…", "address": "0x…", "name": "" },
"iat": 1717000000,
"disclosedClaims": { "full_name": "Alice", "student_id": "BK001" }
},
"errors": []
}Flags
| Flag | Env | Default |
| ----------------------------- | ------------------------- | --------------------------------------------- |
| --rpc <url> | BK_RPC_URL | https://ethereum-sepolia.publicnode.com |
| --registry <addr> | BK_CREDENTIAL_REGISTRY | Sepolia deployment |
| --issuer-registry <addr> | BK_ISSUER_REGISTRY | Sepolia deployment |
| --api-base <url> | BK_API_BASE | (required for credId mode) |
| --ipfs-gateway <url> | BK_IPFS_GATEWAY | https://gateway.pinata.cloud/ipfs/ |
| --json | — | Off (emit human-readable summary) |
| --quiet | — | Off (suppress non-error output) |
| --no-color | NO_COLOR | Color on if TTY, off otherwise |
| -V, --version | — | |
| -h, --help | — | |
Default registry addresses come from
packages/contracts/deployments/sepolia.json
and are baked into the published bundle.
Exit codes
| Code | Meaning |
| ---- | --------------------------------------------------------------- |
| 0 | Valid — all six checks passed |
| 1 | Invalid — at least one check failed (signature, anchor, etc.) |
| 2 | Usage error — missing/unknown flag or argument |
| 3 | I/O error — file not found, RPC unreachable, network failure |
CI-friendly: npx bk-verify file.vc.json --quiet && echo ok works.
What gets checked
- Issuer signature — ES256K recovery against the issuer's Ethereum address (no public key needed on chain).
- Issuer registered on-chain —
IssuerRegistry.isIssuer(addr). - Merkle proof — re-derive the leaf from the PCV claims, verify against
the signed
merkleRoot. - Batch anchored on-chain —
CredentialRegistry.getAnchor(root). - Not revoked —
CredentialRegistry.isRevoked(keccak256(credId)). - Not expired — local
expcheck against system clock.
License
MIT
