reencryptToCurrentKey method

Future<int> reencryptToCurrentKey()

Re-encrypts every xpub and hdpubkey row that is not under the current key version with the current key, and returns how many rows changed.

Completes a key rotation: once it returns, previousKeys are no longer needed. Runs in one transaction; throws SecureStorageException (and changes nothing) if any row cannot be decrypted.

Implementation

Future<int> reencryptToCurrentKey() async {
  try {
    return await _pool.runTx((session) async {
      final rows = await session.execute(
        Sql.named('''
          SELECT key_name, encrypted_value, nonce, key_version
          FROM secure_secrets
          WHERE key_version <> @current
            AND (key_name LIKE @xpub_pattern OR key_name LIKE @hdpubkey_pattern)
          FOR UPDATE
        '''),
        parameters: {
          'current': _encryptionService.keyVersion,
          'xpub_pattern': '$_xpubKeyPrefix%',
          'hdpubkey_pattern': '$_hdPubKeyPrefix%',
        },
      );
      for (final row in rows) {
        final keyName = row[0] as String;
        final plaintext = await _decryptRow(
          keyName,
          row[1] as Uint8List,
          row[2] as Uint8List,
          row[3] as int,
        );
        final encrypted = await _encryptionService.encrypt(
          plaintext: plaintext,
          context: keyName,
        );
        await session.execute(
          Sql.named('''
            UPDATE secure_secrets
            SET encrypted_value = @encrypted_value,
                nonce = @nonce,
                key_version = @key_version,
                updated_at = NOW()
            WHERE key_name = @key_name
          '''),
          parameters: {
            'key_name': keyName,
            'encrypted_value':
                TypedValue(Type.byteArray, encrypted.ciphertext),
            'nonce': TypedValue(Type.byteArray, encrypted.nonce),
            'key_version': _encryptionService.keyVersion,
          },
        );
      }
      return rows.length;
    });
  } on SecureStorageException {
    rethrow;
  } catch (e) {
    throw SecureStorageException('Failed to re-encrypt secrets: $e', e);
  }
}