-
Notifications
You must be signed in to change notification settings - Fork 2
added encrypted bundle caching + escrow key injection #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
5d50e05 to
1fb0969
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds encrypted export-bundle caching in the export-and-sign iframe’s localStorage, plus an in-memory “escrow” decryption key injection flow to decrypt bundles and enable signing without persisting the escrow key.
Changes:
- Added
localStoragehelpers for storing/removing encrypted bundles keyed by wallet address. - Added new iframe event handlers for storing encrypted bundles, injecting an escrow decryption bundle, listing stored addresses, clearing stored bundles, and burning the session.
- Added comprehensive Jest coverage for the escrow storage/decryption/signing lifecycle and related helper APIs.
Reviewed changes
Copilot reviewed 4 out of 11 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| shared/turnkey-core.js | Adds shared localStorage helpers to persist encrypted bundles. |
| export-and-sign/src/turnkey-core.js | Re-exports the new shared encrypted-bundle helper APIs through the iframe TKHQ facade. |
| export-and-sign/src/event-handlers.js | Implements new message handlers for encrypted bundle storage + escrow key injection + session/bundle management. |
| export-and-sign/index.test.js | Adds test coverage for new helper APIs and events (store/decrypt/sign/burn/clear/list). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function setEncryptedBundle(address, bundleData) { | ||
| const bundles = getEncryptedBundles() || {}; | ||
| bundles[address] = bundleData; | ||
| window.localStorage.setItem( | ||
| TURNKEY_ENCRYPTED_BUNDLES, | ||
| JSON.stringify(bundles) | ||
| ); | ||
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using an untrusted address as an object key allows prototype pollution (e.g., "proto", "constructor", "prototype"), and bundles[address] = ... can mutate the object prototype. Since address ultimately comes from postMessage inputs, this is a concrete risk. Use a Map-like serialization (array of [address, bundleData] entries), or store into an object created via Object.create(null) and explicitly reject dangerous keys before setting/removing. Apply the same protection in removal paths.
| bundleObj.data | ||
| ); | ||
| if (!verified) { | ||
| throw new Error(`failed to verify enclave signature: ${bundle}`); |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error message embeds the full bundle payload (including ciphertext/encappedPublic). If this propagates to sendMessageUp("ERROR", ...) and/or logs, it can leak sensitive encrypted material and bloat logs. Prefer a redacted error (e.g., include version + organizationId + maybe a short hash) rather than interpolating the complete bundle string.
| throw new Error(`failed to verify enclave signature: ${bundle}`); | |
| throw new Error( | |
| `failed to verify enclave signature (version=${bundleObj.version}, organizationId=${organizationId ?? "unknown"})` | |
| ); |
| // PKCS8 DER prefix for a P-256 private key (without optional public key field) | ||
| // SEQUENCE { | ||
| // INTEGER 0 (version) | ||
| // SEQUENCE { OID ecPublicKey, OID P-256 } | ||
| // OCTET STRING { SEQUENCE { INTEGER 1, OCTET STRING(32) <key> } } | ||
| // } | ||
| const pkcs8Prefix = new Uint8Array([ | ||
| 0x30, 0x41, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, | ||
| 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, | ||
| 0x01, 0x07, 0x04, 0x27, 0x30, 0x25, 0x02, 0x01, 0x01, 0x04, 0x20, | ||
| ]); |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PKCS#8 prefix is a hard-coded DER blob with multiple magic constants (e.g., 0x41 / 0x27 / 0x25). This is brittle and hard to audit. Consider adding an authoritative reference link (RFC / ASN.1 structure source) and/or deriving these lengths programmatically to reduce the chance of subtle format errors if the structure ever needs to change.
f6e33fa to
51d9867
Compare
Added the ability to store encrypted wallet account export bundles in the
export-and-signiframe's local storage, and decrypt + sign transactions & messages using an injected "escrow" private key export bundle.General Flow:
Now all our encrypted bundles are safely stored encrypted in local storage but decrypted in memory! The escrow private key is never kept in persistent storage and must be re-injected whenever the iframe gets destroyed.
Additional helpers events include:
Sorta open questions:
Quick Demo 🎉
https://www.loom.com/share/5b3c9d5427414629b42e81c9278e5df0
https://www.loom.com/share/3dca52882847484fa8df2738329fc997
Project doc: https://docs.google.com/document/d/16YXWrR75RnRmkM_gURLAzwrEG7FUzK_pF6Z-rQ5g0j8/edit?tab=t.0