Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 KiB
@suaveplan/license - Compact, offline-verifiable, secp256k1-signed software-license-key format: 72-bit bitpacked payload (version + signing-key id + product id + tier + flags + time encoding + time value + feature mask + nonce) plus full or truncated (20-byte) signature, Crockford-Base32 encoded with signature-derived obfuscation. suaveplan-enterprise additions: SuaveplanError-typed errors, OTel counters/histograms/spans, Zod boundary validation, injectable Clock/Rng, AbortSignal propagation, pluggable RevocationStore. Universal.
Compact, offline-verifiable, secp256k1-signed software-license-key
format. Bit-for-bit wire-format compatible with upstream
@suaveplan/license. runs unchanged in Bun, Node, and the browser.
Documentation tree
- base32 — Crockford-style Base32 codec used to serialise the packed payload and signature into a printable license-key string. Th…
- base32 — Crockford-style Base32 codec used to serialise the packed payload and signature into a printable license-key string. Th…
- bitpack — Bit-precise pack/unpack primitives used to serialise the 72-bit license payload into 9 bytes. Plain
DataView/ typed-…- bitpack — Bit-precise pack/unpack primitives used to serialise the 72-bit license payload into 9 bytes. Plain
DataView/ typed-…
- bitpack — Bit-precise pack/unpack primitives used to serialise the 72-bit license payload into 9 bytes. Plain
- obfuscation — Per-key character obfuscation pass run after Base32 encoding. The first 15 non-dash characters of the encoded key (whic…
- obfuscation — Per-key character obfuscation pass run after Base32 encoding. The first 15 non-dash characters of the encoded key (whic…
- payload —
packPayloadandunpackPayloadare the 72-bit payload codec on top of the bitpack primitives. They emit exactly 9 by…- payload —
packPayloadandunpackPayloadare the 72-bit payload codec on top of the bitpack primitives. They emit exactly 9 by…
- payload —
- constants — Wire-format constants for the license package. Every value here is part of the binary contract: the per-field b…
- constants — Wire-format constants for the license package. Every value here is part of the binary contract: the per-field b…
- flags — Bitmask helpers for the 5-bit
flagsfield (LicenseFlags) and the 19-bitfeatureMaskfield. The flags field stores…- flags — Bitmask helpers for the 5-bit
flagsfield (LicenseFlags) and the 19-bitfeatureMaskfield. The flags field stores…
- flags — Bitmask helpers for the 5-bit
- generate — Top-level license-key generation, payload construction, and parse-only inspection. This is the place that brings togeth…
- generate — Top-level license-key generation, payload construction, and parse-only inspection. This is the place that brings togeth…
- manager — High-level
LicenseManagerclass that bundles a public-key registry, an optional signing key, defaultsigningKeyId/…- manager — High-level
LicenseManagerclass that bundles a public-key registry, an optional signing key, defaultsigningKeyId/…
- manager — High-level
- otel — OpenTelemetry instrumentation for the license package. Registers a single
@opentelemetry/apimeter (`@suaveplan/licen…- instruments — OpenTelemetry instrumentation for the license package. Registers a single
@opentelemetry/apimeter (`@suaveplan/licen…
- instruments — OpenTelemetry instrumentation for the license package. Registers a single
- revocation-store — Pluggable revocation blocklist for the license package. The
RevocationStoreinterface defines three methods (add, `…- revocation-store — Pluggable revocation blocklist for the license package. The
RevocationStoreinterface defines three methods (add, `…
- revocation-store — Pluggable revocation blocklist for the license package. The
- schemas — Zod schemas for every public input boundary in the license package.
generateLicense,validateLicense, `createPayloa…- schemas — Zod schemas for every public input boundary in the license package.
generateLicense,validateLicense, `createPayloa…
- schemas — Zod schemas for every public input boundary in the license package.
- time — Time-encoding helpers for the four
TimeEncodingTypevariants. The 72-bit payload encodes time in 18 bits with an addi…- time — Time-encoding helpers for the four
TimeEncodingTypevariants. The 72-bit payload encodes time in 18 bits with an addi…
- time — Time-encoding helpers for the four
- types — Public type surface for the license package, ported verbatim from the upstream
index.types.tsat the wire-format leve…- license — Public type surface for the license package, ported verbatim from the upstream
index.types.tsat the wire-format leve…
- license — Public type surface for the license package, ported verbatim from the upstream
- validate — License-key validation: parse, verify signature, check version, check expiration, surface every failure as a typed enum…
- validate — License-key validation: parse, verify signature, check version, check expiration, surface every failure as a typed enum…
- error-codes — This module owns the stable error-code registry for
@suaveplan/license. It exposesLICENSE_CODES(a frozen `defineC…
Highlights
- 72-bit bitpacked payload (version + signing-key id + product id
- tier + flags + time encoding + time value + feature mask + nonce) encoded as 9 bytes.
- Truncated 20-byte signature by default keeps keys ~50-60
characters; opt into 64-byte full signatures with
useFullSignature: true. - Crockford-style Base32 alphabet (no
I/L/O/U— paste- friendly). - Signature-derived obfuscation pass rotates the first 15 payload characters by per-license offsets drawn from the signature.
- Four time-encoding strategies:
NONE,ABSOLUTE_DAYS,RELATIVE_DAYS,ABSOLUTE_TIMESTAMP. - 19-bit feature mask + 5-bit flag bitfield (TRIAL, UPGRADE, MACHINE_LOCKED, SUBSCRIPTION, EDUCATIONAL).
- Enterprise extras: typed
LicenseErrorregistry, OTel counters/histograms, Zod boundary validation, injectableClock/Rng,AbortSignalpropagation, pluggableRevocationStore.
Quick-start
import { secp256k1 } from "@suaveplan/crypto-curve";
import { LicenseManager, PackageTier } from "@suaveplan/license";
// 1. Generate a signing key (typically done once, off-line)
const secret = secp256k1.randomPrivateKey();
const publicKey = { p: secp256k1.getPublicKey(secret) };
// 2. Bind the manager to that key
const m = new LicenseManager({
signingKey: { s: secret, p: publicKey.p },
publicKeys: { 0: publicKey },
});
// 3. Mint a license key
const { key } = await m.generate({
productId: 5,
tier: PackageTier.PRO,
expirationDays: 365,
});
// 4. Hand the key to the user, validate it on incoming requests
const v = await m.validate(key);
if (v.success) {
console.log(`Tier ${v.details.packageTier}, expires ${v.details.expirationDate}`);
}
Modules
Every feature module is a tree-shakeable subpath export:
| Module | Subpath | Docs |
|---|---|---|
| Error codes | ./error-codes/error-codes |
src/error-codes.md |
| Constants | ./constants/constants |
src/constants/constants.md |
| Types + enums | ./types/license |
src/types/license.md |
| Base32 codec | ./codec/base32/base32 |
src/codec/base32/base32.md |
| Bitpack | ./codec/bitpack/bitpack |
src/codec/bitpack/bitpack.md |
| Payload codec | ./codec/payload/payload |
src/codec/payload/payload.md |
| Obfuscation | ./codec/obfuscation/obfuscation |
src/codec/obfuscation/obfuscation.md |
| Time helpers | ./time/time |
src/time/time.md |
| Flag helpers | ./flags/flags |
src/flags/flags.md |
| Zod schemas | ./schemas/schemas |
src/schemas/schemas.md |
| OTel instruments | ./otel/instruments |
src/otel/instruments.md |
| Generate | ./generate/generate |
src/generate/generate.md |
| Validate | ./validate/validate |
src/validate/validate.md |
| Manager | ./manager/manager |
src/manager/manager.md |
| Revocation | ./revocation/revocation-store/revocation-store |
src/revocation/revocation-store/revocation-store.md |
The package root (.) re-exports every public symbol from each module
for ergonomic single-import usage.
Benchmarks
Captured with mitata on Intel(R) Xeon(R) CPU E5-2696 v4 @ 2.20GHz,
Bun 1.3.14 (Linux x64). Run yourself with
bun src/__benchmarks__/license.bench.ts.
| Benchmark | Mean | p75 | p99 |
|---|---|---|---|
base32Encode (73 bytes) |
38.09 µs | 42.82 µs | 134.18 µs |
base32Decode (73 bytes) |
65.06 µs | 70.70 µs | 189.76 µs |
packPayload |
733.58 ns | 744.46 ns | 1.37 µs |
unpackPayload |
1.16 µs | 1.18 µs | 1.75 µs |
generateLicense (truncated) |
67.64 µs | 73.84 µs | 242.36 µs |
validateLicense (truncated) |
40.52 µs | 42.73 µs | 116.04 µs |
A full generate + validate round-trip completes in ~108 µs on this hardware, comfortably below the 1 ms p99 budget targeted by the upstream enterprise audit.
Revocation
Revocation is an opt-in pluggable layer. Wire any store implementing
the RevocationStore interface (sync or async); the package ships a
synchronous in-memory store with optional LRU eviction.
import {
createMemoryRevocationStore,
fingerprintLicense,
LicenseManager,
} from "@suaveplan/license";
const store = createMemoryRevocationStore({ maxEntries: 100_000 });
const m = new LicenseManager({ signingKey, publicKeys, revocationStore: store });
// Block a compromised key
await m.revoke(fingerprintLicense(compromisedKey));
// Subsequent validates of that key return { error: 'REVOKED' }
Wire format
The on-the-wire layout is unchanged from upstream @suaveplan/license.
Each 9-byte payload packs nine fields MSB-first:
| v(3) | kid(5) | pid(6) | tier(4) | flg(5) | te(2) | timeValue(18) | featureMask(19) | nonce(10) |
total = 72 bits
A complete key encodes payload || signature (29 bytes with truncated
signature, 73 bytes with full signature) as Base32, dashes inserted
every 5 chars for legibility, then runs the signature-derived
obfuscation pass over the first 15 payload chars. The
seed-from-key-region trick (chars 16-30 carry the obfuscation seed)
breaks the circular dependency between "decode needs the seed" and
"the seed comes from a decoded signature".
License
MIT.