Repository navigation
[FEATURE] Quantum-safe provider doc signing: ML-DSA via crypto/mldsa, signature embedded in the doc, drop .sig sidecars #482
Description
Replace the Ed25519 + X.509 + .sig sidecar scheme with a quantum-safe signature from the Go stdlib, embedded in the provider document itself. Coordinated with stackql/any-sdk#153, stackql-labs/omnisdk#17 and stackql/stackql#856. stackql has verification off by default (verifyConfig.nopVerify: true), so this rolls out without breaking existing users.
NOTE: Must be compliant with at least FIPS 204.
What changes
-
Algorithm: ML-DSA (FIPS 204) via
crypto/mldsa, new in Go 1.27. Requiresgo 1.27ingo.mod(currentlygo 1.17). -
Signature lives in the doc, hex encoded. No
.sigfiles. Proposed key:x-stackql-signature: alg: ML-DSA-65 kid: stackql-2026-01 sig: <hex>
-
Sign: load YAML -> drop
x-stackql-signature-> RFC 8785 canonical JSON (github.com/gowebpki/jcs, Apache-2.0) ->mldsasign with contextstackql-provider-doc-v1-> write the key back. Verify: pop the key -> same canonicalisation ->mldsa.Verify. -
Keys: the secret is the 32-byte ML-DSA seed (
mldsa.NewPrivateKey(params, seed)). Public keys embedded in the verifier, selected bykid. Rotation is adding akid. No X.509, no signed timestamps, no faketime cert renewals (the cause of the staleedcryptotest fixtures today). -
Coverage unchanged: one signature per doc (
provider.yamland everyservices/*.yaml), so any-sdk's per-doc verification keeps its shape.
One shared package
Signer (CI here) and verifier (any-sdk) must canonicalise byte-for-byte identically. Canonicalise + sign + verify + embedded keyset go in one Go package in this repo (signing/pqsig), which any-sdk already imports. Tests use ephemeral keys; no production key material in fixtures.
Sequence and dependencies
- This repo:
signing/pqsig,go 1.27, CLIsign-doc/verify-doc(in place), tagged release. No dependencies. - any-sdk and stackql: consume the release, verify embedded signatures, fall back to
.sigwhen absent. Tracked in [FEATURE] Verify ML-DSA signatures embedded in provider docs (x-stackql-signature), with .sig fallback any-sdk#153 (current verifier), [FEATURE] Verify ML-DSA signatures embedded in provider docs (x-stackql-signature) when opening a registry tree stackql-labs/omnisdk#17 (successor verifier, omnisdk is replacing any-sdk) and [FEATURE] Verify ML-DSA signatures embedded in provider docs (x-stackql-signature), replacing .sig sidecars stackql#856 (bump, config, docs). Blocked by 1. - This repo: CI signs in place with a new seed secret. Keep emitting
.sig+ Ed25519 until a stackql release with step 2 is out, then drop.sig,edcrypto, X.509 and the Ed25519 secrets. Blocked by 2.
Decisions needed
- Parameter set: Go's package doc says "Most applications should use MLDSA44". ML-DSA-65 is the conservative pick for long-lived archives. Signatures are 2420 / 3309 bytes, double that as hex; negligible against doc sizes.
- Per-doc signatures (above) vs one signature in
provider.yamlover a digest manifest of the service docs. - Canonicalisation edge cases to pin down in the package: YAML 1.1 scalars (
yes/no, octal), floats, ints beyond 2^53, anchors and merge keys, non-string keys, duplicate keys. Reject docs that do not round-trip.
Related: stackql/stackql#832 (verify before extraction and trust-root policy). This issue changes how a doc is signed, not what is verified or when.
Activity
jeffreyaven commentedon Oct 23, 2026
Implemented by #487 with stackql/any-sdk#155, stackql-labs/omnisdk#23, stackql/stackql#869 and stackql/stackql-provider-tests#2.
Approved changes to the proposal: use standard-library ML-DSA-65 and strictly framed exact-byte raw-v1 signatures, not RFC 8785. The corpus has 439 documents with aliases, 13 with numbers outside JSON's safe integer range, and one unquoted date; all 1,968 documents now sign and verify without rewriting their contents. The signing package is an independently publishable nested Go module because the full provider tree exceeds Go's module-zip limit. Legacy signing code is removed; runtime verification remains opt-in.
The new production signing seed is provisioned in Actions and only its public key is committed. Registry CI, including the full signing audit, is green. Merge provider-tests first, then both SDKs, then StackQL AND publish the new binary. Merge this registry PR LAST, after its provider-test downloader receives that binary. Code-only merging does not rewrite deployed provider artifacts; legacy archives need republishing before opting in. Important closure exception: this repository defaults to dev, but the production PR targets main. GitHub will not auto-close this issue from that merge. Closing #482 immediately after merging #487 is now an explicit mandatory step in the PR cutover checklist; no default-branch setting or broad automation was changed.
jeffreyaven commentedon Oct 24, 2026
Implementation merged into main via #487 and synchronized into dev via #488 (c92b7b6). Verified main is an ancestor of dev, the trees are identical, and signing/v0.1.0-alpha01 is preserved in dev history. Normal feature -> dev -> main flow is restored. Verification remains opt-in; publish StackQL v0.13 before the next provider publication. Existing provider revisions acquire ML-DSA signatures incrementally as updated or republished. Post-sync CI is currently running; this closes the implemented migration issue, not a claim that v0.13 has been released.