docs: correct licence scheme to ECDSA P-384 with SHA-256

hyperboloide/lk signs with ECDSA P-384 and SHA-256, not ed25519, and encodes
keys as base32 rather than hex. Probed in task 1 of the licensing-core plan.
Design is unaffected — only the prose was wrong.
This commit is contained in:
2026-07-24 14:56:36 +01:00
parent f10fe61916
commit a50219c0d9
5 changed files with 9 additions and 9 deletions
@@ -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`:
@@ -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.
@@ -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 |
@@ -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{
"<hex ed25519 public key>",
"<base32 ECDSA P-384 public key>",
}
```
@@ -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.
+1 -1
View File
@@ -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