import base64 from '@root/encoding/base64.js';
import hex from '@root/encoding/hex.js';
import bytes from '@root/encoding/bytes.js';
import { Address, Bn, PrivKey, PubKey } from '@ts-bitcoin/core';
import el from 'elliptic';
import * as jose from 'jose';
import padStart from 'lodash/padStart.js';
import { Envelope } from 'univrse';
import crypto from '../runtime/crypto.js';
import { MB_DERIVING_KEY, MB_SIGNING_KEY, OutputType, } from './types.js';
import { DocV1 } from './envelopes/doc/v1.js';
import { pigiV2 } from './envelopes/envelopes.js';
import { PeppolV1 } from './envelopes/peppol/v1.js';
import { NATURE_ADDRESS } from './mintblue.js';
export { crypto };
export { jose };
export { hex };
export { base64 };
export * from './cbor-jose.js';
const DEFAULT_PBKDF2_ITERATIONS_SENSITIVE = 200000;
//const PBKDF2_ITERATIONS_INTERMEDIATE = 10000;
export async function deriveKey(password, salt, iterations = DEFAULT_PBKDF2_ITERATIONS_SENSITIVE, keyLength = 256) {
    const importedKey = await crypto.subtle.importKey('raw', bytes.strToBuf(password), 'PBKDF2', false, ['deriveKey']);
    const cryptoKey = await crypto.subtle.deriveKey({
        name: 'PBKDF2',
        salt: base64.base64ToBuf(salt),
        iterations: iterations,
        hash: 'SHA-256',
    }, importedKey, { name: 'AES-GCM', length: keyLength }, true, ['encrypt', 'decrypt']);
    return {
        salt: salt,
        cryptoKey,
        rawKey: await crypto.subtle.exportKey('raw', cryptoKey),
    };
}
export async function serializeKeys(keys) {
    const serializedKeys = JSON.stringify({
        kek: await exportKey(keys.kek, 'base64'),
        masterKey: await exportKey(keys.masterKey, 'base64'),
        recoveryKey: await exportKey(keys.recoveryKey, 'base64'),
        privateKey: keys.privateKey.toWif(),
        publicKeyHex: keys.publicKeyHex,
    });
    const encrypted = await encrypt(bytes.strToBuf(serializedKeys));
    const b64 = {
        encryptedData: base64.bufToBase64(encrypted.encryptedData),
        iv: base64.bufToBase64(encrypted.iv),
        key: base64.bufToBase64(encrypted.key),
    };
    return JSON.stringify(b64);
}
export async function deserializeKeys(str) {
    const { encryptedData, iv, key } = JSON.parse(str);
    const s = JSON.parse(bytes.bufToStr(await decrypt(base64.base64ToBuf(encryptedData), base64.base64ToBuf(iv), await importSymmetricKey(key, 'base64'))));
    return {
        kek: await importSymmetricKey(s.kek, 'base64'),
        masterKey: await importSymmetricKey(s.masterKey, 'base64'),
        recoveryKey: await importSymmetricKey(s.recoveryKey, 'base64'),
        privateKey: PrivKey.fromWif(s.privateKey),
        publicKeyHex: s.publicKeyHex,
        additionalKeys: await generateAdditionalKeys(await importSymmetricKey(s.masterKey, 'base64')),
    };
}
export async function saveKeysInStorage(keys, storage) {
    storage.setItem('keys', await serializeKeys(keys));
}
export async function loadKeysFromStorage(storage) {
    const serializedKeys = storage.getItem('keys');
    if (!serializedKeys) {
        return null;
    }
    return deserializeKeys(serializedKeys);
}
export async function getRecoveryKey(keys, userKeyAttributes) {
    const recoveryKey = await decryptFromB64String(userKeyAttributes.recoveryKeyEncryptedWithMasterKey, keys.masterKey);
    return new Uint8Array(recoveryKey);
}
export function generateSaltToDeriveKey(saltLength = 32) {
    const salt = crypto.getRandomValues(new Uint8Array(saltLength));
    return base64.bufToBase64(salt);
}
export async function generateUserKeyAttributes(password, keys) {
    const salt = generateSaltToDeriveKey();
    const { cryptoKey } = await deriveKey(password, salt, DEFAULT_PBKDF2_ITERATIONS_SENSITIVE);
    // These keys are used if they are present in supplied keys
    const masterKey = keys ? keys.masterKey : await generateSymmetricKey();
    const recoveryKey = keys ? keys.recoveryKey : await generateSymmetricKey();
    const privateKey = keys ? keys.privateKey : PrivKey.fromRandom();
    const publicKey = PubKey.fromPrivKey(privateKey);
    const masterKeyEncryptedWithKek = await encryptToB64String(await exportKey(masterKey), cryptoKey);
    const masterKeyEncryptedWithRecoveryKey = await encryptToB64String(await exportKey(masterKey), recoveryKey);
    const recoveryKeyEncryptedWithMasterKey = await encryptToB64String(await exportKey(recoveryKey), masterKey);
    const privateKeyEncryptedWithMasterKey = await encryptToB64String(privateKey.toBuffer(), masterKey);
    return {
        userKeyAttributes: {
            masterKeyEncryptedWithKek,
            masterKeyEncryptedWithRecoveryKey,
            recoveryKeyEncryptedWithMasterKey,
            privateKeyEncryptedWithMasterKey,
            publicKeyHex: publicKey.toHex(),
            kekSalt: salt,
            kekIterations: DEFAULT_PBKDF2_ITERATIONS_SENSITIVE,
        },
        masterKey,
    };
}
/**
 * Regenerates a userKeyAttributes from a recovery key and a new password, keeping the same recovery key
 *
 * @param userKeyAttributes
 * @param newPassword
 * @param recoveryKeyHex
 * @returns
 */
