Email security@usewombat.eu with the subject line [SECURITY] @usewombat/crypto.
We will acknowledge within 48 hours and aim to ship a fix within 14 days for critical issues. We do not currently operate a bug bounty programme, but we publicly credit researchers who report responsibly.
Please do not open a public GitHub issue for security vulnerabilities.
@usewombat/crypto is the cryptographic foundation for Wombat ID. It handles:
- Ed25519 key generation and
did:keyencoding/resolution - W3C Verifiable Credential 2.0 issuance and verification
- (Phase 2) BBS+ selective disclosure signing and proof derivation
User identity is cryptographically owned by the user. A did:key DID is
derived from the user's public key. If Wombat disappears, the DID persists.
No registry, no Wombat server, no revocation authority.
Credentials are self-contained. A VC issued by this library can be verified
by anyone who can resolve the issuer's did:key — which requires no network
call and no Wombat infrastructure.
Verification never throws on invalid input. All verification paths return a
typed VerifyResult. Callers must not branch on exception type for security
decisions.
This library does not protect against:
- A compromised issuer private key — if the issuer key is stolen, fraudulent credentials can be issued. Issuer key management is the responsibility of the Wombat ID auth server.
- A compromised user device — if the device's secure enclave is compromised,
the user's
did:keyprivate key may be extractable. - Side-channel attacks on key generation — we use
@noble/ed25519which does not guarantee constant-time operations on all platforms.
The eddsa-rdfc-2022 cryptosuite specification requires RDF Dataset Normalisation (RDNA/URDNA2015) for the canonicalisation step.
This library uses a deterministic canonical JSON serialisation instead:
keys sorted lexicographically at every level, no whitespace, undefined values
omitted. This is documented in src/vc/issue.ts.
Why: A full JSON-LD processor is a significant dependency with its own attack surface and historical security issues. Wombat credentials have a fixed, well-known schema — no arbitrary JSON-LD extensions, no blank nodes, no graph merging. The canonical JSON approach is auditable by inspection and produces stable, unambiguous output for this constrained document type.
Interoperability note: Credentials issued by this library are not byte-for-byte compatible with implementations using full RDNA canonicalisation. They are structurally valid W3C VC 2.0 documents. If full RDNA interop is required in future, the signing input can be upgraded without changing the proof envelope structure.
The same deterministic canonicalisation is used for BBS+ message encoding in
buildMessagesFromSubject (src/bbs/utils.ts). This is critical: the message
bytes must be identical at signing time (browser) and verification time
(server). Plain JSON.stringify is NOT deterministic — it preserves JavaScript
object key insertion order, which can be silently reordered by database storage
(e.g. PostgreSQL JSONB sorts keys alphabetically). The canonicalise utility
recursively sorts object keys to prevent this mismatch.
Ed25519 (EdDSA over Curve25519). Implemented by @noble/ed25519 — pure
TypeScript, no native dependencies, audited by Cure53 (2022).
We use @noble/ed25519 v2, which requires an explicit SHA-512 implementation.
We provide @noble/hashes/sha512 — same author, same audit posture.
did:key using base58btc multibase with the 0xed01 multicodec prefix for
Ed25519 public keys. This follows the did:key method specification v0.9.
All cryptographic operations use the @noble/* family (Paul Miller):
@noble/ed25519— Cure53 audit, 2022@noble/hashes— Cure53 audit, 2022@noble/bls12-381— (Phase 2) — Cure53 audit, 2022
These libraries have no native dependencies and run identically in Node.js
and the browser. They do not use eval, dynamic require, or any mechanism
that could be exploited for code injection.
multiformats and uint8arrays are encoding utilities with no cryptographic
operations.
-
src/did/key.ts— multicodec encoding/decoding. Does the varint prefix correctly identify Ed25519 keys? Does resolution correctly reject non-Ed25519 keys? -
src/vc/issue.ts—buildSigningInput— is the canonicalisation deterministic and unambiguous? Can two different documents produce the same signing input? -
src/vc/verify.ts—validateStructure— are all required fields checked? Is there any path that reaches signature verification with a structurally incomplete credential? -
src/vc/verify.ts—verifyProof— does the reconstructed signing input exactly match the one produced inbuildSigningInput? Any divergence here would allow tampered credentials to verify. -
The deny-by-default property —
verifyCredentialmust never return{ valid: true }for a credential that was tampered with, expired, or issued by a different key. The test suite covers these cases but a researcher should attempt to find inputs not covered by the tests.