/**
 * Passphrase-wrapped bundle format for `studiograph admin backup` /
 * `restore`.
 *
 * Wire format (single binary file):
 *   magic     [4]  ASCII "SG1\0"
 *   version   [1]  0x01
 *   salt      [16] random per encrypt; fed to scrypt
 *   nonce     [24] random per encrypt; fed to xchacha20poly1305
 *   ciphertext (variable) — AEAD output (ciphertext + 16-byte tag)
 *
 * The AAD is the magic+version+salt+nonce header bytes themselves, so
 * tampering with any header field invalidates the tag.
 *
 * Crypto choices:
 *   - scrypt(passphrase, salt, N=2^17, r=8, p=1, keylen=32) → 32B key.
 *     N=131072 is the OWASP 2023 recommendation for password-hashing.
 *     Tunable upwards in the future via the `version` byte.
 *   - xchacha20poly1305(key, nonce, aad) → ciphertext + 16B tag.
 *     Same primitive used by the encrypted secrets store
 *     ([src/core/secrets/encryption.ts]), so we don't fan out crypto
 *     dependencies for a single new feature.
 *
 * What this module does NOT do: stream encryption. The whole bundle is
 * loaded into memory for encrypt/decrypt. Workspaces are typically
 * 1-100 MB; if a workspace ever grows past ~500MB this should be
 * upgraded to a streaming AEAD (libsodium secretstream / age-style
 * chunked AEAD). For now the simpler one-shot design is correct.
 */
import { scryptSync, randomBytes } from 'crypto';
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
const MAGIC = Buffer.from('SG1\0', 'binary');
const VERSION_V1 = 0x01;
const SALT_LEN = 16;
const NONCE_LEN = 24;
const KEY_LEN = 32;
const HEADER_LEN = MAGIC.length + 1 + SALT_LEN + NONCE_LEN;
// scrypt cost: N must be a power of 2; r * p < 2^30; memory ≈ 128 * r * N bytes.
// At N=2^17, r=8 we use ~128MB of RAM per derive. Fine for a CLI step that
// runs at most once per backup/restore, mean to take 0.5-2s on a laptop.
const SCRYPT_N = 1 << 17;
const SCRYPT_R = 8;
const SCRYPT_P = 1;
function deriveKey(passphrase, salt) {
    if (!passphrase)
        throw new Error('passphrase is required');
    return scryptSync(passphrase, salt, KEY_LEN, {
        N: SCRYPT_N,
        r: SCRYPT_R,
        p: SCRYPT_P,
        maxmem: 256 * 1024 * 1024,
    });
}
export function sealBundle(plaintext, passphrase) {
    const salt = randomBytes(SALT_LEN);
    const nonce = randomBytes(NONCE_LEN);
    const versionByte = Buffer.from([VERSION_V1]);
    const header = Buffer.concat([MAGIC, versionByte, salt, nonce]);
    const key = deriveKey(passphrase, salt);
    // AAD = header bytes. Any tamper to magic/version/salt/nonce trips the tag.
    const cipher = xchacha20poly1305(new Uint8Array(key), new Uint8Array(nonce), new Uint8Array(header));
    const ct = cipher.encrypt(new Uint8Array(plaintext));
    return Buffer.concat([header, Buffer.from(ct)]);
}
export function openBundle(bundle, passphrase) {
    if (bundle.length < HEADER_LEN + 16) {
        throw new Error('bundle: truncated (too small to contain header + AEAD tag)');
    }
    const magic = bundle.subarray(0, MAGIC.length);
    if (!magic.equals(MAGIC)) {
        throw new Error('bundle: bad magic (not a Studiograph backup file)');
    }
    const version = bundle[MAGIC.length];
    if (version !== VERSION_V1) {
        throw new Error(`bundle: unsupported version 0x${version.toString(16)}`);
    }
    const salt = bundle.subarray(MAGIC.length + 1, MAGIC.length + 1 + SALT_LEN);
    const nonce = bundle.subarray(MAGIC.length + 1 + SALT_LEN, HEADER_LEN);
    const header = bundle.subarray(0, HEADER_LEN);
    const ct = bundle.subarray(HEADER_LEN);
    const key = deriveKey(passphrase, Buffer.from(salt));
    const cipher = xchacha20poly1305(new Uint8Array(key), new Uint8Array(nonce), new Uint8Array(header));
    try {
        const pt = cipher.decrypt(new Uint8Array(ct));
        return Buffer.from(pt);
    }
    catch {
        // Surface a useful message without leaking ciphertext bytes.
        throw new Error('bundle: wrong passphrase or corrupted file (AEAD tag mismatch)');
    }
}
// Exported for tests + future format-version migrations.
export const BUNDLE_FORMAT = { MAGIC, VERSION_V1, SALT_LEN, NONCE_LEN, KEY_LEN, HEADER_LEN };
//# sourceMappingURL=crypto-bundle.js.map