Skip to content

Add extractable option to generateKeyPair and generateKeyPairSigner#1531

Merged
lorisleiva merged 1 commit into
mainfrom
04-08-add_extractable_option_to_generatekeypair_and_generatekeypairsigner_
Apr 8, 2026
Merged

Add extractable option to generateKeyPair and generateKeyPairSigner#1531
lorisleiva merged 1 commit into
mainfrom
04-08-add_extractable_option_to_generatekeypair_and_generatekeypairsigner_

Conversation

@lorisleiva
Copy link
Copy Markdown
Member

This PR adds an optional extractable boolean argument to generateKeyPair in @solana/keys and threads it through generateKeyPairSigner in @solana/signers. The argument defaults to false, preserving the existing secure-by-default behavior that prevents the bytes of the private key from being visible to JS. When set to true, the generated private key can be exported via crypto.subtle.exportKey(), which is useful for scenarios like persisting a generated key pair. This brings generateKeyPair and generateKeyPairSigner in line with the sibling helpers createKeyPairFromBytes, createKeyPairFromPrivateKeyBytes, createKeyPairSignerFromBytes, and createKeyPairSignerFromPrivateKeyBytes, which already accept an extractable parameter.

…ner`

This PR adds an optional `extractable` boolean argument to `generateKeyPair` in `@solana/keys` and threads it through `generateKeyPairSigner` in `@solana/signers`. The argument defaults to `false`, preserving the existing secure-by-default behavior that prevents the bytes of the private key from being visible to JS. When set to `true`, the generated private key can be exported via `crypto.subtle.exportKey()`, which is useful for scenarios like persisting a generated key pair. This brings `generateKeyPair` and `generateKeyPairSigner` in line with the sibling helpers `createKeyPairFromBytes`, `createKeyPairFromPrivateKeyBytes`, `createKeyPairSignerFromBytes`, and `createKeyPairSignerFromPrivateKeyBytes`, which already accept an `extractable` parameter.
@lorisleiva lorisleiva marked this pull request as ready for review April 8, 2026 13:01
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 8, 2026

🦋 Changeset detected

Latest commit: 9c43c16

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 46 packages
Name Type
@solana/signers Minor
@solana/keys Minor
@solana/kit Minor
@solana/plugin-interfaces Minor
@solana/program-client-core Minor
@solana/react Minor
@solana/wallet-account-signer Minor
@solana/compat Minor
@solana/instruction-plans Minor
@solana/offchain-messages Minor
@solana/rpc-api Minor
@solana/rpc-graphql Minor
@solana/rpc-subscriptions-api Minor
@solana/transaction-confirmation Minor
@solana/transactions Minor
@solana/rpc Minor
@solana/sysvars Minor
@solana/rpc-subscriptions Minor
@solana/accounts Minor
@solana/addresses Minor
@solana/assertions Minor
@solana/codecs-core Minor
@solana/codecs-data-structures Minor
@solana/codecs-numbers Minor
@solana/codecs-strings Minor
@solana/codecs Minor
@solana/errors Minor
@solana/fast-stable-stringify Minor
@solana/functional Minor
@solana/instructions Minor
@solana/nominal-types Minor
@solana/options Minor
@solana/plugin-core Minor
@solana/programs Minor
@solana/promises Minor
@solana/rpc-parsed-types Minor
@solana/rpc-spec-types Minor
@solana/rpc-spec Minor
@solana/rpc-subscriptions-channel-websocket Minor
@solana/rpc-subscriptions-spec Minor
@solana/rpc-transformers Minor
@solana/rpc-transport-http Minor
@solana/rpc-types Minor
@solana/subscribable Minor
@solana/transaction-messages Minor
@solana/webcrypto-ed25519-polyfill Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@bundlemon
Copy link
Copy Markdown

bundlemon Bot commented Apr 8, 2026

BundleMon

Files updated (3)
Status Path Size Limits
keys/dist/index.browser.mjs
2.07KB (-35B -1.63%) -
keys/dist/index.native.mjs
2.06KB (-35B -1.63%) -
keys/dist/index.node.mjs
2.06KB (-35B -1.63%) -
Unchanged files (139)
Status Path Size Limits
@solana/kit production bundle
kit/dist/index.production.min.js
46.27KB -
errors/dist/index.node.mjs
19.34KB -
errors/dist/index.browser.mjs
19.32KB -
errors/dist/index.native.mjs
19.32KB -
rpc-graphql/dist/index.browser.mjs
18.82KB -
rpc-graphql/dist/index.native.mjs
18.81KB -
rpc-graphql/dist/index.node.mjs
18.81KB -
wallet-account-signer/dist/index.node.mjs
16.4KB -
wallet-account-signer/dist/index.browser.mjs
16.37KB -
wallet-account-signer/dist/index.native.mjs
16.37KB -
transaction-messages/dist/index.browser.mjs
11.32KB -
transaction-messages/dist/index.native.mjs
11.32KB -
transaction-messages/dist/index.node.mjs
11.32KB -
instruction-plans/dist/index.browser.mjs
6.58KB -
instruction-plans/dist/index.native.mjs
6.58KB -
instruction-plans/dist/index.node.mjs
6.58KB -
codecs-data-structures/dist/index.browser.mjs
5.04KB -
codecs-data-structures/dist/index.native.mjs
5.03KB -
codecs-data-structures/dist/index.node.mjs
5.03KB -
offchain-messages/dist/index.browser.mjs
4.89KB -
offchain-messages/dist/index.native.mjs
4.89KB -
offchain-messages/dist/index.node.mjs
4.89KB -
transactions/dist/index.browser.mjs
4.07KB -
transactions/dist/index.native.mjs
4.07KB -
transactions/dist/index.node.mjs
4.07KB -
codecs-core/dist/index.browser.mjs
3.62KB -
codecs-core/dist/index.native.mjs
3.62KB -
codecs-core/dist/index.node.mjs
3.62KB -
webcrypto-ed25519-polyfill/dist/index.node.mj
s
3.61KB -
webcrypto-ed25519-polyfill/dist/index.browser
.mjs
3.59KB -
webcrypto-ed25519-polyfill/dist/index.native.
mjs
3.57KB -
rpc-subscriptions/dist/index.browser.mjs
3.37KB -
rpc-subscriptions/dist/index.node.mjs
3.34KB -
rpc-subscriptions/dist/index.native.mjs
3.31KB -
rpc-transformers/dist/index.browser.mjs
3.16KB -
rpc-transformers/dist/index.native.mjs
3.16KB -
rpc-transformers/dist/index.node.mjs
3.16KB -
signers/dist/index.browser.mjs
3.15KB -
signers/dist/index.native.mjs
3.15KB -
signers/dist/index.node.mjs
3.15KB -
react/dist/index.browser.mjs
3.09KB -
react/dist/index.native.mjs
3.09KB -
react/dist/index.node.mjs
3.09KB -
addresses/dist/index.browser.mjs
2.93KB -
addresses/dist/index.native.mjs
2.92KB -
addresses/dist/index.node.mjs
2.92KB -
kit/dist/index.browser.mjs
2.78KB -
kit/dist/index.native.mjs
2.78KB -
kit/dist/index.node.mjs
2.78KB -
codecs-strings/dist/index.browser.mjs
2.55KB -
codecs-strings/dist/index.node.mjs
2.51KB -
codecs-strings/dist/index.native.mjs
2.47KB -
transaction-confirmation/dist/index.node.mjs
2.41KB -
sysvars/dist/index.browser.mjs
2.37KB -
sysvars/dist/index.native.mjs
2.37KB -
sysvars/dist/index.node.mjs
2.37KB -
transaction-confirmation/dist/index.native.mj
s
2.36KB -
transaction-confirmation/dist/index.browser.m
js
2.35KB -
rpc-subscriptions-spec/dist/index.node.mjs
2.18KB -
rpc-subscriptions-spec/dist/index.native.mjs
2.13KB -
rpc-subscriptions-spec/dist/index.browser.mjs
2.13KB -
rpc/dist/index.node.mjs
1.95KB -
codecs-numbers/dist/index.browser.mjs
1.95KB -
codecs-numbers/dist/index.native.mjs
1.95KB -
codecs-numbers/dist/index.node.mjs
1.94KB -
rpc-transport-http/dist/index.browser.mjs
1.91KB -
rpc-transport-http/dist/index.native.mjs
1.9KB -
rpc/dist/index.native.mjs
1.81KB -
rpc/dist/index.browser.mjs
1.8KB -
subscribable/dist/index.node.mjs
1.8KB -
subscribable/dist/index.native.mjs
1.75KB -
subscribable/dist/index.browser.mjs
1.74KB -
rpc-transport-http/dist/index.node.mjs
1.72KB -
rpc-types/dist/index.browser.mjs
1.53KB -
rpc-types/dist/index.native.mjs
1.53KB -
rpc-types/dist/index.node.mjs
1.53KB -
rpc-subscriptions-channel-websocket/dist/inde
x.node.mjs
1.33KB -
rpc-subscriptions-channel-websocket/dist/inde
x.native.mjs
1.27KB -
rpc-subscriptions-channel-websocket/dist/inde
x.browser.mjs
1.26KB -
program-client-core/dist/index.browser.mjs
1.21KB -
program-client-core/dist/index.native.mjs
1.21KB -
program-client-core/dist/index.node.mjs
1.21KB -
options/dist/index.browser.mjs
1.18KB -
options/dist/index.native.mjs
1.18KB -
options/dist/index.node.mjs
1.17KB -
accounts/dist/index.browser.mjs
1.17KB -
accounts/dist/index.native.mjs
1.17KB -
accounts/dist/index.node.mjs
1.16KB -
rpc-api/dist/index.browser.mjs
976B -
rpc-api/dist/index.native.mjs
975B -
rpc-api/dist/index.node.mjs
973B -
compat/dist/index.browser.mjs
969B -
compat/dist/index.native.mjs
968B -
compat/dist/index.node.mjs
966B -
rpc-spec-types/dist/index.browser.mjs
962B -
rpc-spec-types/dist/index.native.mjs
961B -
rpc-spec-types/dist/index.node.mjs
959B -
rpc-subscriptions-api/dist/index.native.mjs
870B -
rpc-subscriptions-api/dist/index.node.mjs
869B -
rpc-subscriptions-api/dist/index.browser.mjs
868B -
rpc-spec/dist/index.browser.mjs
852B -
rpc-spec/dist/index.native.mjs
851B -
rpc-spec/dist/index.node.mjs
850B -
promises/dist/index.browser.mjs
799B -
promises/dist/index.native.mjs
798B -
promises/dist/index.node.mjs
797B -
assertions/dist/index.browser.mjs
783B -
instructions/dist/index.browser.mjs
771B -
instructions/dist/index.native.mjs
770B -
instructions/dist/index.node.mjs
768B -
plugin-core/dist/index.browser.mjs
749B -
plugin-core/dist/index.native.mjs
749B -
plugin-core/dist/index.node.mjs
747B -
fast-stable-stringify/dist/index.browser.mjs
726B -
fast-stable-stringify/dist/index.native.mjs
725B -
assertions/dist/index.native.mjs
724B -
fast-stable-stringify/dist/index.node.mjs
724B -
assertions/dist/index.node.mjs
723B -
programs/dist/index.browser.mjs
329B -
programs/dist/index.native.mjs
327B -
programs/dist/index.node.mjs
325B -
event-target-impl/dist/index.node.mjs
230B -
functional/dist/index.browser.mjs
154B -
functional/dist/index.native.mjs
152B -
text-encoding-impl/dist/index.native.mjs
152B -
functional/dist/index.node.mjs
151B -
codecs/dist/index.browser.mjs
137B -
codecs/dist/index.native.mjs
136B -
codecs/dist/index.node.mjs
134B -
event-target-impl/dist/index.browser.mjs
133B -
ws-impl/dist/index.node.mjs
131B -
text-encoding-impl/dist/index.browser.mjs
122B -
text-encoding-impl/dist/index.node.mjs
119B -
ws-impl/dist/index.browser.mjs
113B -
crypto-impl/dist/index.node.mjs
111B -
crypto-impl/dist/index.browser.mjs
109B -
rpc-parsed-types/dist/index.browser.mjs
66B -
rpc-parsed-types/dist/index.native.mjs
65B -
rpc-parsed-types/dist/index.node.mjs
63B -

Total files change -79B -0.02%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

Copy link
Copy Markdown

@trevor-cortex trevor-cortex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Adds an optional extractable: boolean = false parameter to generateKeyPair (@solana/keys) and threads it through generateKeyPairSigner (@solana/signers). When true, the generated Ed25519 private key can be exported via crypto.subtle.exportKey(). The default remains false, preserving the existing secure-by-default behavior.

This aligns the signatures of generateKeyPair / generateKeyPairSigner with their sibling helpers (createKeyPairFromBytes, createKeyPairFromPrivateKeyBytes, createKeyPairSignerFromBytes, createKeyPairSignerFromPrivateKeyBytes), which already expose the same flag with the same default.

What to watch out for

  • Backwards compatible: the new parameter is optional and defaults to false, so existing call sites keep their current (non-extractable) behavior. The changeset is correctly marked as a minor bump for both packages.
  • Secure-by-default preserved: the old inline comment "Prevents the bytes of the private key from being visible to JS" is now captured in the TSDoc for the extractable parameter, so the security rationale isn't lost.
  • Consistency: parameter name, default, and TSDoc wording match the sibling helpers in packages/keys/src/key-pair.ts.

Test coverage

  • packages/keys/src/__tests__/key-pair-test.ts adds two cases: explicit false and explicit true, asserting the resulting privateKey.extractable flag.
  • packages/signers/src/__tests__/keypair-signer-test.ts updates the existing default-call test to additionally assert generateKeyPair was called with false, and adds a new test verifying that generateKeyPairSigner(true) forwards true to generateKeyPair.

Notes for subsequent reviewers

  • Worth a quick sanity check that the docs site / API reference picks up the new @param extractable TSDoc block for both functions.
  • No runtime changes beyond the new parameter pass-through — the crypto.subtle.generateKey call now receives extractable directly instead of a hardcoded false, which is the only behavioral change.

Looks good to me. 🚀

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

Documentation Preview: https://kit-docs-i95fbygaw-anza-tech.vercel.app

@beeman
Copy link
Copy Markdown
Contributor

beeman commented Apr 8, 2026

Super happy to finally see this as I think it means I can update my answer here 🙏

@lorisleiva lorisleiva requested a review from mcintyre94 April 8, 2026 13:21
@lorisleiva
Copy link
Copy Markdown
Member Author

@beeman Yeah the other factory helpers already have that extractable option anyway so it's more consistent that way and we of course keep the non-extractable behaviour by default. I'm about to add grind and write helpers as well.

@beeman
Copy link
Copy Markdown
Contributor

beeman commented Apr 8, 2026

@lorisleiva amazing! Way better that these are part of the official package so that people don't have to write ad-hoc implementations of this. 🙏

Copy link
Copy Markdown
Member

@mcintyre94 mcintyre94 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy with this, I think it makes sense to have as an option, with false as the default

I think the argument not to do this would be that with the FromBytes helpers, you already have the bytes in insecure JS when you call it, so extracting doesn't add any additional risk. Whereas when you generate, assuming a sane browser implementation, those bytes are never available insecurely.

That said if you have malicious JS in your sandbox (browser extension etc) then it can hijack the crypto APIs anyway so I don't think anything we can do actually holds up to that threat.

@lorisleiva lorisleiva added this pull request to the merge queue Apr 8, 2026
Merged via the queue into main with commit d79f8d1 Apr 8, 2026
14 checks passed
@lorisleiva lorisleiva deleted the 04-08-add_extractable_option_to_generatekeypair_and_generatekeypairsigner_ branch April 8, 2026 16:01
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

🔎💬 Inkeep AI search and chat service is syncing content for source 'Solana Kit Docs'

@github-actions
Copy link
Copy Markdown
Contributor

Because there has been no activity on this PR for 14 days since it was merged, it has been automatically locked. Please open a new issue if it requires a follow up.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Apr 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants