2
2
import "react-native-get-random-values" ;
3
3
import "./utils/buffer-polyfill" ;
4
4
import "./utils/mmkv-localstorage-polyfill" ;
5
-
6
- /* eslint-disable import/extensions */
7
5
import { type ConnectionConfig } from "@aa-sdk/core" ;
8
6
import {
9
7
createPasskey ,
@@ -291,7 +289,7 @@ export class RNSignerClient extends BaseSignerClient<ExportWalletParams> {
291
289
292
290
/**
293
291
* Exports the wallet's private key for the authenticated user.
294
- *
292
+ *
295
293
* This uses the local storage approach recommended by Turnkey for mobile contexts.
296
294
* A P256 key pair is generated locally, the public key is used to encrypt the export,
297
295
* and the private key is used to decrypt the bundle locally.
@@ -308,28 +306,30 @@ export class RNSignerClient extends BaseSignerClient<ExportWalletParams> {
308
306
309
307
/**
310
308
* Exports the wallet and returns the decrypted private key or seed phrase.
311
- *
309
+ *
312
310
* @param {ExportWalletParams } params Export parameters
313
311
* @returns {Promise<ExportWalletResult> } The decrypted export data
314
312
* @throws {Error } If the user is not authenticated or export fails
315
313
*/
316
- async exportWalletWithResult ( params ?: ExportWalletParams ) : Promise < ExportWalletResult > {
314
+ async exportWalletWithResult ( params ?: ExportWalletParams ) : Promise < any > {
317
315
if ( ! this . user ) {
318
316
throw new Error ( "User must be authenticated to export wallet" ) ;
319
317
}
320
318
321
319
const exportAs = params ?. exportAs || "PRIVATE_KEY" ;
322
-
320
+
323
321
// Step 1: Generate a P256 key pair for encryption
324
322
const embeddedKey = generateP256KeyPair ( ) ;
325
-
323
+
326
324
// Step 2: Save the private key in secure storage
327
325
const keyId = `export_key_${ Date . now ( ) } ` ;
328
326
this . storage . set ( keyId , embeddedKey . privateKey ) ;
329
-
327
+
328
+ console . log ( "exportWalletWithResult" ) ;
329
+
330
330
try {
331
331
let exportBundle : string ;
332
-
332
+
333
333
if ( exportAs === "PRIVATE_KEY" ) {
334
334
// Step 3a: Export as private key
335
335
const { activity } = await this . turnkeyClient . exportWalletAccount ( {
@@ -347,18 +347,18 @@ export class RNSignerClient extends BaseSignerClient<ExportWalletParams> {
347
347
this . user . orgId ,
348
348
"exportWalletAccountResult" ,
349
349
) ;
350
-
350
+
351
351
if ( ! result . exportBundle ) {
352
352
throw new Error ( "Failed to export wallet: no export bundle returned" ) ;
353
353
}
354
-
354
+
355
355
exportBundle = result . exportBundle ;
356
356
} else {
357
357
// Step 3b: Export as seed phrase (need to find the wallet first)
358
358
const { wallets } = await this . turnkeyClient . getWallets ( {
359
359
organizationId : this . user . orgId ,
360
360
} ) ;
361
-
361
+
362
362
const walletAccounts = await Promise . all (
363
363
wallets . map ( ( { walletId } ) =>
364
364
this . turnkeyClient . getWalletAccounts ( {
@@ -367,7 +367,7 @@ export class RNSignerClient extends BaseSignerClient<ExportWalletParams> {
367
367
} ) ,
368
368
) ,
369
369
) . then ( ( x ) => x . flatMap ( ( x ) => x . accounts ) ) ;
370
-
370
+
371
371
const walletAccount = walletAccounts . find (
372
372
( x ) => x . address === this . user ! . address ,
373
373
) ;
@@ -391,43 +391,54 @@ export class RNSignerClient extends BaseSignerClient<ExportWalletParams> {
391
391
this . user . orgId ,
392
392
"exportWalletResult" ,
393
393
) ;
394
-
394
+
395
395
if ( ! result . exportBundle ) {
396
396
throw new Error ( "Failed to export wallet: no export bundle returned" ) ;
397
397
}
398
-
398
+
399
399
exportBundle = result . exportBundle ;
400
400
}
401
401
402
- // Step 4: Decrypt the export bundle using HPKE
403
- // The export bundle is hex-encoded, so we need to convert it to bytes
404
- const bundleBytes = Buffer . from ( exportBundle , 'hex' ) ;
405
- const encappedKeyBuf = bundleBytes . slice ( 0 , 65 ) ;
406
- const ciphertextBuf = bundleBytes . slice ( 65 ) ;
407
-
402
+ // Step 4: Parse the export bundle and decrypt using HPKE
403
+ // The export bundle is a JSON string containing version, data, etc.
404
+ const bundleJson = JSON . parse ( exportBundle ) ;
405
+
406
+ // The data field contains another JSON string that's hex-encoded
407
+ const innerDataHex = bundleJson . data ;
408
+ const innerDataJson = JSON . parse (
409
+ Buffer . from ( innerDataHex , "hex" ) . toString ( ) ,
410
+ ) ;
411
+
412
+ // Extract the encapped public key and ciphertext from the inner data
413
+ const encappedPublicKeyHex = innerDataJson . encappedPublic ;
414
+ const ciphertextHex = innerDataJson . ciphertext ;
415
+
416
+ const encappedKeyBuf = Buffer . from ( encappedPublicKeyHex , "hex" ) ;
417
+ const ciphertextBuf = Buffer . from ( ciphertextHex , "hex" ) ;
418
+
419
+ // Decrypt the data using HPKE
408
420
const decryptedData = hpkeDecrypt ( {
409
- encappedKeyBuf ,
410
- ciphertextBuf ,
421
+ ciphertextBuf : ciphertextBuf ,
422
+ encappedKeyBuf : encappedKeyBuf ,
411
423
receiverPriv : embeddedKey . privateKey ,
412
424
} ) ;
413
425
414
- // Step 5: Parse the decrypted data
415
- const exportData = JSON . parse ( new TextDecoder ( ) . decode ( decryptedData ) ) ;
416
-
417
- // Return the result based on export type
426
+ // Step 5: Process the decrypted data based on export type
418
427
const result : ExportWalletResult = {
419
428
address : this . user . address ,
420
429
exportAs,
421
430
} ;
422
-
431
+
423
432
if ( exportAs === "PRIVATE_KEY" ) {
424
- result . privateKey = exportData . privateKey ;
433
+ // For private key, the decrypted data is the raw private key bytes
434
+ // Convert to hex string with 0x prefix
435
+ result . privateKey = "0x" + Buffer . from ( decryptedData ) . toString ( "hex" ) ;
425
436
} else {
426
- result . seedPhrase = exportData . mnemonic ;
437
+ // For seed phrase, the decrypted data is the mnemonic string
438
+ result . seedPhrase = new TextDecoder ( ) . decode ( decryptedData ) ;
427
439
}
428
-
440
+
429
441
return result ;
430
-
431
442
} finally {
432
443
// Step 6: Clean up - remove the embedded key from storage
433
444
this . storage . delete ( keyId ) ;
0 commit comments