Skip to content

Commit bfb3789

Browse files
authored
feat: key export (#2249)
* feat: key export * feat: encrypted key export * chore: bump docs-site * chore: remove unencrypted
1 parent 45327a9 commit bfb3789

File tree

2 files changed

+97
-0
lines changed

2 files changed

+97
-0
lines changed

account-kit/signer/src/base.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,16 @@ export abstract class BaseAlchemySigner<TClient extends BaseSignerClient>
10511051
return this.inner.exportWallet(params);
10521052
};
10531053

1054+
/**
1055+
* Exports a private key for a given account encrypted with the provided public key
1056+
*
1057+
* @param {ExportPrivateKeyParams} opts the parameters for the export
1058+
* @returns {Promise<string>} the private key
1059+
*/
1060+
exportPrivateKeyEncrypted: TClient["exportPrivateKeyEncrypted"] = (opts) => {
1061+
return this.inner.exportPrivateKeyEncrypted(opts);
1062+
};
1063+
10541064
/**
10551065
* This method lets you adapt your AlchemySigner to a viem LocalAccount, which
10561066
* will let you use the signer as an EOA directly.

account-kit/signer/src/client/base.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
recoverPublicKey,
88
serializeSignature,
99
sha256,
10+
fromHex,
1011
type Address,
1112
type Hex,
1213
} from "viem";
@@ -66,6 +67,23 @@ export interface BaseSignerClientParams {
6667
rpId?: string;
6768
}
6869

70+
export interface ExportPrivateKeyParams {
71+
type: "SOLANA" | "ETHEREUM";
72+
client?: TurnkeyClient;
73+
orgId?: string;
74+
}
75+
76+
export interface MultiOwnerExportPrivateKeyParams {
77+
type: "SOLANA" | "ETHEREUM";
78+
orgId: string;
79+
}
80+
81+
export interface ExportPrivateKeyEncryptedResult {
82+
exportBundle: string;
83+
ciphertext: string;
84+
encapsulatedKey: string;
85+
}
86+
6987
export type ExportWalletStamper = TurnkeyClient["stamper"] & {
7088
injectWalletExportBundle(bundle: string, orgId: string): Promise<boolean>;
7189
injectKeyExportBundle(bundle: string, orgId: string): Promise<boolean>;
@@ -1491,4 +1509,73 @@ export abstract class BaseSignerClient<
14911509
protected getOauthNonce = (turnkeyPublicKey: string): string => {
14921510
return sha256(new TextEncoder().encode(turnkeyPublicKey)).slice(2);
14931511
};
1512+
1513+
/**
1514+
* Exports a private key for a given account encrypted with the provided public key
1515+
*
1516+
* @param {ExportPrivateKeyParams} opts the parameters for the export
1517+
* @returns {Promise<string>} the private key
1518+
*/
1519+
public exportPrivateKeyEncrypted = async (
1520+
opts: ExportPrivateKeyParams & { encryptWith: string },
1521+
): Promise<ExportPrivateKeyEncryptedResult> => {
1522+
if (!this.user) {
1523+
throw new NotAuthenticatedError();
1524+
}
1525+
1526+
const targetAddressFormat =
1527+
opts.type === "ETHEREUM"
1528+
? "ADDRESS_FORMAT_ETHEREUM"
1529+
: "ADDRESS_FORMAT_SOLANA";
1530+
const turnkeyClient = opts.client ?? this.turnkeyClient;
1531+
const organizationId = opts.orgId ?? this.user.orgId;
1532+
1533+
const wallets = await turnkeyClient.getWalletAccounts({ organizationId });
1534+
const account = wallets.accounts.find(
1535+
(account) => account.addressFormat === targetAddressFormat,
1536+
);
1537+
if (!account?.address) {
1538+
throw new Error("Failed to find account: " + opts.type);
1539+
}
1540+
const exported = await turnkeyClient.exportWalletAccount({
1541+
organizationId,
1542+
type: "ACTIVITY_TYPE_EXPORT_WALLET_ACCOUNT",
1543+
timestampMs: Date.now().toString(),
1544+
parameters: {
1545+
address: account.address,
1546+
targetPublicKey: opts.encryptWith,
1547+
},
1548+
});
1549+
const exportBundle =
1550+
exported?.activity.result.exportWalletAccountResult?.exportBundle;
1551+
if (!exportBundle) throw new Error("No export bundle found");
1552+
1553+
const parsedExportBundle = JSON.parse(exportBundle);
1554+
const signedData = JSON.parse(
1555+
fromHex(`0x${parsedExportBundle.data}`, { to: "string" }),
1556+
);
1557+
1558+
return {
1559+
exportBundle,
1560+
ciphertext: signedData.ciphertext,
1561+
encapsulatedKey: signedData.encappedPublic,
1562+
};
1563+
};
1564+
1565+
/**
1566+
* Exports a private key for a given account in a multi-owner org
1567+
*
1568+
* @param {MultiOwnerExportPrivateKeyParams} opts the parameters for the export
1569+
* @returns {Promise<string>} the private key
1570+
*/
1571+
public experimental_multiOwnerExportPrivateKeyEncrypted = async (
1572+
opts: MultiOwnerExportPrivateKeyParams & { encryptWith: string },
1573+
): Promise<ExportPrivateKeyEncryptedResult> => {
1574+
return this.exportPrivateKeyEncrypted({
1575+
type: opts.type,
1576+
client: this.experimental_createMultiOwnerTurnkeyClient(),
1577+
orgId: opts.orgId,
1578+
encryptWith: opts.encryptWith,
1579+
});
1580+
};
14941581
}

0 commit comments

Comments
 (0)