export async function recoverUserKeyAttributes(userKeyAttributes, newPassword, recoveryKeyHex) {
    const keys = await getKeysFromUserKeyAttributes(null, userKeyAttributes, recoveryKeyHex);
    return await generateUserKeyAttributes(newPassword, keys);
}
/**
 * Either give password or recoveryKeyHex
 * userKeyAttributes is required
 *
 * @param password
 * @param userKeyAttributes
 * @param recoveryKeyHex
 * @returns
 */
export async function getKeysFromUserKeyAttributes(password, // pass null when you give recoveryKeyHex
userKeyAttributes, recoveryKeyHex) {
    let decryptedMasterKey;
    let cryptoKey;
    if (!userKeyAttributes) {
        throw new Error('You must give a userKeyAttributes');
    }
    if (password && !recoveryKeyHex) {
        const derivedFrompassword = await deriveKey(password, userKeyAttributes.kekSalt, userKeyAttributes.kekIterations);
        cryptoKey = derivedFrompassword.cryptoKey;
        decryptedMasterKey = await decryptFromB64String(userKeyAttributes.masterKeyEncryptedWithKek, cryptoKey);
    }
    else if (password === null && recoveryKeyHex) {
        const importedRecoveryKey = await importSymmetricKey(hex.hexToBuf(recoveryKeyHex));
        cryptoKey = importedRecoveryKey;
        try {
            decryptedMasterKey = await decryptFromB64String(userKeyAttributes.masterKeyEncryptedWithRecoveryKey, cryptoKey);
        }
        catch (e) {
            throw new Error('Could not decrypt with recovery key');
        }
    }
    else {
        throw new Error('Either give a password or a recovery key');
    }
    const masterKey = await importSymmetricKey(decryptedMasterKey);
    const decryptedRecoveryKey = await decryptFromB64String(userKeyAttributes.recoveryKeyEncryptedWithMasterKey, masterKey);
    const recoveryKey = await importSymmetricKey(decryptedRecoveryKey);
    const decryptedPrivateKey = await decryptFromB64String(userKeyAttributes.privateKeyEncryptedWithMasterKey, masterKey);
    const privateKey = PrivKey.fromBuffer(Buffer.from(decryptedPrivateKey));
    const publicKey = PubKey.fromPrivKey(privateKey);
    if (publicKey.toHex() !== userKeyAttributes.publicKeyHex) {
        throw new Error('Private Public key mismatch');
    }
    const additionalKeys = await generateAdditionalKeys(masterKey);
    const keys = {
        masterKey,
        recoveryKey,
        privateKey,
        publicKeyHex: PubKey.fromPrivKey(privateKey).toHex(),
        kek: cryptoKey,
        additionalKeys,
    };
    return keys;
}
async function generateSecretsForKey(rawMasterKey, name) {
    const rawbits = await sha256hmac(new Uint8Array(rawMasterKey), toUint8Array(name));
    const bn = new Bn(rawbits);
    const n = new Bn('ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551', 'hex');
    const modded = bn.mod(n).add(new Bn(1));
    const secret = modded.toBuffer();
    const ec = new el.ec(el.curves['p256']);
    const keyfrompriv = ec.keyFromPrivate(secret);
    const pubKey = new Uint8Array(keyfrompriv.getPublic(false, 'array')).slice(1);
    const xs = pubKey.slice(0, 32);
    const ys = pubKey.slice(32);
    return {
        secret,
        xs,
        ys,
    };
}
async function generateDerivationBits(rawMasterKey) {
    const keyNames = ['peppol', 'kvk', MB_DERIVING_KEY];
    const additionalKeys = [];
    for (const name of keyNames) {
        const { secret, xs, ys } = await generateSecretsForKey(rawMasterKey, name);
        const jwk = {
            kty: 'EC',
            crv: 'P-256',
            d: base64.bufToUrlBase64(secret),
            x: base64.bufToUrlBase64(xs),
            y: base64.bufToUrlBase64(ys),
        };
        const kid = await jose.calculateJwkThumbprint(jwk, 'sha256');
        const cryptoKey = await crypto.subtle.importKey('jwk', jwk, { name: 'ECDH', namedCurve: 'P-256' }, true, [
            'deriveBits',
        ]);
        additionalKeys.push({
            info: name,
            kid: kid,
            jwk: jwk,
            cryptoKey: cryptoKey,
        });
    }
    return additionalKeys;
}
async function generateSigningKeys(rawMasterKey) {
    const { secret, xs, ys } = await generateSecretsForKey(rawMasterKey, MB_SIGNING_KEY);
    const jwk = {
        kty: 'EC',
        crv: 'P-256',
        d: base64.bufToUrlBase64(secret),
        x: base64.bufToUrlBase64(xs),
        y: base64.bufToUrlBase64(ys),
    };
    const kid = await jose.calculateJwkThumbprint(jwk, 'sha256');
    const cryptoKey = await crypto.subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign']);
    return {
        info: MB_SIGNING_KEY,
        kid,
        jwk,
        cryptoKey,
    };
}
async function generateAdditionalKeys(masterKey) {
    // Add experimental peppol key
    const rawMasterKey = await crypto.subtle.exportKey('raw', masterKey);
    const derivationKeys = await generateDerivationBits(rawMasterKey);
    const signingKey = await generateSigningKeys(rawMasterKey);
    return [...derivationKeys, signingKey];
}
export async function importSymmetricKey(key, format = 'raw') {
    let decodedKey;
    switch (format) {
        case 'base64':
            decodedKey = base64.base64ToBuf(key);
            break;
        case 'hex':
            decodedKey = hex.hexToBuf(key);
            break;
        default: // 'raw'
            decodedKey = key;
    }
    return await crypto.subtle.importKey('raw', decodedKey, 'AES-GCM', true, ['encrypt', 'decrypt']);
}
export async function generateSymmetricKey() {
    return await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
}
export async function exportKey(key, format) {
    let exportedKey;
    if (format === 'jwk') {
        exportedKey = await crypto.subtle.exportKey('jwk', key);
        return exportedKey;
    }
    else {
        exportedKey = await crypto.subtle.exportKey('raw', key);
    }
    const uint8ArrayData = new Uint8Array(exportedKey);
    if (format === 'hex') {
        return hex.bufToHex(uint8ArrayData);
    }
    if (format === 'base64') {
        return base64.bufToBase64(uint8ArrayData);
    }
    return uint8ArrayData;
}
export async function encrypt(data, key) {
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const k = key ? await importSymmetricKey(key) : await generateSymmetricKey();
    const encryptedData = await crypto.subtle.encrypt({
        name: 'AES-GCM',
        iv: iv,
    }, k, data);
    return {
        encryptedData: new Uint8Array(encryptedData),
        iv: iv,
        key: await exportKey(k),
    };
}
export async function decrypt(data, iv, key) {
    const decrypted = await crypto.subtle.decrypt({
        name: 'AES-GCM',
        iv: iv,
    }, key, data);
    return new Uint8Array(decrypted);
}
export async function encryptToB64String(data, key) {
    try {
        const { encryptedData, iv } = await encrypt(new Uint8Array(data), ArrayBuffer.isView(key) ? key : await exportKey(key));
        const buff = new Uint8Array(iv.byteLength + encryptedData.byteLength);
        buff.set(iv, 0);
        buff.set(encryptedData, iv.byteLength);
        return base64.bufToBase64(buff);
    }
    catch (e) {
        console.log(`Error - ${e}`);
        return '';
    }
}
export async function decryptFromB64String(data, key) {
    const encryptedDataBuff = base64.base64ToBuf(data);
    const iv = encryptedDataBuff.slice(0, 12);
    const encryptedData = encryptedDataBuff.slice(12);
    try {
        const decryptedContent = await crypto.subtle.decrypt({
            name: 'AES-GCM',
            iv: iv,
        }, key, encryptedData);
        return new Uint8Array(decryptedContent);
    }
    catch (e) {
        console.log(`Error - ${e}`);
        return new Uint8Array([]);
    }
}
export function toUint8Array(data) {
    return typeof data === 'string' ? bytes.strToBuf(data) : data;
}
/**
 * Returns a Uint8Array string
 * @param data
 * @returns Uint8Array
 */
