# @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](./src/codec/base32/base32.md) — Crockford-style Base32 codec used to serialise the packed payload and signature into a printable license-key string. Th… - [base32](./src/codec/base32/base32.md) — Crockford-style Base32 codec used to serialise the packed payload and signature into a printable license-key string. Th… - [bitpack](./src/codec/bitpack/bitpack.md) — Bit-precise pack/unpack primitives used to serialise the 72-bit license payload into 9 bytes. Plain `DataView` / typed-… - [bitpack](./src/codec/bitpack/bitpack.md) — Bit-precise pack/unpack primitives used to serialise the 72-bit license payload into 9 bytes. Plain `DataView` / typed-… - [obfuscation](./src/codec/obfuscation/obfuscation.md) — Per-key character obfuscation pass run after Base32 encoding. The first 15 non-dash characters of the encoded key (whic… - [obfuscation](./src/codec/obfuscation/obfuscation.md) — Per-key character obfuscation pass run after Base32 encoding. The first 15 non-dash characters of the encoded key (whic… - [payload](./src/codec/payload/payload.md) — `packPayload` and `unpackPayload` are the 72-bit payload codec on top of the bitpack primitives. They emit exactly 9 by… - [payload](./src/codec/payload/payload.md) — `packPayload` and `unpackPayload` are the 72-bit payload codec on top of the bitpack primitives. They emit exactly 9 by… - [constants](./src/constants/constants.md) — Wire-format constants for the license package. Every value here is part of the binary contract: the per-field b… - [constants](./src/constants/constants.md) — Wire-format constants for the license package. Every value here is part of the binary contract: the per-field b… - [flags](./src/flags/flags.md) — Bitmask helpers for the 5-bit `flags` field (`LicenseFlags`) and the 19-bit `featureMask` field. The flags field stores… - [flags](./src/flags/flags.md) — Bitmask helpers for the 5-bit `flags` field (`LicenseFlags`) and the 19-bit `featureMask` field. The flags field stores… - [generate](./src/generate/generate.md) — Top-level license-key generation, payload construction, and parse-only inspection. This is the place that brings togeth… - [generate](./src/generate/generate.md) — Top-level license-key generation, payload construction, and parse-only inspection. This is the place that brings togeth… - [manager](./src/manager/manager.md) — High-level `LicenseManager` class that bundles a public-key registry, an optional signing key, default `signingKeyId` /… - [manager](./src/manager/manager.md) — High-level `LicenseManager` class that bundles a public-key registry, an optional signing key, default `signingKeyId` /… - [otel](./src/otel/instruments.md) — OpenTelemetry instrumentation for the license package. Registers a single `@opentelemetry/api` meter (`@suaveplan/licen… - [instruments](./src/otel/instruments.md) — OpenTelemetry instrumentation for the license package. Registers a single `@opentelemetry/api` meter (`@suaveplan/licen… - [revocation-store](./src/revocation/revocation-store/revocation-store.md) — Pluggable revocation blocklist for the license package. The `RevocationStore` interface defines three methods (`add`, `… - [revocation-store](./src/revocation/revocation-store/revocation-store.md) — Pluggable revocation blocklist for the license package. The `RevocationStore` interface defines three methods (`add`, `… - [schemas](./src/schemas/schemas.md) — Zod schemas for every public input boundary in the license package. `generateLicense`, `validateLicense`, `createPayloa… - [schemas](./src/schemas/schemas.md) — Zod schemas for every public input boundary in the license package. `generateLicense`, `validateLicense`, `createPayloa… - [time](./src/time/time.md) — Time-encoding helpers for the four `TimeEncodingType` variants. The 72-bit payload encodes time in 18 bits with an addi… - [time](./src/time/time.md) — Time-encoding helpers for the four `TimeEncodingType` variants. The 72-bit payload encodes time in 18 bits with an addi… - [types](./src/types/license.md) — Public type surface for the license package, ported verbatim from the upstream `index.types.ts` at the wire-format leve… - [license](./src/types/license.md) — Public type surface for the license package, ported verbatim from the upstream `index.types.ts` at the wire-format leve… - [validate](./src/validate/validate.md) — License-key validation: parse, verify signature, check version, check expiration, surface every failure as a typed enum… - [validate](./src/validate/validate.md) — License-key validation: parse, verify signature, check version, check expiration, surface every failure as a typed enum… - [error-codes](./src/error-codes.md) — This module owns the stable error-code registry for `@suaveplan/license`. It exposes `LICENSE_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 `LicenseError` registry, OTel counters/histograms, Zod boundary validation, injectable `Clock` / `Rng`, `AbortSignal` propagation, pluggable `RevocationStore`. ## Quick-start ```ts 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](src/error-codes.md) | | Constants | `./constants/constants` | [src/constants/constants.md](src/constants/constants.md) | | Types + enums | `./types/license` | [src/types/license.md](src/types/license.md) | | Base32 codec | `./codec/base32/base32` | [src/codec/base32/base32.md](src/codec/base32/base32.md) | | Bitpack | `./codec/bitpack/bitpack` | [src/codec/bitpack/bitpack.md](src/codec/bitpack/bitpack.md) | | Payload codec | `./codec/payload/payload` | [src/codec/payload/payload.md](src/codec/payload/payload.md) | | Obfuscation | `./codec/obfuscation/obfuscation` | [src/codec/obfuscation/obfuscation.md](src/codec/obfuscation/obfuscation.md) | | Time helpers | `./time/time` | [src/time/time.md](src/time/time.md) | | Flag helpers | `./flags/flags` | [src/flags/flags.md](src/flags/flags.md) | | Zod schemas | `./schemas/schemas` | [src/schemas/schemas.md](src/schemas/schemas.md) | | OTel instruments | `./otel/instruments` | [src/otel/instruments.md](src/otel/instruments.md) | | Generate | `./generate/generate` | [src/generate/generate.md](src/generate/generate.md) | | Validate | `./validate/validate` | [src/validate/validate.md](src/validate/validate.md) | | Manager | `./manager/manager` | [src/manager/manager.md](src/manager/manager.md) | | Revocation | `./revocation/revocation-store/revocation-store` | [src/revocation/revocation-store/revocation-store.md](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. ```ts 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: ```text | 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.