decryptExportBundle function

Future<String> decryptExportBundle({
  1. required String exportBundle,
  2. required String embeddedKey,
  3. required String organizationId,
  4. String? dangerouslyOverrideSignerPublicKey,
  5. String keyFormat = 'HEXADECIMAL',
  6. bool returnMnemonic = false,
})

Decrypts an encrypted export bundle (such as a private key or wallet account bundle).

This function verifies the enclave signature to ensure the authenticity of the encrypted data. It uses HPKE (Hybrid Public Key Encryption) to decrypt the contents of the bundle and returns either the decrypted mnemonic or the decrypted data in hexadecimal format, based on the returnMnemonic flag.

Parameters:

  • exportBundle: The encrypted export bundle in JSON format.
  • organizationId: The expected organization ID to verify against the signed data.
  • embeddedKey: The private key used for decrypting the data, provided as a hex string (32 bytes).
  • dangerouslyOverrideSignerPublicKey: (Optional) Override the default signer public key used for verifying the signature. This should only be done for testing purposes.
  • keyFormat: Format of the key (default is HEXADECIMAL).
  • returnMnemonic: If true, returns the decrypted data as a mnemonic string; otherwise, returns it in hexadecimal format (default is false).

Returns:

  • A Future<String> that resolves to the decrypted mnemonic or the decrypted hexadecimal data.

Throws:

  • Exception: If decryption or signature verification fails, an error with details is thrown.

Implementation

Future<String> decryptExportBundle({
  required String exportBundle,
  required String embeddedKey,
  required String organizationId,
  String? dangerouslyOverrideSignerPublicKey,
  String keyFormat = 'HEXADECIMAL',
  bool returnMnemonic = false,
}) async {
  try {
    final parsedExportBundle = jsonDecode(exportBundle);

    final verified = await verifyEnclaveSignature(
      enclaveQuorumPublic: parsedExportBundle['enclaveQuorumPublic'],
      publicSignature: parsedExportBundle['dataSignature'],
      signedData: parsedExportBundle['data'],
      dangerouslyOverrideSignerPublicKey: dangerouslyOverrideSignerPublicKey,
    );
    if (!verified) {
      throw Exception('failed to verify enclave signature');
    }

    final dataBytes = uint8ArrayFromHexString(parsedExportBundle['data']);
    final signedDataJson = utf8.decode(dataBytes);
    final signedData = jsonDecode(signedDataJson);

    if (signedData['organizationId'] == null ||
        signedData['organizationId'] != organizationId) {
      throw Exception(
        'organization id does not match. Expected: $organizationId. Found: ${signedData["organizationId"]}.',
      );
    }

    if (signedData['encappedPublic'] == null) {
      throw Exception('missing "encappedPublic" in bundle signed data');
    }
    final encappedKeyBuf =
        uint8ArrayFromHexString(signedData['encappedPublic']);

    final ciphertextBuf = uint8ArrayFromHexString(signedData['ciphertext']);

    final decryptedData = hpkeDecrypt(
      ciphertextBuf: ciphertextBuf,
      encappedKeyBuf: encappedKeyBuf,
      receiverPriv: embeddedKey,
      hpkeInfo: zeroxkeyHpkeInfo,
    );

    if (keyFormat == 'SOLANA' && !returnMnemonic) {
      if (decryptedData.length != 32) {
        throw Exception(
            'invalid private key length. Expected 32 bytes, got ${decryptedData.length}');
      }

      final algorithm = crypto.Ed25519();
      final keyPair = await algorithm.newKeyPairFromSeed(decryptedData);
      final publicKey = await keyPair.extractPublicKey();
      final publicKeyBytes = publicKey.bytes; // should be 32 bytes

      if (publicKeyBytes.length != 32) {
        throw Exception(
            'invalid public key length. Expected 32 bytes. Got ${publicKeyBytes.length}');
      }

      final concatenatedBytes = Uint8List(64);
      concatenatedBytes.setAll(0, decryptedData);
      concatenatedBytes.setAll(32, publicKeyBytes);

      return base58.encode(concatenatedBytes);
    }

    final decryptedDataHex = uint8ArrayToHexString(decryptedData);
    return returnMnemonic ? hexToAscii(decryptedDataHex) : decryptedDataHex;
  } catch (error) {
    throw Exception('Error decrypting bundle: $error');
  }
}