Skip to main content
Both the TypeScript and Python SDKs include advanced features for enterprise use cases. This page covers features beyond basic validation, signing, and verification.

Selective Disclosure (SD-JWT)

SD-JWT allows credential holders to selectively disclose specific claims without revealing the entire credential. This is useful for privacy-preserving verification scenarios.
import {
  createSdJwt,
  createPresentation,
  verifySdJwt,
} from '@beltic/sdk';

// Create SD-JWT with selectively disclosable claims
const sdJwt = await createSdJwt(credential, privateKey, {
  alg: 'EdDSA',
  issuerDid: 'did:web:beltic.com',
  subjectDid: 'did:web:developer.example.com',
  disclosablePaths: [
    '/legalName',
    '/incorporationJurisdiction',
    '/kybTier',
  ],
});

// Create presentation with selected disclosures
const presentation = createPresentation(sdJwt, [
  '/kybTier',  // Only disclose KYB tier
]);

// Verify SD-JWT presentation
const result = await verifySdJwt(presentation, {
  keyResolver: async (header) => publicKey,
});

console.log(result.disclosedClaims);  // Only contains kybTier

Use Cases

  • Minimal disclosure: Share only KYB tier without revealing legal name
  • Regulatory compliance: Disclose different fields to different verifiers
  • Privacy protection: Prevent data correlation across platforms

Replay Protection

Prevent credential tokens from being reused in replay attacks by tracking seen JTI (JWT ID) values.
import {
  InMemoryJtiStore,
  checkReplay,
  ReplayDetectedError,
} from '@beltic/sdk';

// Create JTI store (use Redis/database in production)
const jtiStore = new InMemoryJtiStore(300); // 5 min TTL

// Check for replay before processing
async function processToken(token: string) {
  const decoded = decodeToken(token);
  const jti = decoded.payload.jti;
  const exp = decoded.payload.exp;
  
  try {
    await checkReplay(jtiStore, jti, exp);
    // Process token...
  } catch (error) {
    if (error instanceof ReplayDetectedError) {
      console.error(`Replay attack detected: ${error.jti}`);
      throw error;
    }
    throw error;
  }
}

// With middleware
import { createReplayMiddleware } from '@beltic/sdk';

const replayMiddleware = createReplayMiddleware(jtiStore);

app.use('/api/verify', replayMiddleware);

Production Considerations

Use a persistent store in production. InMemoryJtiStore is suitable for development but data is lost on restart. Use Redis, PostgreSQL, or a similar persistent store.

Content Integrity

Compute and verify cryptographic hashes of content to ensure integrity.
import {
  computeContentHash,
  verifyContentHash,
  createHashClaim,
  verifyHashClaim,
} from '@beltic/sdk';

// Compute hash of content
const content = Buffer.from(JSON.stringify(credential));
const hash = computeContentHash(content, 'sha-256');

// Verify hash
const isValid = verifyContentHash(content, hash, 'sha-256');

// Create hash claim for embedding in credentials
const hashClaim = createHashClaim(content, 'sha-256');
// { algorithm: "sha-256", hash: "base64url-encoded-hash" }

// Verify hash claim
const claimValid = verifyHashClaim(content, hashClaim);

Credential Hash Chains

Create an auditable chain of credentials where each entry is linked to the previous one via cryptographic hashes.
import {
  CredentialChain,
  verifyChainIntegrity,
} from '@beltic/sdk';

// Create a new chain
const chain = new CredentialChain();

// Add credentials to chain
const entry1 = chain.add(credential1, token1);
const entry2 = chain.add(credential2, token2);
const entry3 = chain.add(credential3, token3);

// Each entry has:
// - credentialHash: hash of the credential
// - previousHash: hash of previous entry (null for first)
// - chainHash: combined hash linking to previous

// Get proof for audit
const proof = chain.getProof();

// Verify chain integrity
const isIntact = chain.verify();

// Verify from serialized entries
const entries = [...]; // Load from database
const valid = verifyChainIntegrity(entries);

Use Cases

  • Audit trail: Track all credential issuances in tamper-evident log
  • Version history: Link credential updates to previous versions
  • Compliance: Demonstrate credential lifecycle for regulators

Multi-Signature Support

Credentials requiring approval from multiple parties can use multi-signature support.
import {
  MultiSigCredential,
  addSignature,
  verifyMultiSignatures,
} from '@beltic/sdk';

// Create multi-sig credential
const multiSig = new MultiSigCredential(credential);

