Signing & Verification Workflows
Complete guide to key management, signing credentials, and verification workflows.
This guide covers cryptographic key generation, credential signing, and verification workflows using the Beltic CLI.
Key Management
Generating Keys
The CLI supports two signature algorithms:
EdDSA (Ed25519) - Recommended:
beltic keygen --alg EdDSA --out my-key.pemES256 (P-256) - For NIST compliance:
beltic keygen --alg ES256 --out my-key.pemOutput files:
my-key.pem- Private key (keep secret!)my-key.pub.pem- Public key (share for verification)
Key Storage Best Practices
Development:
mkdir -p ~/.beltic/keys
chmod 700 ~/.beltic/keys
mv my-key.pem ~/.beltic/keys/
chmod 600 ~/.beltic/keys/my-key.pemProduction:
- Use HSM or KMS (AWS KMS, Azure Key Vault, GCP KMS)
- Rotate keys every 90 days
- Never commit keys to version control
Signing Workflows
Developer Credential Signing
beltic sign \
--payload developer-credential.json \
--key ~/.beltic/keys/dev-key.pem \
--out developer-credential.jwt \
--kid did:web:example.com#dev-key-1 \
--issuer did:web:issuer.beltic.dev \
--subject did:web:developer.example.comParameters explained:
--payload: Input credential JSON--key: Private key file--out: Where to save JWS token (algorithm auto-detected from key)--kid: Key ID (appears in JWT header)--issuer: Issuer DID (appears inissclaim)--subject: Subject DID (appears insubclaim)
Agent Credential Signing
beltic sign \
--payload agent-credential.json \
--key ~/.beltic/keys/agent-key.pem \
--out agent-credential.jwt \
--kid did:web:example.com#agent-key-1 \
--subject did:web:agent.example.comNote: Agent credentials require --subject flag because the subject DID differs from the credential's agentId.
Self-Signing Credentials
Self-signing allows developers to sign their own credentials where the issuer and subject are the same DID. This is useful for:
- Development and testing - Quick iteration without external issuer
- Self-hosted agents - Agents that manage their own identity
- Decentralized scenarios - No central authority required
Self-signing a DeveloperCredential:
beltic sign \
--payload developer-credential.json \
--key ~/.beltic/keys/dev-key.pem \
--issuer did:web:developer.example.com \
--subject did:web:developer.example.com \
--kid did:web:developer.example.com#dev-key-1 \
--out self-signed-credential.jwtSelf-signing an AgentCredential:
beltic sign \
--payload agent-credential.json \
--key ~/.beltic/keys/agent-key.pem \
--issuer did:web:agent.example.com \
--subject did:web:agent.example.com \
--kid did:web:agent.example.com#agent-key-1 \
--out self-signed-agent.jwtKey points:
- Issuer DID (
--issuer) and subject DID (--subject) are identical - The credential's
issuerDidandsubjectDidfields should match - Self-signed credentials are cryptographically valid but may have different trust implications
- Verifiers can check if
iss === subto detect self-signed credentials
Verifying self-signed credentials:
beltic verify \
--token self-signed-credential.jwt \
--key dev-key.pub.pemThe verification process is identical - self-signed credentials pass all cryptographic checks.
Custom Claims
Add custom JWT claims:
beltic sign \
--payload credential.json \
--key key.pem \
--out token.jwt \
--issuer did:web:custom-issuer.com \
--audience did:web:platform.example.com \
--kid custom-key-idCustom claims:
--issuer: Override default issuer--audience: Addaudclaim for specific verifier--kid: Custom key identifier
Skip Schema Validation
For debugging only:
beltic sign \
--payload test-credential.json \
--key key.pem \
--skip-schemaWarning: Production credentials must always validate against schema!
Verification Workflows
Basic Verification
beltic verify \
--token credential.jwt \
--key public-key.pub.pemChecks performed:
- JWT format valid
- Signature mathematically valid
- Claims valid (not expired, etc.)
- Schema validation against credential type
Success output:
✓ Signature valid
✓ Claims valid
✓ Schema valid
VALIDFailure output:
✗ Signature verification failed
INVALID: Signature does not matchConstrained Verification
Verify with specific expectations:
beltic verify \
--token credential.jwt \
--key public-key.pub.pem \
--issuer did:web:issuer.beltic.dev \
--audience did:web:platform.example.comAdditional checks:
- Issuer matches expected DID
- Audience matches expected DID
- Rejects credentials from untrusted issuers
Verification from File or String
From file:
beltic verify --token credential.jwt --key key.pub.pemFrom string:
beltic verify --token "eyJhbGci..." --key key.pub.pemFrom stdin:
cat credential.jwt | beltic verify --key key.pub.pemAlgorithm Selection
When to use EdDSA:
- Default choice for most use cases
- Faster signing and verification
- Smaller signatures
- Modern, recommended by cryptographers
When to use ES256:
- Enterprise compliance requirements
- NIST/FIPS mandated environments
- Legacy system compatibility
- Regulatory requirements (some jurisdictions)
Performance comparison:
| Operation | EdDSA | ES256 |
|---|---|---|
| Key generation | 0.5ms | 2ms |
| Signing | 0.3ms | 1.5ms |
| Verification | 0.8ms | 2ms |
| Signature size | 64 bytes | 64 bytes |
Key Rotation
When rotating keys:
# 1. Generate new key
beltic keygen --alg EdDSA --out new-key.pem
# 2. Re-sign all active credentials
for cred in credentials/*.json; do
beltic sign \
--payload "$cred" \
--key new-key.pem \
--out "$(basename "$cred" .json).jwt"
done
# 3. Update DID document with new key
# (Manual step - update did:web document or DID registry)
# 4. Securely delete old key after grace period
shred -u old-key.pemError Handling
Common Signing Errors
Error: "Schema validation failed"
Cause: Credential doesn't match schema
Solution: Run ajv validate first, fix errorsError: "Failed to read key file"
Cause: Wrong path or permissions
Solution: Check file exists and is readableError: "Unsupported algorithm"
Cause: Algorithm not EdDSA or ES256
Solution: Use --alg EdDSA or --alg ES256Common Verification Errors
Error: "Signature invalid"
Cause: Wrong public key or credential modified
Solution: Ensure using matching public keyError: "Token expired"
Cause: Current time > exp claim
Solution: Credential expired, request new oneError: "Issuer mismatch"
Cause: Token issuer != expected issuer
Solution: Verify issuer DID or remove constraintProduction Checklist
Before deploying to production:
- Generate production keys in secure environment
- Store private keys in HSM/KMS
- Set up key rotation schedule (90 days)
- Document key IDs and DID mappings
- Test verification with all expected verifiers
- Set appropriate expiration dates (6-12 months)
- Configure monitoring for signature failures
- Establish key recovery procedures