export async function sha1(data) {
    return crypto.subtle.digest('SHA-1', toUint8Array(data)).then((result) => new Uint8Array(result));
}
/**
 * Returns a hex string
 * @param data
 * @returns hex string
 */
export async function sha1s(data) {
    return hex.bufToHex(await sha1(data));
}
/**
 * Returns a Uint8Array string
 * @param data
 * @returns Uint8Array
 */
export async function sha256(data) {
    return crypto.subtle.digest('SHA-256', toUint8Array(data)).then((result) => new Uint8Array(result));
}
/**
 * Returns a hex string
 * @param data
 * @returns hex string
 */
export async function sha256s(data) {
    return hex.bufToHex(await sha256(data));
}
/**
 * Returns a Uint8Array
 * @param data
 * @returns hex string
 */
export async function sha512(data) {
    return crypto.subtle.digest('SHA-512', toUint8Array(data)).then((result) => new Uint8Array(result));
}
/**
 * Returns a hex string
 * @param data
 * @returns hex string
 */
export async function sha512s(data) {
    return hex.bufToHex(await sha512(data));
}
/**
 * Returns a buffer
 * @param data
 * @returns buffer
 */
export async function sha256hmac(data, message) {
    const algorithm = { name: 'HMAC', hash: 'SHA-256' };
    const key = await crypto.subtle.importKey('raw', toUint8Array(message), algorithm, false, ['sign', 'verify']);
    const signature = await crypto.subtle.sign(algorithm.name, key, toUint8Array(data));
    const digest = new Uint8Array(signature);
    return digest;
}
/**
 * Returns a hex string
 * @param data
 * @returns hex string
 */
