import base62 from '@fry/base62';
import base64 from '@root/encoding/base64.js';
import bytes from '@root/encoding/bytes.js';
import hex from '@root/encoding/hex.js';
import { Script } from '@ts-bitcoin/core';
import cbor from 'cbor';
import * as jose from 'jose';
import { cbor2jose, jose2cbor } from '../../cbor-jose.js';
import * as lzma from '../../lzma.js';
import { crypto, JWKOrHexKeyToCryptoKeyAndJWK, pbkdf2, sha256, sha256hmac } from '../../utils.js';
/**
 * DocV1 Envelope
 *
 */
export class DocV1 {
    constructor(options) {
        this.createOptions = options.create;
        this.parseOptions = options.parse;
        this.info = options.info;
    }
    static async calculateSecrets(data, iterations = 200000) {
        const hash = await sha256(data);
        const cryptoKey = await pbkdf2(data, hash, 512, iterations);
        const secret1 = cryptoKey.slice(0, 32);
        const secret2 = cryptoKey.slice(32);
        const rawCek = await sha256hmac(secret1, secret2);
        const cek = await crypto.subtle.importKey('raw', rawCek, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);
        const cekKID = await jose.calculateJwkThumbprint(await jose.exportJWK(cek));
        return { cek, secret1, secret2, cekKID, iterations, hash };
    }
    static async create(options) {
        // TODO validate DocV1Output
        const receivers = [];
        if (options.receivers && Array.isArray(options.receivers) && options.receivers.length > 0) {
            for (const receiver of options.receivers) {
                const { jwk, cryptoKey } = await JWKOrHexKeyToCryptoKeyAndJWK(receiver, 'ECDH', 'p256');
                const kid = await jose.calculateJwkThumbprint(jwk);
                receivers.push([cryptoKey, kid]);
            }
        }
        const { cek, cekKID, secret1, secret2, iterations, hash } = await DocV1.calculateSecrets(options.data);
        const payload = await cbor.encodeAsync([
            options.data,
            options.options.filename,
            options.options.mimetype,
            options.options.meta,
        ]);
        const compressed = await lzma.compress(payload, 9);
        const _l0 = new jose.GeneralEncrypt(compressed);
        _l0
            .setProtectedHeader({ enc: 'A256GCM', iter: iterations, salt: hash })
            .addRecipient(cek)
            .setUnprotectedHeader({ alg: 'A256GCMKW', kid: cekKID }); // add key id
        for (const [k, kid] of receivers) {
            _l0.addRecipient(k).setUnprotectedHeader({ alg: 'ECDH-ES+A256KW', kid: kid });
        }
        const l0 = await _l0.encrypt();
        const cl0 = await jose2cbor(l0);
        const _l1 = await new jose.GeneralSign(cl0);
        for (const signingKey of options.signers) {
            const { jwk, cryptoKey } = await JWKOrHexKeyToCryptoKeyAndJWK(signingKey, 'ECDSA', 'p256');
            const kid = await jose.calculateJwkThumbprint(jwk);
            _l1.addSignature(cryptoKey).setProtectedHeader({ alg: 'ES256', kid: kid });
        }
        const l1 = await _l1.sign();
        const _l2 = new jose.GeneralEncrypt(await jose2cbor(l1));
        const secret1Key = await crypto.subtle.importKey('raw', secret1, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);
        const secret1KeyKID = await jose.calculateJwkThumbprint(await jose.exportJWK(secret1Key));
        _l2
            .setProtectedHeader({ enc: 'A256GCM' })
            .addRecipient(secret1Key)
            .setUnprotectedHeader({ alg: 'A256GCMKW', kid: secret1KeyKID }); // add key id
        for (const [k, kid] of receivers) {
            _l2.addRecipient(k).setUnprotectedHeader({ alg: 'ECDH-ES+A256KW', kid: kid });
        }
        const l2 = await _l2.encrypt();
        const final = await jose2cbor(l2);
        const info = {
            type: 'doc',
            hash,
            signatures: l1.signatures,
            secret1: secret1,
            secret1Hex: hex.bufToHex(secret1),
            secret1Base62: base62.encode(secret1),
            secret2: secret2,
            secret2Hex: hex.bufToHex(secret2),
            secret2Base62: base62.encode(secret2),
            encryptedData: final,
            receiverKeyIds: receivers.map((r) => r[1]),
            PROTOCOL_ID: DocV1.PROTOCOL_ID,
            safeScriptArray: [bytes.strToBuf(DocV1.PROTOCOL_ID), final],
            iterations,
            data: options.data,
            options: {
                meta: options.options.meta,
                filename: options.options.filename,
                mimetype: options.options.mimetype,
            },
        };
        return new DocV1({ create: options, parse: null, info: info });
    }
    static async parse(options) {
        let l1key, l2key;
        if (options.secret1 && options.secret2) {
            const rawCek = await sha256hmac(options.secret1, options.secret2);
            const cek = await crypto.subtle.importKey('raw', rawCek, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);
            l1key = cek;
            l2key = options.secret1;
        }
        else if (options.receiverKey) {
            const importedPrivateKey = await jose.importJWK(options.receiverKey, 'ECDH-ES');
            l1key = l2key = importedPrivateKey;
        }
        const l2 = await cbor2jose(options.encryptedData);
        const l2decrypted = await jose.generalDecrypt(l2, l2key);
        const l1 = await cbor2jose(l2decrypted.plaintext);
        let l0;
        if (options.verifyKey) {
            const l1verify = await jose.generalVerify(l1, options.verifyKey);
            l0 = await cbor2jose(l1verify.payload);
        }
        else {
            // Signature present but not choosing to verify
            l0 = await cbor2jose(base64.base64ToBuf(l1.payload));
        }
        const l0decrypted = await jose.generalDecrypt(l0, l1key);
        const l0decrypteddata = l0decrypted.plaintext;
        const decompressed = await lzma.decompress(l0decrypteddata);
        const [originaldata, filename, mimetype, meta] = cbor.decode(decompressed);
        const { secret1, secret2, hash } = await DocV1.calculateSecrets(originaldata, l0decrypted.protectedHeader.iter);
        const s1 = options.secret1 || secret1;
        const s2 = options.secret2 || secret2;
        const info = {
            type: 'doc',
            hash,
            signatures: l1.signatures,
            secret1: s1,
            secret1Hex: hex.bufToHex(s1),
            secret1Base62: base62.encode(s1),
            secret2: s2,
            secret2Hex: hex.bufToHex(s2),
            secret2Base62: base62.encode(s2),
            encryptedData: options.encryptedData,
            receiverKeyIds: [],
            PROTOCOL_ID: DocV1.PROTOCOL_ID,
            safeScriptArray: [bytes.strToBuf(DocV1.PROTOCOL_ID), options.encryptedData],
            iterations: l0decrypted.protectedHeader.iter,
            data: originaldata,
            options: {
                meta,
                filename,
                mimetype,
            },
        };
        return new DocV1({ create: null, parse: options, info: info });
    }
    toOpReturnScriptHex() {
        const script = Script.fromSafeDataArray(this.info.safeScriptArray.map((x) => Buffer.from(x)));
        return script.toHex();
    }
}
DocV1.PROTOCOL_ID = 'mB:doc1';
//# sourceMappingURL=v1.js.map