Repository navigation
feat(python): pyrind, rind's API for Python - #459
Merged
Merged
Conversation
Quest-Claim: 00ff78d095c98fad
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README carries the benchmark against pyca/cryptography, hashlib, and hmac, run on a held host. pyca/cryptography's last Windows on Arm wheel lacks ML-KEM, so that runner tests pyrind against Wycheproof, the README, and its stubs only. rustfmt applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- hmac.Context.sign signs a copy, so dropping the box wipes the heap state in place rather than freeing it unwiped after a move. - Arguments take the raw bytes of any buffer, whatever its item format, as hashlib does. - HKDF and PBKDF2 refuse a length no hash allows before allocating it. - A partly uploaded release stays unpublished until its sdist, which twine uploads last, is on PyPI, and the retry skips what is already there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Arguments other than bytes are copied by one buffer export and PyBuffer_ToContiguous, so no Rust slice covers memory another thread may write on free-threaded CPython, and an exporter allowing one export works. - PBKDF2 refuses a length past its own hash's limit before allocating. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The binding's wipes are rind's own drops, so they follow rind's wipe rule; say so rather than claim an unconditional wipe. Plan the CI that loads the musl and armv7 wheels, and say why the PyPI publisher stays a maintainer step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # sh/dispatch.sh
kixelated
marked this pull request as ready for review
November 7, 2026 19:04
|
Your OpenCode Go account is out of credit. Top it up, then re-trigger Pullfrog. OpenCode Go's response
|
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 36 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
This was referenced Nov 7, 2026
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.

Problem
Python can't use rind. Quest: quest/m2/adoption/pyrind.md. Goal:
pip install pyrindgives rind's own API (AEADs, hashes, HMAC, HKDF, PBKDF2, X25519, Ed25519, ECDSA and ECDH, RSA, ML-KEM) from wheels for every platform rind supports, and a published per-call comparison againstcryptography,hashlib, andhmac.Approach
Crate. New
rind-pythoncrate (publish = false), built by pyo3 0.30 and maturin as thepyrindpackage.pyrind.aead,.agreement,.cpu,.digest,.hkdf,.hmac,.kem,.pbkdf2,.rsa,.signature(with.signand.verify).legacysubmodule, as in rind.Errors and import. Every rind error is a
pyrind.Errorsubclass.KeyRejectedcarries rind's reason.ImportErrornaming any CPU feature rind requires that is missing.Memory. Every rind value is boxed inside its Python object. pyo3 places a class's contents in memory CPython aligns to only 16 bytes, and ML-KEM's AVX2 loads need 32 (PyO3/pyo3#6526).
mlkem_basemul_k3. A regression test now holds 32 keys at once.Dropexactly when rind wipes. The published wheels build rind with its default features and forward no feature, as rind's other adapters do. On today'smainthat build wipes on drop; once moq-dev/rind#461 makes the wipe opt-in, the wheels skip it, as rind's default build does. A source build can turn it on withmaturin build --features rind/wipeafter feat: wiping keys on drop is opt-in, under awipefeature #461.Inputs, outputs, and the GIL.
bytesbuffers are copied first, so no other thread can mutate them mid-call.bytes, with no zero-fill pass.Wheels (
python.yml): abi3 for CPython 3.11 and later, on these targets:There is also an sdist. Every runner that can load its wheel runs the unit tests, Wycheproof, the README example, and mypy stubtest. The workflow runs nightly, on dispatch, and from
release.yml.Release.
rind-pythonfrom git tags (git_only), so the next release PR carries 0.1.0 with a CHANGELOG.just release,sh/pyrind-unpublished.shreports a tag that PyPI lacks.release.ymlthen builds the wheels viapython.ymland publishes them by trusted publishing, from the existingpublishenvironment, with no token.Checks. A
pymodule injust checkbuilds the wheel and runs the tests and stubtest wheneverrind-python/changes.Impact
admin/scan-licensenow fails ifrind-python/LICENSEdrifts fromrind/LICENSE.Per-call numbers
Measured on a held host and published in rind-python/README.md:
On an Intel Core i7-8700 (Coffee Lake, 2017), Linux 6.18, CPython 3.14.8,
against cryptography 50.0.2 on OpenSSL 3.5.9, one call at a time with its
inputs in hand, fastest of seven runs:
pyrind wins most where protocols spend their calls: small messages and per-message keys. On 16 KiB the two converge, and RSA signing is the same exponentiation in both.
Choices made without asking
aadandlabelare keyword-only, so they can't be swapped with the plaintext.hkdf.Prk.expandreturns anaead.Keyorhmac.Keydirectly, so derived key bytes never reach Python.cipher,key_wrap,constant_time,rand,quic,sign_digest, custom PSS salts, andDit. These are protocol plumbing or covered by Python itself (hmac.compare_digest,os.urandom), and can be added on request.Boxrule above, and oneunsafehelper (bytes_with) that fills a newbyteswithout the zero-fill pass. Its SAFETY comment states the invariant.publishenvironment rather than adding apypione. release-plzgit_only, since the crate is never on crates.io.pycheck coversrind-python/changes only. A rind change that breaks the bindings shows up in the nightlypythonworkflow.win_arm64wheel, 46.0.3, lacks ML-KEM. That runner therefore skips the comparisons against pyca and runs Wycheproof, the README example, and stubtest.Validation
just check origin/mainpasses on fade49a, after merging main, whose newdartmodule sat besidepyinsh/dispatch.sh(rs, msrv, cross, deny, license, py, dart, quest, gh, and the rest).sh/python.sh test: 61 tests (pyca and stdlib comparisons, RFC vectors, and Wycheproof for every family in the API) and stubtest. 80 consecutive clean runs after the alignment fix.hmac.Contextnow wipes its boxed state.release-plz updategives rind-python 0.1.0 with a CHANGELOG.actionlint,sh/pins.sh, andsh/alert.sh checkpass.Follow-ups
Models
Decisions
Settled by the maintainer on 2026-11-07:
admin/ctgrindcovers.hmac.Context.sign's second wipe of the boxed heap copy (rejected: a new in-placesign(&mut self)in rind). No new rind API.publishenvironment for PyPI trusted publishing (rejected: a newpypienvironment). Adding the pending publisher on PyPI stays the maintainer's action. It gates the first release, not this merge, so it lives as aMaintainer:Waiting item in quest/m2/adoption/pyrind.md, not here.wipefeature of its own, and forwards none, matching feat: wiping keys on drop is opt-in, under awipefeature #461's adapters. moq-dev/rind#461 is still open; whichever lands second needs no change to the binding's code. See Wiping under Approach for what the wheels build with.Review
Rewritten by
quest reviewafter each round.01a1b535-f5b4-7bd1-b4e3-ac3d7548ece1, round 5The merge preserves the reviewed Python implementation and both Python/Dart checks, all 61 tests pass against the existing wheel, and no new findings were identified.c32c635 is a conflict-free merge of main onto the reviewed fade49a.