Onboard a BYOK Issuer
Who this is for: an institution that wants to keep custody of its own Ed25519 signing key (HSM/KMS) instead of letting Attest ID hold it, and needs the task order — not the byte-level canonicalization spec — to get there.
Every claim below traces to BYOK Signing Spec (the authoritative contract) and BYOK Reference SDK & Oxford Demo Issuer. Where the two disagree, the signing spec wins.
Why BYOK instead of the default (custodial)
By default Attest ID generates and holds your signing key. BYOK inverts that: you keep the private key in your own HSM/KMS and Attest ID only ever sees your public key and your signatures — never anything that lets it reconstruct your private key.
Steps
1. Generate an Ed25519 keypair in your own key custody
Production: an HSM (PKCS#11), AWS KMS, Azure Key Vault, Google Cloud KMS, or equivalent — never a
plaintext on-disk key. For local evaluation only, the reference SDK’s LocalKeystoreSigner
(clients/byok-sdk-java) is explicitly a demo-only software keystore — don’t point it at
anything you’d call production.
2. Register your public key
POST /api/v1/issuer/register
{ "name": "...", "website_url": "...", "contact_email": "...", "country_code": "...",
"byok": true, "public_key": "<base64, raw 32-byte Ed25519 public key>" }public_key is required once byok: true — registration fails 400 without it. You’ll get back
status: "PENDING_APPROVAL" — unconditional for every issuer, custodial or BYOK; there is no path
to ACTIVE straight out of registration.
3. Wait for admin approval
An Attest ID admin calls POST /api/v1/issuers/{issuer_did}/approve. IssuanceOrchestrator
rejects issuance for any issuer whose status isn’t exactly ACTIVE. There’s nothing to poll for
this specifically — retry issuance, or check status via GET /api/v1/issuers/{issuer_did}.
4. Build the credential, canonicalize, and sign — entirely on your side
This is the part with real byte-for-byte precision requirements — don’t reimplement it from a
paraphrase. Follow the signing spec §2–§4 exactly, or use the reference SDK’s
VcCanonicalizer/Signer (clients/byok-sdk-java), which implements the identical algorithm. The
two things every from-scratch implementation gets wrong first:
- You generate
credential_id,issuanceDate, and the proof’screatedyourself, before signing — not the server. Truncate every timestamp to millisecond precision before formatting, or your signature won’t verify. - You sign the concatenated hash pair (
configHash || documentHash), never the raw document — signing the document alone leavescreated/proofPurpose/verificationMethodcompletely unauthenticated.
5. Submit the issuance request with your signature
POST /api/v1/credentials/issue
{ "issuer_did": "...", "holder_did": "...", "credential_type": "...", "credential_data": {...},
"signature": "<base64, 64-byte Ed25519 sig>", "key_id": "...", "credential_id": "...",
"issuance_timestamp": ..., "expiration_timestamp": ..., "proof_created_timestamp": ... }All five BYOK-only fields are required together — a request with a signature but no
proof_created_timestamp is rejected outright, since the server has no other way to know what
created you actually signed over.
6. Assemble and hand over the final signed credential
The issuance response does not echo the signed VC — you already have everything needed: the
unsigned shell from step 4, plus the signature and key_id you just used. Attach the proof
object per the signing spec §6, using the exact created you signed over. This resulting
document is the shareable, fully signed W3C VC.
7. Confirm the round trip
POST /api/v1/credentials/verify
{ "credential": { ...the fully signed VC... }, "check_status": true, "check_expiration": true }is_valid: true and status: "VALID" confirm your signature verifies against your registered
public key.
If something doesn’t verify
Almost always one of: a sub-millisecond timestamp that didn’t round-trip through
Instant.ofEpochMilli, signing the document instead of the configHash || documentHash
concatenation, or a created in the final proof object that doesn’t exactly match what was
signed. See the signing spec §2.3–§2.4 and §6 for the precise byte-level rules.
Reference implementation
clients/oxford-demo-issuer exercises this exact flow end-to-end against a running Attest ID
deployment, built entirely on the public clients/byok-sdk-java SDK.