export async function sha256hmacs(data, message) {
    return hex.bufToHex(await sha256hmac(data, message));
}
/**
 * Identifies an output type
 */
export function identifyOutput(output) {
    var _a, _b, _c, _d, _e;
    // try to parse when value = 0 sats
    if (output.valueBn.isZero()) {
        const protocolId = (_b = (_a = output.script.chunks[2]) === null || _a === void 0 ? void 0 : _a.buf) === null || _b === void 0 ? void 0 : _b.toString();
        // check pigi & peppol
        if (protocolId === pigiV2.PROTOCOL_ID && output.script.chunks[3].buf.toString('hex', 0, 2) === '0000') {
            return OutputType.pigi;
        }
        else if (protocolId === PeppolV1.PROTOCOL_ID) {
            return OutputType.peppol;
        }
        else if (protocolId === DocV1.PROTOCOL_ID) {
            return OutputType.doc;
        }
        // check file/data/hash (univrse)
        try {
            const envelope = Envelope.fromScript(output.script);
            const type = ((_e = (_d = (_c = envelope === null || envelope === void 0 ? void 0 : envelope.header) === null || _c === void 0 ? void 0 : _c.headers) === null || _d === void 0 ? void 0 : _d.meta) === null || _e === void 0 ? void 0 : _e.type)
                ? OutputType[envelope.header.headers.meta.type]
                : OutputType[OutputType.data];
            return type;
        }
        catch (e) {
            return OutputType[OutputType.undefined];
            // no univrse envelope
            // TODO: reasonable output
        }
    }
    else if (output.script.isPubKeyHashOut() && Address.fromTxOutScript(output.script).toString() === NATURE_ADDRESS) {
        // check nature
        return OutputType[OutputType.tree];
    }
    else if (output.script.isPubKeyHashOut()) {
        // check payment
        return OutputType[OutputType.payment];
    }
    return OutputType[OutputType.undefined];
}
export function JWKToUncompressedHex(jwk) {
    const uncompressedPublicKey = '04' + hex.bufToHex(base64.base64ToBuf(jwk.x)) + hex.bufToHex(base64.base64ToBuf(jwk.y));
    return uncompressedPublicKey;
}
/**
 * Compress a uncompressed public key (hex)
 * @param key uncompressed key 04...
 * @returns compressed key
 */
