Skip to content

feat: add export and post-quantum signing (ML-DSA-65)#11

Open
akeemjenkins wants to merge 6 commits into
mainfrom
feat/export-pq-sign
Open

feat: add export and post-quantum signing (ML-DSA-65)#11
akeemjenkins wants to merge 6 commits into
mainfrom
feat/export-pq-sign

Conversation

@akeemjenkins

@akeemjenkins akeemjenkins commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • okf export exports an entire OKF bundle as a deterministic .okf tar.gz archive. The archive is byte-reproducible (sorted paths, zeroed timestamps, fixed file modes) so the same bundle contents always produce the same bytes, on any machine. Output includes a manifest with per-file SHA-256 hashes, file count, total bytes, and archive hash.

  • okf sign provides post-quantum digital signatures for exported archives using ML-DSA-65 (FIPS 204, formerly CRYSTALS-Dilithium) via cloudflare/circl.

    • keygen generates an ML-DSA-65 key pair (private key stored as the 32-byte FIPS 204 seed)
    • sign signs the archive's SHA-256 hash with the private key
    • verify checks the signature with the signer's public key and confirms the archive is untampered

Both commands are registered in the schema (okf schema) for agentic-first discovery.

Design note

An earlier revision of this branch used ML-KEM-768 + HPKE. ML-KEM is a key-encapsulation mechanism, not a signature scheme: anyone holding the public key could seal a fresh "signature" over a tampered archive, and verification required the private key. That revision was replaced with ML-DSA-65, the NIST post-quantum signature standard, with the conventional key roles: private key signs, public key verifies. Verification always uses the public key supplied on the command line, never the one embedded in the signature file.

Test plan

  • 5 export tests: archive creation, determinism, hidden dir skipping, non-directory error, manifest file hashes
  • 6 sign tests: key generation, sign+verify happy path, tampered archive, wrong key, forged signature (public key cannot sign; corrupted signature rejected), wrong algorithm
  • Full test suite passes with -race, go vet and golangci-lint clean on PR packages
  • End-to-end: export twice produces identical bytes; export -> keygen -> sign -> verify cycle passes; tampered archive fails verification with a structured error; signing with the public key is rejected
okf export ./testdata/valid -o bundle.okf
okf sign bundle.okf keygen
okf sign bundle.okf sign --priv <private-key-hex> -o sig.json
okf sign bundle.okf verify --pub <public-key-hex> --sig sig.json
# { "verified": true, "algorithm": "ML-DSA-65" }

🤖 Generated with Claude Code

akeemjenkins and others added 2 commits July 1, 2026 22:03
- export: deterministic tar.gz archive of entire bundle with per-file
  SHA-256 manifest and archive hash
- sign: post-quantum authentication via ML-KEM-768 (FIPS 203) and HPKE
  (RFC 9180) using only the Go standard library (crypto/hpke +
  crypto/mlkem) — no external dependencies
- sign keygen: generate ML-KEM-768 key pair
- sign sign: seal archive hash with HPKE using public key
- sign verify: open HPKE ciphertext with private key, confirm archive
  hash matches (detects tampering)
- Schema entries for both commands (agentic-first discovery)
- 9 new tests (5 export, 4 sign) covering happy path, determinism,
  tampered archive, wrong key, hidden dir skipping
- Updated README with export + sign documentation

End-to-end verified: export → keygen → sign → verify cycle works.
The previous scheme sealed the archive hash with HPKE using the
recipient's PUBLIC key. Since anyone with the public key can seal,
signatures were forgeable by design: an attacker could tamper with the
archive and mint a fresh valid "signature" with the very key published
in the signature file. Verification also required the private key,
so third parties could not verify at all.

Replace it with ML-DSA-65 (FIPS 204, formerly CRYSTALS-Dilithium) via
cloudflare/circl: the private key (stored as the 32-byte FIPS 204 seed)
signs the archive's SHA-256 hash, and anyone with the public key
verifies. Verification uses only the key passed on the command line,
never the one embedded in the signature file. CLI flags swap
accordingly: sign --priv, verify --pub.

Also fix export correctness issues:
- take tar entry size from the bytes actually read, not a separate Stat
- fix tar mode to 0644 so archives are byte-reproducible across machines
- do not skip the bundle root itself when it is a hidden directory
- use bytes.Buffer instead of strings.Builder for binary data

Adds a forgery regression test proving the public key cannot produce a
valid signature, plus wrong-algorithm and tampered-signature tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@akeemjenkins akeemjenkins changed the title feat: add export and post-quantum signing feat: add export and post-quantum signing (ML-DSA-65) Jul 10, 2026
akeemjenkins and others added 4 commits July 9, 2026 23:44
PR #10 merged a second identical copy of the testBundle helper into
internal/validate/validate_test.go, so main currently fails go vet and
golangci-lint with a redeclaration error. Merge main and keep a single
copy of the helper. Also update the README test count.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exclude G703 alongside the existing G304 exclusion: reading and writing
user-supplied paths from argv is the point of a CLI. Suppress G117 on
KeyPairToJSON, where emitting the private key is the purpose of keygen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant