"use strict";
/**
 * SDK-compatible storage encryption for private state and signing key blobs.
 *
 * Implements the EXACT wire format used by the Midnight JS SDK's LevelDB
 * private-state provider so that exports produced by NIGHTGATE's CAP-DB
 * provider can be imported by the SDK's LevelDB provider, and vice versa.
 *
 * Wire format of an encrypted payload (base64-decoded):
 *
 *   [ 1 byte  version    = 2          ]
 *   [ 32 bytes salt      (PBKDF2)     ]
 *   [ 12 bytes IV        (GCM nonce)  ]
 *   [ 16 bytes authTag   (GCM tag)    ]
 *   [ N bytes  ciphertext (AES-256-GCM) ]
 *
 * Key derivation: PBKDF2-SHA256(password, salt, 600_000, 32 bytes).
 *
 * For internal CAP-DB storage we use a memoized per-account master key
 * (one PBKDF2 per session, not per row), the SDK's LevelDB provider does
 * the same. Export blobs always include their own salt and re-derive,
 * preserving cross-compat.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StorageEncryption = exports.CURRENT_ENCRYPTION_VERSION = exports.ENCRYPTION_VERSION_V2 = exports.PBKDF2_ITERATIONS_V2 = exports.SALT_LENGTH = exports.AUTH_TAG_LENGTH = exports.IV_LENGTH = exports.KEY_LENGTH = exports.ALGORITHM = void 0;
exports.deriveKey = deriveKey;
exports.extractEncryptedComponents = extractEncryptedComponents;
exports.decryptWithPassword = decryptWithPassword;
const crypto_1 = __importDefault(require("crypto"));
exports.ALGORITHM = 'aes-256-gcm';
exports.KEY_LENGTH = 32; // AES-256
exports.IV_LENGTH = 12; // GCM standard
exports.AUTH_TAG_LENGTH = 16;
exports.SALT_LENGTH = 32;
exports.PBKDF2_ITERATIONS_V2 = 600_000;
exports.ENCRYPTION_VERSION_V2 = 2;
exports.CURRENT_ENCRYPTION_VERSION = exports.ENCRYPTION_VERSION_V2;
const VERSION_PREFIX_LENGTH = 1;
const HEADER_LENGTH = VERSION_PREFIX_LENGTH + exports.SALT_LENGTH + exports.IV_LENGTH + exports.AUTH_TAG_LENGTH;
/**
 * Mirrors the SDK's StorageEncryption class for byte-compatibility.
 * One instance ↔ one (password, salt) pair.
 */
class StorageEncryption {
    salt;
    encryptionKey;
    constructor(password, existingSalt) {
        this.salt = existingSalt ?? crypto_1.default.randomBytes(exports.SALT_LENGTH);
        this.encryptionKey = deriveKey(password, this.salt);
    }
    /** Encrypts `data` (UTF-8 string) and returns base64-encoded SDK wire format. */
    encrypt(data) {
        const plaintext = Buffer.from(data, 'utf-8');
        const iv = crypto_1.default.randomBytes(exports.IV_LENGTH);
        const cipher = crypto_1.default.createCipheriv(exports.ALGORITHM, this.encryptionKey, iv);
        const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
        const authTag = cipher.getAuthTag();
        const version = Buffer.from([exports.CURRENT_ENCRYPTION_VERSION]);
        return Buffer.concat([version, this.salt, iv, authTag, encrypted]).toString('base64');
    }
    /**
     * Decrypts an SDK-format base64 payload. The salt in the payload must match
     * this instance's salt (i.e. the same password was used).
     */
    decrypt(encryptedData) {
        const data = Buffer.from(encryptedData, 'base64');
        const { version, salt, iv, authTag, encrypted } = extractEncryptedComponents(data);
        if (version !== exports.CURRENT_ENCRYPTION_VERSION) {
            throw new Error(`Unsupported encryption version: ${version}`);
        }
        if (!this.salt.equals(salt)) {
            throw new Error('Salt mismatch: data was encrypted with a different password/salt');
        }
        const decipher = crypto_1.default.createDecipheriv(exports.ALGORITHM, this.encryptionKey, iv, { authTagLength: exports.AUTH_TAG_LENGTH });
        decipher.setAuthTag(authTag);
        const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
        return decrypted.toString('utf-8');
    }
}
exports.StorageEncryption = StorageEncryption;
function deriveKey(password, salt) {
    return crypto_1.default.pbkdf2Sync(password, salt, exports.PBKDF2_ITERATIONS_V2, exports.KEY_LENGTH, 'sha256');
}
function extractEncryptedComponents(data) {
    if (data.length < HEADER_LENGTH) {
        throw new Error('Invalid encrypted data: too short');
    }
    const version = data[0];
    if (version !== exports.CURRENT_ENCRYPTION_VERSION) {
        throw new Error(`Unsupported encryption version: ${version}`);
    }
    return {
        version,
        salt: data.subarray(VERSION_PREFIX_LENGTH, VERSION_PREFIX_LENGTH + exports.SALT_LENGTH),
        iv: data.subarray(VERSION_PREFIX_LENGTH + exports.SALT_LENGTH, VERSION_PREFIX_LENGTH + exports.SALT_LENGTH + exports.IV_LENGTH),
        authTag: data.subarray(VERSION_PREFIX_LENGTH + exports.SALT_LENGTH + exports.IV_LENGTH, HEADER_LENGTH),
        encrypted: data.subarray(HEADER_LENGTH)
    };
}
/**
 * Decrypts an SDK-format base64 payload given a password. Re-derives the key
 * from the salt embedded in the payload. Used for import where we don't yet
 * know the salt.
 */
function decryptWithPassword(encryptedData, password) {
    const data = Buffer.from(encryptedData, 'base64');
    const { version, salt, iv, authTag, encrypted } = extractEncryptedComponents(data);
    if (version !== exports.CURRENT_ENCRYPTION_VERSION) {
        throw new Error(`Unsupported encryption version: ${version}`);
    }
    const key = deriveKey(password, salt);
    const decipher = crypto_1.default.createDecipheriv(exports.ALGORITHM, key, iv, { authTagLength: exports.AUTH_TAG_LENGTH });
    decipher.setAuthTag(authTag);
    const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
    return decrypted.toString('utf-8');
}
