diff --git a/docs/superpowers/plans/2026-07-24-instance-licensing.md b/docs/superpowers/plans/2026-07-24-instance-licensing.md index ebab166..24ce8dc 100644 --- a/docs/superpowers/plans/2026-07-24-instance-licensing.md +++ b/docs/superpowers/plans/2026-07-24-instance-licensing.md @@ -849,7 +849,7 @@ Expected: no output. - [ ] **Step 6: Rate-limit the licence endpoint** There is no oracle here worth protecting — the blob is signed public data — but an -unbounded verify endpoint is an unbounded CPU endpoint, and ed25519 verification +unbounded verify endpoint is an unbounded CPU endpoint, and signature verification is not free. Add to `server/internal/api/licence.go`: diff --git a/docs/superpowers/plans/2026-07-24-licensing-core.md b/docs/superpowers/plans/2026-07-24-licensing-core.md index 1f837bd..231527e 100644 --- a/docs/superpowers/plans/2026-07-24-licensing-core.md +++ b/docs/superpowers/plans/2026-07-24-licensing-core.md @@ -2,11 +2,11 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Add `shared/license`, the ed25519-signed licence payload with offline verification, plus `lkctl` to issue licences by hand. +**Goal:** Add `shared/license`, the signed licence payload with offline verification, plus `lkctl` to issue licences by hand. **Architecture:** A new package inside the existing `shared` module, so the control plane (verifier) and the future admin service (signer) read one struct definition. Signing is excluded from the server binary by a build tag; the trusted public keys are compiled in as a slice. No network calls anywhere — verification checks a signature, an expiry, a deployment mode and an instance ID, and asks nobody's permission. -**Tech Stack:** Go 1.26, `github.com/hyperboloide/lk` (ed25519 + base32), the existing `shared` module. +**Tech Stack:** Go 1.26, `github.com/hyperboloide/lk` (ECDSA P-384 + SHA-256, base32), the existing `shared` module. **No automated tests.** Verification is by compiler, a probe command, and `lkctl` round trips at the command line. Every task ends with observable output. @@ -168,7 +168,7 @@ Create `shared/license/license.go`: // Package license defines the Vantage licence payload and its offline // verification. // -// A licence is an ed25519-signed blob. The server checks a signature, an +// A licence is a signed blob (ECDSA P-384 with SHA-256). The server checks a signature, an // expiry, a deployment mode and an instance ID, and asks nobody's permission. // That buys air-gapped self-hosting and means no instance depends on the // licensing service being reachable. diff --git a/docs/superpowers/specs/2026-07-24-admin-backend-design.md b/docs/superpowers/specs/2026-07-24-admin-backend-design.md index dcd3de2..41828e9 100644 --- a/docs/superpowers/specs/2026-07-24-admin-backend-design.md +++ b/docs/superpowers/specs/2026-07-24-admin-backend-design.md @@ -326,7 +326,7 @@ remembering — the same deny-by-default reasoning as spec 2's middleware. | `ADMIN_MONGO_URI` | yes | admin's own database; name read from the URI path, refused if absent | | `CONTROL_MONGO_URI` | yes | control-plane database, for injection and cloud auth | | `REDIS_ADDR` | yes | sessions | -| `LICENSE_SIGNING_KEY` | yes | ed25519 private key hex. **Boot fails without it** — a licensing service that cannot sign is worse than one that is down, because it looks healthy | +| `LICENSE_SIGNING_KEY` | yes | ECDSA P-384 private key, base32 (lk PrivateKey.ToB32String). **Boot fails without it** — a licensing service that cannot sign is worse than one that is down, because it looks healthy | | `PUBLIC_URL` | yes | for verification and licence links | | `SMTP_*` | yes | licence delivery | | `ADMIN_ORIGIN` | yes | CORS allow-list | diff --git a/docs/superpowers/specs/2026-07-24-licensing-core-design.md b/docs/superpowers/specs/2026-07-24-licensing-core-design.md index b8ec673..a23d06a 100644 --- a/docs/superpowers/specs/2026-07-24-licensing-core-design.md +++ b/docs/superpowers/specs/2026-07-24-licensing-core-design.md @@ -52,7 +52,7 @@ shared/license/ └── license_test.go ``` -Uses `github.com/hyperboloide/lk` (ed25519, base32 encoding). +Uses `github.com/hyperboloide/lk` (ECDSA P-384 with SHA-256, base32 encoding). ### Payload @@ -197,7 +197,7 @@ which is the signal that a clock is badly off. // To rotate: prepend the new key, ship a server release, then reissue. // Remove a retired key only after every license signed with it has expired. var trustedPublicKeys = []string{ - "", + "", } ``` @@ -215,7 +215,7 @@ Key generation is a documented one-off: go run ./shared/license/cmd/lkgen keypair ``` -prints a private key hex for the vault and a public key hex to paste into +prints a private key (base32) for the vault and a public key (base32) to paste into `keys.go`. The private key is stored in a password manager and in the admin service's environment. **If it is lost, no new licenses can be issued for any existing customer without a server release.** Back it up in two places. diff --git a/docs/superpowers/specs/README.md b/docs/superpowers/specs/README.md index 9bbe909..bde49aa 100644 --- a/docs/superpowers/specs/README.md +++ b/docs/superpowers/specs/README.md @@ -31,7 +31,7 @@ service, because a self-hosted instance has no row in the cloud database at all. ## Decisions that everything else follows from -**Licences are offline-verified signed blobs.** ed25519 via +**Licences are offline-verified signed blobs.** ECDSA P-384 with SHA-256 via `github.com/hyperboloide/lk`, public key compiled into the server, no phone-home anywhere. This buys air-gapped self-hosting and means no Vantage instance ever depends on the licensing service being up. It costs revocation: a licence is