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,129 @@
# `@suaveplan/license` — generate
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Top-level license-key generation, payload construction, and parse-only
inspection. This is the place that brings together every lower module:
it packs the payload, signs the packed bytes with secp256k1 (via
`@suaveplan/crypto-curve`) over a SHA-256 digest, optionally truncates
the signature, Base32-encodes `payload || signature`, applies the
signature-derived obfuscation pass, and emits OpenTelemetry counters
and span attributes — all behind a single async function.
`createPayload` is the quick-builder: callers supply a partial
`QuickLicenseOptions` bag, and the function fills sensible defaults
(version, signingKeyId, tier, etc.) and computes the time-encoding
fields from `expirationDays` / `useAbsoluteTime`. The `Rng` for the
nonce and the `Clock` for absolute-day expirations are both injectable
so tests run deterministically.
`parseLicense` is the inverse of generation up to (but not including)
signature verification — it deobfuscates, Base32-decodes, and unpacks
the payload, returning the three byte slices plus the decoded record.
It does NOT verify the signature or check expiration; for that, use
`validateLicense`.
## Features
- `generateLicense` — full generation pipeline with Zod boundary
validation, OTel emission, and `AbortSignal` propagation.
- `createPayload` — quick-builder with injectable `Rng` / `Clock` and
Zod-validated input.
- `parseLicense` — pure decode-and-unpack, returning the byte slices
plus the decoded payload.
- Wire-format bit-identical to upstream `@suaveplan/license`.
## Usage: Basic
```ts
import { secp256k1 } from "@suaveplan/crypto-curve";
import {
createPayload,
generateLicense,
} from "@suaveplan/license/generate/generate";
const s = secp256k1.randomPrivateKey();
const result = await generateLicense(
createPayload({ productId: 5, expirationDays: 365 }),
{ signingKey: { s }, useFullSignature: false }
);
console.log(result.key); // "XYZ12-ABCDE-..."
```
## Usage: Advanced
Deterministic generation with `Rng` injection, `AbortSignal`, and
truncated signature for a compact key:
```ts
import { secp256k1 } from "@suaveplan/crypto-curve";
import type { Rng } from "@suaveplan/types";
import { LicenseError } from "@suaveplan/license";
import {
createPayload,
generateLicense,
parseLicense,
} from "@suaveplan/license/generate/generate";
const rng: Rng = { nextUint32: () => 0x123, nextFloat: () => 0 };
const payload = createPayload(
{ productId: 7, expirationDays: 90 },
{ rng }
);
const ctrl = new AbortController();
try {
const r = await generateLicense(payload, {
signingKey: { s: secp256k1.randomPrivateKey() },
useFullSignature: false,
signal: ctrl.signal,
});
const parsed = parseLicense(r.key);
console.log("payload bytes:", parsed.payloadBytes.length); // 9
console.log("signature bytes:", parsed.signatureBytes.length); // 20
} catch (e) {
if (e instanceof LicenseError && e.code === "LICENSE.ABORTED") {
console.log("cancelled");
}
}
```
## API Reference
### `generateLicense(payload, options): Promise<LicenseGenerationResult>`
Pack + sign + encode + obfuscate. Options: `signingKey` (required),
`signingKeyIdOverride?`, `useFullSignature?` (default `true` at the
function level; `LicenseManager` overrides to `false`), `signal?`,
`rng?` (forwarded to `createPayload` when this function is invoked from
the manager). Throws `LicenseError(LICENSE.INVALID_INPUT)` on schema
failure, `LicenseError(LICENSE.ABORTED)` on a pre-aborted signal.
### `createPayload(options?, injection?): LicensePayload`
Quick-builder; defaults: `productId=1`, `tier=PackageTier.BASIC`,
`expirationDays=0` (perpetual), `flags=0`, `features=0`,
`useAbsoluteTime=false`. `injection.rng` drives the 10-bit nonce;
`injection.clock` drives `getDayNumber` when `useAbsoluteTime`.
### `parseLicense(licenseKey): LicenseParsedResult`
Deobfuscate + Base32-decode + slice + unpack. Throws
`LicenseError(LICENSE.MALFORMED)` when the decoded byte length is
less than `PAYLOAD_BYTES + 1`, or when the Base32 decode surfaces an
`INVALID_ENCODING`.
## Implementation Notes
The signature scheme is upstream-compatible: SHA-256 over the 9-byte
payload, then secp256k1 sign over the digest. The truncated path
slices the first 20 bytes of the resulting compact signature; the full
path keeps all 64. This means a key generated by upstream verifies
under suaveplan and vice versa as long as the same SignKey/PublicKey
byte material is used. The `Rng` default uses `crypto.getRandomValues`
to build a 32-bit unsigned integer, which is then masked to the 10-bit
nonce by the caller.