Skip to content

perf(keys): borrow key material instead of cloning per encode/decode (#121) - #127

Merged
ZhuchkaTriplesix merged 1 commit into
devfrom
issue/121-borrow-keys
Sep 28, 2026
Merged

ZhuchkaTriplesix merged 1 commit into
devfrom
issue/121-borrow-keys

Conversation

@ZhuchkaTriplesix

Copy link
Copy Markdown
Member

Problem

EncodingKeyMaterial::encoding_key() / DecodingKeyMaterial::decoding_key() returned an owned clone of the jsonwebtoken key on every encode/decode call (a full 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 — the documented, recommended usage for asymmetric keys — that's pure repeated copying of the same immutable data.

Fix

EncodingKey and DecodingKey are now #[pyclass(frozen)]. Frozen pyclasses let pyo3 hand out &T via Bound::get() with no runtime borrow-flag check, and — because the reference's lifetime is tied to the calling scope rather than to a PyRef guard — it can be threaded straight through py.detach() without cloning. 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, which has no persistent key object) builds an owned one, same as before. Both Deref to the underlying jsonwebtoken key 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 the Arc for.

Raw str/bytes HMAC keys are unaffected — there's no persistent key object to borrow from in that case.

No behavioral change: neither class had a &mut self pymethod to begin with, so frozen has no Python-visible effect beyond enabling the borrow.

Results

Pre-built EncodingKey, 2048-bit RSA key, release build:

#120 (before this PR) after
RS256 encode 201 µs 187 µs
vs. raw cryptography sign (190 µs) +6% -1.6%

Checklist

  • No key clone on the encode / decode / decode_verified_complete hot paths when a prebuilt key object is passed (structurally: BorrowedEncodingKey::Ref / BorrowedDecodingKey::Ref hold a &'a reference, there is no .clone() on that path)
  • EncodingKey / DecodingKey are #[pyclass(frozen)]
  • cargo build/clippy --all-targets -D warnings/fmt --check clean with both aws_lc_rs (default) and --no-default-features --features rust_crypto
  • cargo test (11/11) and pytest (258 passed, 1 skipped) green
  • mypy clean
  • No behavior change; before/after numbers in this PR and CHANGELOG.md

Closes #121.

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
@ZhuchkaTriplesix
ZhuchkaTriplesix merged commit fae3e6c into dev Sep 28, 2026
7 checks passed
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