Add writeKeyPair and writeKeyPairSigner helpers#1537
Conversation
🦋 Changeset detectedLatest commit: aec6308 The changes in this PR will be included in the next version bump. This PR includes changesets to release 46 packages
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 |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
BundleMonFiles added (2)
Files updated (13)
Unchanged files (129)
Total files change +1.57KB +0.32% Final result: ✅ View report in BundleMon website ➡️ |
|
Documentation Preview: https://kit-docs-ot4ygze9f-anza-tech.vercel.app |
trevor-cortex
left a comment
There was a problem hiding this comment.
Summary
This PR adds writeKeyPair (@solana/keys) and writeKeyPairSigner (@solana/signers) — one-line helpers to persist an extractable Ed25519 key pair to disk as a 64-byte JSON array, byte-for-byte compatible with solana-keygen new. A new private @solana/fs-impl package isolates Node filesystem access (following the crypto-impl / event-target-impl pattern), with browser stubs that throw a structured error. The implementation is clean, well-documented, and thoroughly tested.
Key observations
@solana/fs-impl export conditions: edge-light / workerd should use browser stubs
The edge-light and workerd export conditions in packages/fs-impl/package.json currently point to the Node entry (index.node.mjs / index.node.cjs). Edge runtimes like Vercel Edge Functions and Cloudflare Workers don't have node:fs/promises or node:path, so these should resolve to the browser stubs instead — the same way @solana/crypto-impl maps edge-light / workerd to the browser entry for types but still points to Node for runtime (since those runtimes do have crypto.subtle). For fs-impl, however, there's no filesystem in those runtimes, so both the runtime and types should use the browser entry.
This is the only issue I'd flag as needing a fix — the rest of the PR looks great.
Everything else looks solid
- PKCS#8 slice-at-16 approach — correctly documented and matches the existing
createPrivateKeyFromBytesimplementation that prepends the same fixed 16-byte header. wxflag for default no-overwrite is the right primitive — lets the OS enforce atomicity.unsafelyOverwriteExistingKeyPairnaming is appropriately scary.- Error codes are properly registered with context, messages, and the union type.
- Tests cover the important cases: round-trip, file mode, recursive dir creation, non-extractable keys, overwrite protection, and browser environments.
writeKeyPairSigneris a clean thin wrapper that delegates towriteKeyPair.- Docs (
@seecross-references betweencreateKeyPairFromBytes↔writeKeyPair↔writeKeyPairSigner) are helpful.
Notes for subsequent reviewers
- Verify that the
@solana/fs-implpackage is wired into the monorepo's CI (turbo pipeline, lint, typecheck, treeshakability). - The PKCS#8 header length (16 bytes) is hard-coded — this is correct for Ed25519 but worth noting in case the codebase ever adds other curve types.
@solana/fs-implis adevDependencyof@solana/keys(since it gets inlined vianoExternal). Confirm that the published@solana/keysbundle doesn't carry a runtime dependency reference to@solana/fs-impl.
62090da to
d408bf4
Compare
ef02578 to
206f03e
Compare
d408bf4 to
684df34
Compare
trevor-cortex
left a comment
There was a problem hiding this comment.
Re-review
All feedback from the previous pass has been addressed. LGTM — submitting as COMMENT since I can't approve without explicit repo access.
Changes since last review
-
@solana/fs-implexport conditions fixed ✅ —edge-lightandworkerdnow resolve to the browser stubs (index.browser.mjs/.cjs) for bothimportandrequire, matching thebrowsercondition. Edge runtimes will no longer pull innode:fs/promises/node:path. -
New PKCS#8 length guard in
writeKeyPair— after slicing the 16-byte PKCS#8 header, the code now assertsprivateKeyBytes.byteLength === 32and throwsSOLANA_ERROR__KEYS__INVALID_PRIVATE_KEY_BYTE_LENGTHotherwise. Good forward-compat defense if WebCrypto ever changes the Ed25519 envelope shape. The inline comment explaining the 16-byte prefix is helpful context for future readers. -
README documentation added for both
writeKeyPair(in@solana/keys) andwriteKeyPairSigner(in@solana/signers). Both snippets correctly showgenerateKeyPair(true)/generateKeyPairSigner(true)to highlight the extractable requirement, and both call out the overwrite-destroys-funds footgun.
Minor observations (non-blocking)
- The new PKCS#8 length branch is effectively unreachable today (Ed25519 PKCS#8 envelopes are fixed at 48 bytes → 32 after the slice), so it's correctly omitted from tests — just flagging it for coverage-report readers who might wonder.
WriteKeyPairConfigis defined in@solana/keysand re-exported from@solana/signersviaexport type { WriteKeyPairConfig }. Clean — avoids type drift between the two helpers.
Notes for subsequent reviewers
- Still worth verifying
@solana/fs-implis wired into CI (turbo pipeline, lint, typecheck, treeshakability scripts are all declared in itspackage.json, but confirm the root turbo config picks it up). - The
writeKeyPairround-trip test asserts the written bytes are compatible withcreateKeyPairFromBytes, which is the key correctness guarantee — good coverage.
684df34 to
523a7df
Compare
Merge activity
|
This PR adds `writeKeyPair` to `@solana/keys` and `writeKeyPairSigner` to `@solana/signers`, giving callers a one-line way to save an extractable key pair as a 64-byte JSON array that's byte-for-byte compatible with `solana-keygen new`. Missing parent directories are created automatically, and the resulting file is written with mode `0600` so only the owner can read or write it. Both helpers refuse to overwrite an existing file by default and require the caller to explicitly set `unsafelyOverwriteExistingKeyPair: true` to replace one — the name is deliberately alarming because overwriting a key pair file permanently destroys the previous key and any assets it controls. Calling either helper in a browser or React Native environment throws `SOLANA_ERROR__KEYS__WRITE_KEY_PAIR_UNSUPPORTED_ENVIRONMENT` up front. To keep browser and React Native bundles free of any `node:fs/promises` or `node:path` references, filesystem access is routed through a new private `@solana/fs-impl` package that follows the same pattern as `@solana/crypto-impl` and friends. Its Node entry re-exports `mkdir`, `writeFile`, and `dirname`; its browser entry provides structurally-identical stubs that throw a new generic `SOLANA_ERROR__FS__UNSUPPORTED_ENVIRONMENT` error as defense-in-depth beneath the helper-specific guard. The package is added to `noExternal` in `getBaseConfig.ts` so it gets inlined at build time.
523a7df to
aec6308
Compare
|
🔎💬 Inkeep AI search and chat service is syncing content for source 'Solana Kit Docs' |
|
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. |

This PR adds
writeKeyPairto@solana/keysandwriteKeyPairSignerto@solana/signers, giving callers a one-line way to save an extractable key pair as a 64-byte JSON array that's byte-for-byte compatible withsolana-keygen new. Missing parent directories are created automatically, and the resulting file is written with mode0600so only the owner can read or write it. Both helpers refuse to overwrite an existing file by default and require the caller to explicitly setunsafelyOverwriteExistingKeyPair: trueto replace one — the name is deliberately alarming because overwriting a key pair file permanently destroys the previous key and any assets it controls. Calling either helper in a browser or React Native environment throwsSOLANA_ERROR__KEYS__WRITE_KEY_PAIR_UNSUPPORTED_ENVIRONMENTup front.To keep browser and React Native bundles free of any
node:fs/promisesornode:pathreferences, filesystem access is routed through a new private@solana/fs-implpackage that follows the same pattern as@solana/crypto-impland friends. Its Node entry re-exportsmkdir,writeFile, anddirname; its browser entry provides structurally-identical stubs that throw a new genericSOLANA_ERROR__FS__UNSUPPORTED_ENVIRONMENTerror as defense-in-depth beneath the helper-specific guard. The package is added tonoExternalingetBaseConfig.tsso it gets inlined at build time.