first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
# @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.
<!-- begin:auto-docs -->
## 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…
<!-- end:auto-docs -->
## 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.