diff --git a/account-kit/signer/src/base.ts b/account-kit/signer/src/base.ts index e77bcd662d..13e25072e9 100644 --- a/account-kit/signer/src/base.ts +++ b/account-kit/signer/src/base.ts @@ -1051,6 +1051,16 @@ export abstract class BaseAlchemySigner return this.inner.exportWallet(params); }; + /** + * Exports a private key for a given account + * + * @param {ExportPrivateKeyParams} opts the parameters for the export + * @returns {Promise} the private key + */ + exportPrivateKey: TClient["exportPrivateKey"] = (opts) => { + return this.inner.exportPrivateKey(opts); + }; + /** * This method lets you adapt your AlchemySigner to a viem LocalAccount, which * will let you use the signer as an EOA directly. diff --git a/account-kit/signer/src/client/base.ts b/account-kit/signer/src/client/base.ts index fbc53c0ad3..adc608be92 100644 --- a/account-kit/signer/src/client/base.ts +++ b/account-kit/signer/src/client/base.ts @@ -7,6 +7,7 @@ import { recoverPublicKey, serializeSignature, sha256, + toHex, type Address, type Hex, } from "viem"; @@ -15,6 +16,7 @@ import { OAuthProvidersError, UnsupportedFeatureError, } from "../errors.js"; +import { decryptExportBundle } from "@turnkey/crypto"; import { getDefaultProviderCustomization } from "../oauth.js"; import type { OauthMode } from "../signer.js"; import { base64UrlEncode } from "../utils/base64UrlEncode.js"; @@ -58,6 +60,7 @@ import type { import { VERSION } from "../version.js"; import { secp256k1 } from "@noble/curves/secp256k1"; import { Point } from "@noble/secp256k1"; +import { p256 } from "@noble/curves/p256"; export interface BaseSignerClientParams { stamper: TurnkeyClient["stamper"]; @@ -66,6 +69,17 @@ export interface BaseSignerClientParams { rpId?: string; } +export interface ExportPrivateKeyParams { + type: "SOLANA" | "ETHEREUM"; + client?: TurnkeyClient; + orgId?: string; +} + +export interface MultiOwnerExportPrivateKeyParams { + type: "SOLANA" | "ETHEREUM"; + orgId: string; +} + export type ExportWalletStamper = TurnkeyClient["stamper"] & { injectWalletExportBundle(bundle: string, orgId: string): Promise; injectKeyExportBundle(bundle: string, orgId: string): Promise; @@ -1491,4 +1505,74 @@ export abstract class BaseSignerClient< protected getOauthNonce = (turnkeyPublicKey: string): string => { return sha256(new TextEncoder().encode(turnkeyPublicKey)).slice(2); }; + + /** + * Exports a private key for a given account + * + * @param {ExportPrivateKeyParams} opts the parameters for the export + * @returns {Promise} the private key + */ + public exportPrivateKey = async ( + opts: ExportPrivateKeyParams, + ): Promise => { + if (!this.user) { + throw new NotAuthenticatedError(); + } + + const targetAddressFormat = + opts.type === "ETHEREUM" + ? "ADDRESS_FORMAT_ETHEREUM" + : "ADDRESS_FORMAT_SOLANA"; + const keyFormat = opts.type === "ETHEREUM" ? "HEXADECIMAL" : "SOLANA"; + const turnkeyClient = opts.client ?? this.turnkeyClient; + const organizationId = opts.orgId ?? this.user.orgId; + + const wallets = await turnkeyClient.getWalletAccounts({ organizationId }); + const account = wallets.accounts.find( + (account) => account.addressFormat === targetAddressFormat, + ); + if (!account?.address) { + throw new Error("Failed to find account: " + opts.type); + } + const targetPrivateKey = p256.utils.randomPrivateKey(); + const targetPublicKey = p256.getPublicKey(targetPrivateKey, false); + const exported = await turnkeyClient.exportWalletAccount({ + organizationId, + type: "ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT", + timestampMs: Date.now().toString(), + parameters: { + address: account.address, + targetPublicKey: toHex(targetPublicKey).slice(2), + }, + }); + const { exportBundle } = + exported?.activity.result.exportWalletAccountResult || {}; + + if (!exportBundle) throw new Error("No export bundle found"); + const decrypted = await decryptExportBundle({ + exportBundle, + embeddedKey: toHex(targetPrivateKey).slice(2), + organizationId, + returnMnemonic: false, + keyFormat, + }); + + return decrypted; + }; + + /** + * Exports a private key for a given account in a multi-owner org + * + * @param {MultiOwnerExportPrivateKeyParams} opts the parameters for the export + * @returns {Promise} the private key + */ + public experimental_multiOwnerExportPrivateKey = async ( + opts: MultiOwnerExportPrivateKeyParams, + ): Promise => { + return this.exportPrivateKey({ + type: opts.type, + client: this.experimental_createMultiOwnerTurnkeyClient(), + orgId: opts.orgId, + }); + }; } diff --git a/docs-site b/docs-site index 9ae826282a..3d9fca2778 160000 --- a/docs-site +++ b/docs-site @@ -1 +1 @@ -Subproject commit 9ae826282ad2310e4838057f9d315fbcb47d40b2 +Subproject commit 3d9fca2778d6e0d96ced662ee50c140882200065