| description | High-performance native Node.js DIDKit plugin using N-API |
|---|
@learncard/didkit-plugin-node is a high-performance native Node.js plugin that provides the same DID and Verifiable Credential functionality as the standard WASM-based DIDKit plugin, but compiled directly to native code using Rust and N-API.
This plugin is ideal for server-side applications where performance is critical, such as credential issuance services, verification endpoints, and batch processing workflows.
{% hint style="info" %} When to use this plugin:
- β Node.js server applications (Express, Fastify, NestJS, etc.)
- β Serverless functions (AWS Lambda, Vercel, etc.)
- β CLI tools and scripts
- β High-throughput credential processing
When to use the WASM plugin instead:
- Browser applications
- React Native / mobile apps
- Environments without native compilation support {% endhint %}
pnpm i @learncard/didkit-plugin-node{% hint style="warning" %} This package includes prebuilt binaries for common platforms (Linux x64, macOS x64/ARM64, Windows x64). If your platform isn't supported, you'll need Rust installed to compile from source during installation. {% endhint %}
import { initLearnCard } from '@learncard/init';
import { getDidKitPlugin } from '@learncard/didkit-plugin-node';
// Replace the default WASM plugin with the native plugin
const learnCard = await initLearnCard({ seed: 'your-seed-phrase', didkit: 'node' });
// Or use it directly
const didKitPlugin = await getDidKitPlugin();
const learnCard = await (await initLearnCard({ custom: true })).addPlugin(await getDidKitPlugin());const didKitPlugin = await getDidKitPlugin(
undefined, // InitInput (optional, for WASM compatibility)
false // allowRemoteContexts - whether to fetch unknown JSON-LD contexts
);| Parameter | Type | Default | Description |
|---|---|---|---|
input |
InitInput |
undefined |
Optional initialization input (for API compatibility with WASM version) |
allowRemoteContexts |
boolean |
false |
Whether to allow fetching unknown JSON-LD contexts over HTTP |
Like the WASM version, the native plugin uses SSI's 50+ embedded JSON-LD contexts. Both versions avoid HTTP requests for common contexts:
- W3C Credentials v1/v2
- W3C Security contexts
- DID Core contexts
- Open Badges v2/v3
- CLR v2
- LearnCard Boosts contexts
- And many more...
Unknown contexts can optionally be fetched via HTTP if allowRemoteContexts is enabled.
All operations that may involve network requests (DID resolution, credential issuance/verification) are truly async and won't block Node.js's event loop:
// These operations run on a separate thread pool
const credential = await learnCard.invoke.issueCredential(unsignedVC, options, keypair);
const result = await learnCard.invoke.verifyCredential(credential);
const didDocument = await learnCard.invoke.resolveDid('did:web:example.com');The plugin includes built-in caching for did:web resolution to avoid redundant HTTP requests:
// Clear the DID web cache when needed (e.g., in tests)
await learnCard.invoke.clearDidWebCache();The native plugin exposes the same API as the WASM DIDKit plugin:
// Generate Ed25519 keypair from seed bytes
const ed25519Key = learnCard.invoke.generateEd25519KeyFromBytes(seedBytes);
// Generate secp256k1 keypair from seed bytes
const secp256k1Key = learnCard.invoke.generateSecp256k1KeyFromBytes(seedBytes);// Convert key to DID
const did = learnCard.invoke.keyToDid('key', keypair);
// Get verification method for a key
const vm = await learnCard.invoke.keyToVerificationMethod('key', keypair);
// Resolve a DID to its document
const didDoc = await learnCard.invoke.resolveDid(did);
// Full DID resolution with metadata
const resolution = await learnCard.invoke.didResolver(did);// Issue a credential
const signedVC = await learnCard.invoke.issueCredential(
unsignedCredential,
{ proofFormat: 'jwt' }, // or 'lds' for JSON-LD signatures
keypair
);
// Verify a credential
const result = await learnCard.invoke.verifyCredential(signedVC);
// Returns: { checks: [...], warnings: [...], errors: [...] }// Issue a presentation
const signedVP = await learnCard.invoke.issuePresentation(
unsignedPresentation,
{ proofFormat: 'jwt', challenge: 'abc123' },
keypair
);
// Verify a presentation
const result = await learnCard.invoke.verifyPresentation(signedVP);// Create encrypted JWE
const jwe = await learnCard.invoke.createJwe(cleartext, recipientDids);
// Decrypt JWE
const decrypted = await learnCard.invoke.decryptJwe(jwe, privateKeys);
// DAG-JWE for structured data
const dagJwe = await learnCard.invoke.createDagJwe(jsonValue, recipientDids);
const decryptedValue = await learnCard.invoke.decryptDagJwe(dagJwe, privateKeys);The biggest advantage of native over WASM is cold start time - critical for serverless functions, CLI tools, and worker threads:
| Mode | Cold Start | Speedup |
|---|---|---|
| WASM | ~1109ms | baseline |
| Native | ~62ms | 17.9x faster |
WASM requires fetching and compiling the binary on every fresh Node process, while native loads instantly. You save ~1047ms per cold start.
Once initialized, native is still faster for most operations:
| Operation | WASM (ms) | Native (ms) | Speedup |
|---|---|---|---|
| issueCredential() | 2.97 | 1.77 | 1.67x faster |
| verifyCredential() | 1.97 | 1.68 | 1.18x faster |
| createJwe() | 0.34 | 0.10 | 3.29x faster |
| decryptJwe() | 0.10 | 0.05 | 2.15x faster |
| createDagJwe() | 0.23 | 0.11 | 2.16x faster |
| decryptDagJwe() | 0.08 | 0.05 | 1.53x faster |
{% hint style="info" %} When to use native: Server environments where cold start matters (serverless, CLI tools, worker threads).
Run benchmarks yourself: cd tests/benchmarking && pnpm benchmark
{% endhint %}
Prebuilt binaries are included for:
| Platform | Architecture | Status |
|---|---|---|
| Linux | x64 (glibc) | β Included |
| Linux | x64 (musl) | β Included |
| Linux | ARM64 | β Included |
| macOS | x64 (Intel) | β Included |
| macOS | ARM64 (Apple Silicon) | β Included |
| Windows | x64 | β Included |
For other platforms, the package will attempt to compile from source during installation (requires Rust toolchain).
If you see an error like "No .node binary found":
# Rebuild the native module
cd node_modules/@learncard/didkit-plugin-node
pnpm buildIf compilation fails, ensure you have the Rust toolchain installed:
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Then reinstall the package
pnpm installFor serverless environments with limited memory, the native plugin is more memory-efficient than WASM. However, if you encounter issues:
// Explicitly clear caches between invocations
await learnCard.invoke.clearDidWebCache();The native plugin is a drop-in replacement for the WASM plugin. Simply change the import:
// Before (WASM)
import { getDidKitPlugin } from '@learncard/didkit-plugin';
// After (Native)
import { getDidKitPlugin } from '@learncard/didkit-plugin-node';All method signatures and return types are identical.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Node.js Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β TypeScript Plugin Layer β
β (Promise wrappers, JSON serialization) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β N-API Bridge β
β (napi-rs async/sync bindings) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Rust Native Code β
β βββββββββββββββ βββββββββββββββ ββββββββββββββββββββββββ
β β DIDKit β β SSI β β Embedded Contexts ββ
β β (signing) β β (resolvers) β β (50+ JSON-LD) ββ
β βββββββββββββββ βββββββββββββββ ββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Tokio Runtime β
β (async HTTP, thread pool for CPU work) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- DIDKit (WASM) - The browser-compatible WASM version
- Plugin System - How plugins work
- Verifiable Credentials - VC concepts
- DIDs - DID concepts