Skip to content

export the content-encryption primitives (#556) - #560

Merged
Zyrtnin merged 1 commit into
mainfrom
feat/556-export-crypto-primitives
Sep 1, 2026
Merged

export the content-encryption primitives (#556)#560
Zyrtnin merged 1 commit into
mainfrom
feat/556-export-crypto-primitives

Conversation

@Zyrtnin

@Zyrtnin Zyrtnin commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

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 GlyphClient method, 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/aead and crypto/kem are 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__.py already applies to GlyphMinter/JsonFilePendingStore: exporting an entry point without what it needs leaves it unreachable.

encrypt_chunked  -> ChunkedCiphertext -> decrypt_chunked      (+ EncryptedChunk)
wrap_cek_x25519  -> WrappedCEK        -> unwrap_cek_x25519
x25519_public_key                                             (recipient pubkey)

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_level drives a full wrap → encrypt → unwrap → decrypt round trip using nothing but names off the top-level package. Verified by planting: dropping the x25519_public_key export fails it while every per-name hasattr check still passes.

It also pins a design detail worth not losing: decrypt_chunked takes plaintext_hash separately 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/GlyphClient entry 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):

gate result
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 + ruff format --check clean
mypy (the three CI paths) no issues

A new test also pins that import pyrxd still does not eagerly pull the Cryptodome cipher backend — the lazy-export discipline the package docstring documents for the browser inspect tool.

#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>
@Zyrtnin
Zyrtnin merged commit 0b89a4a into main Sep 1, 2026
19 checks passed
@Zyrtnin
Zyrtnin deleted the feat/556-export-crypto-primitives branch September 1, 2026 06:58
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