Repository navigation
perf(keys): borrow key material instead of cloning per encode/decode (#121) - #127
Merged
Merged
Conversation
EncodingKeyMaterial::encoding_key() and DecodingKeyMaterial::decoding_key() returned an owned clone of the jsonwebtoken key on every call (an owned copy of the DER bytes or HMAC secret), and jsonwebtoken's signer/verifier factory cloned it again internally. For a prebuilt EncodingKey/DecodingKey reused across many calls, that is pure overhead: the same key material gets copied on every single encode/decode. EncodingKey and DecodingKey are now `#[pyclass(frozen)]`. Frozen pyclasses let pyo3 hand out `&T` via `Bound::get()` with no runtime borrow-flag check and no clone, and that reference's lifetime is tied to the calling scope rather than to a PyRef guard, so it can be threaded straight through to `py.detach()`. encoding_key_from_py / decoding_key_from_py now return a small BorrowedEncodingKey/BorrowedDecodingKey enum that either borrows the key straight out of a frozen pyclass, or (for a raw HMAC secret with no persistent key object) owns a freshly built one; both Deref to the jsonwebtoken key type, so call sites are unchanged. The RSA fast path added for #120 also drops its Arc<RsaKeyPair> clone in favor of a plain borrow, since the frozen class covers the same lifetime need. Raw str/bytes HMAC keys are unaffected: there is no persistent key object to borrow from in that case, so an owned key is still built per call, same as before. No behavioural change; the classes had no &mut self pymethods to begin with, so `frozen` has no Python-visible effect beyond enabling the borrow. Measured (2048-bit RSA key, pre-built EncodingKey): RS256 encode 201us -> 187us (drops the now-unnecessary Arc clone from #120's cached signing key), now ~1% over raw cryptography sign (190us). Closes #121
3 tasks
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
EncodingKeyMaterial::encoding_key()/DecodingKeyMaterial::decoding_key()returned an owned clone of thejsonwebtokenkey on everyencode/decodecall (a full copy of the DER bytes or HMAC secret), andjsonwebtoken's signer/verifier factory cloned it again internally. For a prebuiltEncodingKey/DecodingKeyreused across many calls — the documented, recommended usage for asymmetric keys — that's pure repeated copying of the same immutable data.Fix
EncodingKeyandDecodingKeyare now#[pyclass(frozen)]. Frozen pyclasses let pyo3 hand out&TviaBound::get()with no runtime borrow-flag check, and — because the reference's lifetime is tied to the calling scope rather than to aPyRefguard — it can be threaded straight throughpy.detach()without cloning.encoding_key_from_py/decoding_key_from_pynow return a smallBorrowedEncodingKey/BorrowedDecodingKeyenum that either borrows the key straight out of a frozen pyclass, or (for a raw HMAC secret, which has no persistent key object) builds an owned one, same as before. BothDerefto the underlyingjsonwebtokenkey type, so almost every call site is unchanged.The RSA fast path from #120 also drops its
Arc<RsaKeyPair>clone in favor of a plain borrow, since the frozen class already covers the lifetime it needed theArcfor.Raw
str/bytesHMAC keys are unaffected — there's no persistent key object to borrow from in that case.No behavioral change: neither class had a
&mut selfpymethod to begin with, sofrozenhas no Python-visible effect beyond enabling the borrow.Results
Pre-built
EncodingKey, 2048-bit RSA key, release build:encodecryptographysign (190 µs)Checklist
encode/decode/decode_verified_completehot paths when a prebuilt key object is passed (structurally:BorrowedEncodingKey::Ref/BorrowedDecodingKey::Refhold a&'areference, there is no.clone()on that path)EncodingKey/DecodingKeyare#[pyclass(frozen)]cargo build/clippy --all-targets -D warnings/fmt --checkclean with bothaws_lc_rs(default) and--no-default-features --features rust_cryptocargo test(11/11) andpytest(258 passed, 1 skipped) greenmypycleanCHANGELOG.mdCloses #121.