export the content-encryption primitives (#556) - #560
Merged
Conversation
#556 found the timelocked-content feature unreachable — ~1,100 lines and five test files with no CLI command, no client method and no export. The crypto half is resolved here; the timelock half is deliberately NOT. WHY THEY SPLIT. `crypto/aead` and `crypto/kem` are general primitives, and their reachability does not depend on what happens to the feature that currently consumes them. They are also BYTE-COMPATIBLE WITH PHOTONIC WALLET (`packages/lib/src/encryption.ts`), verified against the draft-irtf-cfrg-xchacha-03 Appendix A.3.1 vector — encrypting content with pyrxd and decrypting it in Photonic is the actual use case, which is what makes them worth a public surface rather than an internal detail. THE EXPORTED SET IS CHOSEN TO BE USABLE, not just to clear the allowlist. Same rule the file already applies to GlyphMinter/JsonFilePendingStore: `encrypt_ chunked` returns a `ChunkedCiphertext` that `decrypt_chunked` consumes, and `wrap_cek_x25519` needs a recipient public key a caller can only derive with `x25519_public_key`. Exporting the four flagged functions alone would have cleared the allowlist and left the entry point unreachable in practice — which is the defect class the export exists to answer, reintroduced by the fix for it. `test_the_exported_crypto_surface_is_USABLE_end_to_end_from_the_top_level` drives a full wrap -> encrypt -> unwrap -> decrypt round trip using nothing but names off the top-level package. Verified by planting: removing the `x25519_public_key` export fails it, while every per-name `hasattr` check still passes — the difference between a symbol being importable and a feature being reachable. It also pins that `decrypt_chunked` takes `plaintext_hash` SEPARATELY from the ciphertext object that carries one. That reads as an awkward signature and is the design: the hash is the on-chain commitment, so sourcing it from the ciphertext would let whoever supplied the ciphertext also supply the value it is authenticated against. A wrong commitment must fail, and now does in a test. The four crypto allowlist entries are removed; the stale-entry ratchet was verified to fire by re-adding one. The seven timelock entries keep their place but no longer say "needs triage" — they record the decision: KEPT UNWIRED, and what remains is a product call (a CLI/GlyphClient entry point, or deleting ~700 lines and three test files), not a code one. Lazy-loading preserved: a new test pins that `import pyrxd` still does not pull the Cryptodome cipher backend. VERIFIED as CI runs it (`-o "addopts="`, so nothing is deselected): pytest tests/ --cov=pyrxd --cov-fail-under=85 -> 10,515 passed, 90.42% pytest tests/security/ --cov-fail-under=100 -> 866 passed, 100% ruff check + format --check, mypy -> clean Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves the crypto half of #556. The timelock half is deliberately left unwired — see below.
#556 found the timelocked-content feature unreachable: ~1,100 lines and five test files with no CLI command, no
GlyphClientmethod, and no export. It listed three defensible outcomes (wire / export / delete) and called the choice a product one. This splits it.Why the two halves split
crypto/aeadandcrypto/kemare general primitives. Their reachability does not depend on what happens to the feature that currently consumes them, so they need no product decision.They are also byte-compatible with Photonic Wallet (
packages/lib/src/encryption.ts), verified against the draft-irtf-cfrg-xchacha-03 Appendix A.3.1 vector. Encrypting Glyph content with pyrxd and decrypting it in Photonic — or the reverse — is the actual use case, and that is what makes them worth a public surface rather than an internal detail.The exported set is chosen to be usable
Same rule
__init__.pyalready applies toGlyphMinter/JsonFilePendingStore: exporting an entry point without what it needs leaves it unreachable.Exporting the four flagged functions alone would have cleared the allowlist and left the feature unreachable in practice — the defect class the export exists to answer, reintroduced by the fix for it.
test_the_exported_crypto_surface_is_USABLE_end_to_end_from_the_top_leveldrives a full wrap → encrypt → unwrap → decrypt round trip using nothing but names off the top-level package. Verified by planting: dropping thex25519_public_keyexport fails it while every per-namehasattrcheck still passes.It also pins a design detail worth not losing:
decrypt_chunkedtakesplaintext_hashseparately from the ciphertext object that carries one. That reads as an awkward signature and is deliberate — the hash is the on-chain commitment, so taking it from the ciphertext would let whoever supplied the ciphertext also supply the value it is authenticated against. A wrong commitment must fail, and now does in a test.Allowlist
Four crypto entries removed. The stale-entry ratchet was verified to fire by re-adding one.
The seven
glyph/timelock*entries stay, but no longer say "needs triage" — they now record the decision: kept unwired on purpose, and what remains is a product call (a CLI/GlyphCliententry point, or deleting ~700 lines and three test files), not a code one. Deliberately loud rather than quietly dropped.Verified
As CI runs it (
-o "addopts=", so the 187 integration tests are not silently deselected):pytest tests/ --cov=pyrxd --cov-fail-under=85pytest tests/security/ --cov-fail-under=100ruff check+ruff format --checkmypy(the three CI paths)A new test also pins that
import pyrxdstill does not eagerly pull the Cryptodome cipher backend — the lazy-export discipline the package docstring documents for the browser inspect tool.