// Add signatures from multiple parties
await multiSig.addSignature(issuerPrivateKey, 'issuer', {
  alg: 'EdDSA',
  issuerDid: 'did:web:beltic.com',
  subjectDid: 'did:web:developer.example.com',
});

await multiSig.addSignature(auditorPrivateKey, 'auditor', {
  alg: 'EdDSA',
  issuerDid: 'did:web:auditor.example.com',
  subjectDid: 'did:web:developer.example.com',
});

// Get all signatures
const signatures = multiSig.getSignatures();
// [{ signerId: 'issuer', ... }, { signerId: 'auditor', ... }]

// Verify all signatures
const result = await multiSig.verifyAll(async (signerId) => {
  // Return public key for each signer
  if (signerId === 'issuer') return issuerPublicKey;
  if (signerId === 'auditor') return auditorPublicKey;
  throw new Error(`Unknown signer: ${signerId}`);
});

console.log(result.allValid);  // true if all signatures valid
console.log(result.results);   // Per-signature results

Use Cases

  • Multi-party verification: Require both issuer and auditor signatures
  • Threshold signatures: Require N-of-M signatures
  • Regulatory approval: Compliance officer co-signs credentials

Audit Logging

Track credential operations with pluggable audit handlers.
import {
  AuditLogger,
  AuditEventType,
  ConsoleAuditHandler,
  InMemoryAuditHandler,
  getAuditLogger,
  setAuditLogger,
} from '@beltic/sdk';

// Create logger with handlers
const logger = new AuditLogger();
logger.addHandler(new ConsoleAuditHandler());

const memoryHandler = new InMemoryAuditHandler();
logger.addHandler(memoryHandler);

// Set as global logger
setAuditLogger(logger);

// Log events manually
logger.log({
  type: AuditEventType.CREDENTIAL_VERIFIED,
  timestamp: new Date(),
  credentialId: 'abc-123',
  action: 'verify',
  actor: 'did:web:verifier.example.com',
  details: { algorithm: 'EdDSA' },
});

// Access in-memory events
console.log(memoryHandler.events);

// SDK operations auto-log when logger is set
const verified = await verifyCredential(token, options);
// Automatically logs CREDENTIAL_VERIFIED event

Event Types

EventDescription
CREDENTIAL_ISSUEDNew credential was signed
CREDENTIAL_VERIFIEDCredential signature was verified
CREDENTIAL_REVOKEDCredential was revoked
SIGNATURE_CREATEDSignature was created
SIGNATURE_VERIFIEDSignature was verified
TRUST_CHAIN_VERIFIEDTrust chain was verified
POLICY_VIOLATIONPolicy check failed

Cloud Signers

Use cloud KMS services for production key management.
import {
  LocalSigner,
  AwsKmsSigner,
  signWithSigner,
} from '@beltic/sdk';

// Local signer (for development)
const localSigner = new LocalSigner(privateKey, 'EdDSA');

// AWS KMS signer (for production)
const kmsSigner = new AwsKmsSigner({
  keyId: 'arn:aws:kms:us-east-1:123456789:key/abc-123',
  region: 'us-east-1',
  algorithm: 'ECDSA_SHA_256',  // ES256
});

// Sign with any signer
const signature = await signWithSigner(kmsSigner, data);

// Get signer info
const info = kmsSigner.getInfo();
console.log(info.algorithm);  // 'ES256'
console.log(info.keyId);      // KMS key ARN
Cloud signers keep private keys in the cloud. The private key never leaves the KMS service, providing better security for production deployments.

Canonicalization (RFC 8785)

Deterministic JSON serialization for consistent hashing.
import {
  canonicalize,
  canonicalizeToString,
  computeCanonicalHash,
} from '@beltic/sdk';

// Canonicalize JSON object
const obj = { b: 2, a: 1 };
const canonical = canonicalizeToString(obj);
// '{"a":1,"b":2}'  // Keys sorted, no extra whitespace

// Compute hash of canonical form
const hash = computeCanonicalHash(credential, 'sha-256');

Error Sanitization

Sanitize error messages to prevent information leakage in production.
import {
  sanitizeMessage,
  createSanitizedError,
  SanitizedError,
} from '@beltic/sdk';

try {
  await verifyCredential(token, options);
} catch (error) {
  // Get safe message for external users
  const publicMessage = getPublicMessage(error);
  
  // Create sanitized error for API response
  const sanitized = createSanitizedError(error);
  
  res.status(401).json({
    error: sanitized.message,  // Safe for public
    code: sanitized.code,
  });
  
  // Log full error internally
  logger.error('Verification failed', { error });
}

See Also