export function compressPublicKey(key) {
    validateHexKey(key);
    const keyLength = (key.length - 2) / 2;
    const x = key.slice(2, keyLength + 2);
    const y = key.slice(2 + keyLength, key.length);
    const isYOdd = parseInt(y[y.length - 1], 16) % 2 === 1;
    const prefix = isYOdd ? '03' : '02';
    return prefix + x;
}
/**
 * Get information about a hex compressed or uncompressed public key
 * Is able to decompress a compressed key if curve is provided
 *
 * @param key hex string of compressed or uncompressed public key
 * @param curve
 * @returns HexKeyInfo - Y coordinate is only available if uncompressed or compressed and curve is provided
 */
export function getHexKeyInfo(key, curve) {
    validateHexKey(key);
    let xh, yh, isYOdd, publicKey, compactPublicKey;
    const isCompactKey = key.substring(0, 2) === '02' || key.substring(0, 2) === '03';
    if (isCompactKey) {
        // Get X coordinate
        xh = key.slice(2, key.length);
        // Determine if the y coordinate is odd or even based on the key prefix
        isYOdd = key.slice(0, 2) === '03';
        compactPublicKey = key;
        if (curve) {
            // Calculate Y point
            const ec = new el.ec(curve);
            const point = ec.curve.pointFromX(xh, isYOdd);
            yh = point.getY().toString(16, 2);
            yh = padStart(yh, 64, '0');
            publicKey = '04' + xh + yh;
        }
    }
    else {
        // key is uncompressed
        publicKey = key;
        // Extract X and Y coordinates
        const keyLength = (key.length - 2) / 2;
        xh = key.slice(2, keyLength + 2);
        yh = key.slice(2 + keyLength, key.length);
        // Determine if Y is odd
        isYOdd = parseInt(yh[yh.length - 1], 16) % 2 === 1;
        compactPublicKey = compressPublicKey(key);
    }
    const xb = base64.bufToUrlBase64(hex.hexToBuf(xh));
    const yb = yh ? base64.bufToUrlBase64(hex.hexToBuf(yh)) : undefined;
    const jwk = {
        x: xb,
        y: yb,
        kty: 'EC',
        crv: curve === 'p256' ? 'P-256' : 'secp256k1',
    };
    return {
        xh,
        xb,
        yh,
        yb,
        isYOdd,
        isCompressedKey: isCompactKey,
        publicKey: publicKey,
        compactPublicKey: compactPublicKey,
        jwk,
    };
}
export async function getSHA1Fingerprint(jwk) {
    const uncompressedPubkey = JWKToUncompressedHex(jwk);
    const bytes = hex.hexToBuf(uncompressedPubkey);
    const fingerprint = await sha1s(bytes);
    return fingerprint;
}
export function validateHexKey(key) {
    // Check if the compressed key is a valid hexadecimal string
    if (!/^[0-9A-Fa-f]+$/.test(key)) {
        throw new Error('Invalid hexadecimal string');
    }
    // Check if the compressed key has the correct length (33 bytes for secp256k1 curve) (includes prefix byte)
    if (key.length !== 33 * 2 && key.length !== 65 * 2) {
        throw new Error('Invalid key length');
    }
    // Check if the key has the correct prefix (02 or 03 for compressed, 04 for uncompressed)
    if (key.slice(0, 2) !== '02' && key.slice(0, 2) !== '03' && key.slice(0, 2) !== '04') {
        throw new Error('Invalid key prefix');
    }
}
function canonicalCurveName(crv) {
    if (crv === 'p256' || crv === 'P-256')
        return 'P-256';
    if (crv === 'secp256k1')
        return 'secp256k1';
    return 'P-256';
}
export async function JWKOrHexKeyToCryptoKeyAndJWK(key, type, crv) {
    let jwk;
    if (typeof key === 'string') {
        const { xb, yb } = getHexKeyInfo(key, crv);
        jwk = {
            x: xb,
            y: yb,
            kty: 'EC',
            crv: canonicalCurveName(crv),
        };
    }
    else if (typeof key === 'object' && key.x && key.y) {
        jwk = {
            x: key.x,
            y: key.y,
            d: key.d,
            kty: key.kty || 'EC',
            crv: key.crv || canonicalCurveName(crv),
        };
    }
    const cryptoKey = await crypto.subtle.importKey('jwk', jwk, {
        name: type,
        namedCurve: canonicalCurveName(crv),
    }, true, type === 'ECDH' ? [] : ['sign']);
    return { cryptoKey, jwk: await jose.exportJWK(cryptoKey) };
}
export async function pbkdf2(buf, salt, keyLengthBits, iterations) {
    const importedKey = await crypto.subtle.importKey('raw', buf, 'PBKDF2', false, ['deriveKey', 'deriveBits']);
    const cryptoKey = await crypto.subtle.deriveBits({
        name: 'PBKDF2',
        salt: salt,
        iterations: iterations,
        hash: 'SHA-256',
    }, importedKey, keyLengthBits);
    return new Uint8Array(cryptoKey);
}
export function concat(arr1, arr2) {
    const mergedArray = new Uint8Array(arr1.length + arr2.length);
    mergedArray.set(arr1);
    mergedArray.set(arr2, arr1.length);
    return mergedArray;
}
export async function parseSDKToken(token, SDKTokenPrefix, APITokenPrefix) {
    let tokenId;
    let tokenSecret;
    if (token.startsWith(SDKTokenPrefix)) {
        tokenId = token.substring(SDKTokenPrefix.length).split('.')[0];
        tokenSecret = token.substring(SDKTokenPrefix.length).split('.')[1];
    }
    else if (token.includes(':')) {
        [tokenId, tokenSecret] = token.split(':');
    }
    else if (token.includes('.')) {
        [tokenId, tokenSecret] = token.split('.');
    }
    else {
        throw new Error('Unable to parse SDK token. Did you forgot to give an SDK token? It looks like "secret-token:mintBlue.com/sdk/XXXXXXX.XXXXXXXXXXXXXXXXX"');
    }
    const bearerToken = `${APITokenPrefix}${tokenId}.${await sha256s(tokenSecret)}`;
    return { tokenId, tokenSecret, bearerToken };
}
//# sourceMappingURL=utils.js.map