"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const sdk_1 = require("@bsv/sdk");
const offsetKey_1 = require("../offsetKey");
const TestUtilsWalletStorage_1 = require("../../../../test/utils/TestUtilsWalletStorage");
const Setup_1 = require("../../../Setup");
const StorageKnex_1 = require("../../StorageKnex");
const utilityHelpers_1 = require("../../../utility/utilityHelpers");
const WalletStorageManager_1 = require("../../WalletStorageManager");
describe('offsetKey tests', () => {
    jest.setTimeout(99999999);
    test('1_offsetPrivKey', async () => {
        const bn2 = sdk_1.BigNumber.fromHex('FFF0000000000000000000000000000000000000000000000000000000000100', 'big');
        const priv2 = new sdk_1.PrivateKey(bn2);
        const privKey2 = priv2.toWif();
        const keyOffset = 'KyaVZ1AnxYN4oB8JnxYVyZ8xYC9ySpq2Umzx6jwzQGVo71k1EgSt';
        const oPrivKey = 'KyMYVLNeyF4qQsgHW3N1eJv9WcRd2aZC8hw7iLgCojQsyizqKsV4';
        const r12 = (0, offsetKey_1.offsetPrivKey)(privKey2, keyOffset);
        expect(r12.keyOffset).toBe(keyOffset);
        expect(r12.offsetPrivKey).toBe(oPrivKey);
    });
    test('2_offsetPubKey', async () => {
        const bn2 = sdk_1.BigNumber.fromHex('FFF0000000000000000000000000000000000000000000000000000000000100', 'big');
        const priv2 = new sdk_1.PrivateKey(bn2);
        const pub2 = priv2.toPublicKey();
        const keyOffset = 'KyaVZ1AnxYN4oB8JnxYVyZ8xYC9ySpq2Umzx6jwzQGVo71k1EgSt';
        const oPrivKey = 'KyMYVLNeyF4qQsgHW3N1eJv9WcRd2aZC8hw7iLgCojQsyizqKsV4';
        const oPubKey = '024b4362ce98e0afd22bf3319831cfaf691ad2f08471a3386bcda98d65435a0f24';
        const r22 = (0, offsetKey_1.offsetPubKey)(pub2.toString(), keyOffset);
        expect(r22.keyOffset).toBe(keyOffset);
        expect(r22.offsetPubKey).toBe(oPubKey);
        const pubKey2 = sdk_1.PrivateKey.fromWif(oPrivKey).toPublicKey().toString();
        expect(pubKey2).toBe(oPubKey);
    });
    test('3_lockScriptWithKeyOffsetFromPubKey', async () => {
        const pubKey = '0397742eaef6c7f08c4aa057397d45529f93ab90345b84ce5a5aac06ea9cdd132e';
        const ko = 'Kx9MjojdkjL3bEo5tQwHpwT1voKN1z56NjpATsa2Sx6QTrVjgMQJ';
        const script = '76a9149d09d0ee09b212c548f6b1a7835641f33654246788ac';
        const r1 = (0, offsetKey_1.lockScriptWithKeyOffsetFromPubKey)(pubKey, ko);
        expect(r1.script).toBe(script);
        expect(r1.keyOffset).toBe(ko);
        // And with a random keyOffset...
        const r2 = (0, offsetKey_1.lockScriptWithKeyOffsetFromPubKey)(pubKey);
        expect(r2.script).not.toBe(script);
        expect(r2.keyOffset).not.toBe(ko);
    });
    test('4a_check keyOffset address', async () => {
        if (TestUtilsWalletStorage_1._tu.noEnv('main'))
            return;
        const env = TestUtilsWalletStorage_1._tu.getEnv('main');
        const privHex = env.devKeys[env.commissionsIdentity];
        const priv = sdk_1.PrivateKey.fromHex(privHex);
        const pub = priv.toPublicKey();
        const keyOffset = 'L2hMY5uW6Vh46DEFMzrYiKSFWDRSMGDTsaeDvhiKNNJGihwKD17w';
        const r = (0, offsetKey_1.offsetPrivKey)(priv.toWif(), keyOffset);
        const privO = sdk_1.PrivateKey.fromWif(r.offsetPrivKey);
        const address = privO.toAddress();
        expect(address).toBe('1EZz5oxwXoG6LgGLxeYPeg1NfzQrP1vL6M');
    });
    test.skip('4_redeemServiceCharges', async () => {
        var _a;
        if (TestUtilsWalletStorage_1._tu.noEnv('main'))
            return;
        const env = TestUtilsWalletStorage_1._tu.getEnv('main');
        if (!env.devKeys[env.commissionsIdentity]) {
            throw new Error('No dev key for commissions identity');
        }
        const knex = Setup_1.Setup.createMySQLKnex(process.env.MAIN_CLOUD_MYSQL_CONNECTION);
        const storage = new StorageKnex_1.StorageKnex({
            chain: env.chain,
            knex: knex,
            commissionSatoshis: 0,
            commissionPubKeyHex: undefined,
            feeModel: { model: 'sat/kb', value: 1 }
        });
        let setup;
        await storage.makeAvailable();
        setup = await TestUtilsWalletStorage_1._tu.createTestWalletWithStorageClient({
            chain: 'main',
            rootKeyHex: env.devKeys[env.commissionsIdentity]
        });
        storage.setServices(setup.services);
        try {
            // await setup.wallet.abortAction({ reference: 'e9c03bdf603e90ebe482044e8d0f7afbf2d6fe480a13dbd8689e2e5e5183bed4' })
            // txid b6f72df4224efbacab42a16e1e88f48c217f03929c36987b9067d2556de47c10
            // height 922107
            // incorrect hash 000000000000000014d97d19bf82956c1f7ce3977da10b7fbdab9a10653c02e7
            //   correct hash 00000000000000001957bfadf841d1709d5039b3243c33ba58e4a6a97b44d2a8
            const sm = new WalletStorageManager_1.WalletStorageManager(setup.identityKey, storage);
            sm.setServices(setup.services);
            // This should correct invalid merkle proofs in proven_txs but currently does not because WoC returns invalid header info.
            await sm.reproveHeader('000000000000000014d97d19bf82956c1f7ce3977da10b7fbdab9a10653c02e7');
            const fca = {
                partial: { isRedeemed: false },
                paged: { limit: 400, offset: 0 }
            };
            for (;;) {
                const comms = [];
                let beef = new sdk_1.Beef();
                const chainTracker = await setup.services.getChainTracker();
                const inputs = [];
                for (; comms.length < fca.paged.limit;) {
                    const unredeemedComms = await storage.findCommissions(fca);
                    if (unredeemedComms.length < 1)
                        break;
                    for (const comm of unredeemedComms) {
                        fca.paged.offset += 1;
                        const tt = (0, utilityHelpers_1.verifyTruthy)(await storage.findTransactionById(comm.transactionId, undefined, true));
                        if (tt.provenTxId && tt.txid) {
                            await storage.getBeefForTransaction(tt.txid, { mergeToBeef: beef, chainTracker, skipInvalidProofs: true });
                            const tx = (0, utilityHelpers_1.verifyTruthy)(beef.findTxid(tt.txid)).tx;
                            const commVOut = tx.outputs.findIndex(o => o.satoshis === comm.satoshis && o.lockingScript.toHex() === sdk_1.Utils.toHex(comm.lockingScript));
                            const commOut = tx.outputs[commVOut];
                            const input = {
                                outpoint: `${tt.txid}.${commVOut}`,
                                inputDescription: `commId:${comm.commissionId}`,
                                unlockingScriptLength: 108
                            };
                            inputs.push(input);
                            comms.push(comm);
                            if (comms.length === fca.paged.limit)
                                break;
                        }
                    }
                }
                if (comms.length < fca.paged.limit)
                    // Only redeem full quota of commissions per cycle to avoid paying higher percentage of fees.
                    break;
                if (comms.length > 0) {
                    fca.paged.offset -= comms.length;
                    console.log(beef.toLogString());
                    const verified = await beef.verify(chainTracker, false);
                    expect(verified).toBe(true);
                    const cao = {
                        randomizeOutputs: false,
                        //signAndProcess: false,
                        noSend: true
                    };
                    const ca = {
                        description: 'redeem commissions',
                        inputs: inputs,
                        inputBEEF: beef.toBinary(),
                        options: cao
                    };
                    const car = await setup.wallet.createAction(ca);
                    expect(car.signableTransaction).toBeTruthy();
                    const st = car.signableTransaction;
                    expect(st.reference).toBeTruthy();
                    const atomicBeef = sdk_1.Beef.fromBinary(st.tx);
                    const txid = atomicBeef.txs[atomicBeef.txs.length - 1].txid;
                    const tx = atomicBeef.findTransactionForSigning(txid);
                    const priv = sdk_1.PrivateKey.fromHex(env.devKeys[env.commissionsIdentity]);
                    const pub = priv.toPublicKey();
                    const curve = new sdk_1.Curve();
                    const p2pkh = new sdk_1.P2PKH();
                    const spends = {};
                    let vin = 0;
                    // set an unlockingScriptTemplate for each commission input being redeemed in unsigned tx
                    for (const comm of comms) {
                        const { hashedSecret } = (0, offsetKey_1.keyOffsetToHashedSecret)(pub, comm.keyOffset);
                        const bn = priv.add(hashedSecret).mod(curve.n);
                        const offsetPrivKey = new sdk_1.PrivateKey(bn);
                        const unlock = p2pkh.unlock(offsetPrivKey, 'all', false);
                        tx.inputs[vin].unlockingScriptTemplate = unlock;
                        vin++;
                    }
                    // sign each input
                    await tx.sign();
                    vin = 0;
                    // extract all the signed unlocking scripts
                    for (const comm of comms) {
                        const script = tx.inputs[vin].unlockingScript;
                        const unlockingScript = script.toHex();
                        spends[vin] = { unlockingScript };
                        vin++;
                    }
                    const signArgs = {
                        reference: st.reference,
                        spends,
                        options: {
                            returnTXIDOnly: true,
                            noSend: true
                        }
                    };
                    // Forward all the unlocking scripts to storage and create the ProvenTxReq for the noSend txid.
                    const sr = await setup.wallet.signAction(signArgs);
                    expect(sr.txid).toBeTruthy();
                    // Update the commissions as redeemed in storage
                    for (const comm of comms) {
                        await storage.updateCommission(comm.commissionId, { isRedeemed: true });
                    }
                    {
                        // Get the transaction broadcast
                        const createArgs = {
                            description: `broadcasting noSend`,
                            options: {
                                acceptDelayedBroadcast: false,
                                sendWith: [sr.txid]
                            }
                        };
                        const cr = await setup.wallet.createAction(createArgs);
                        expect(cr.noSendChange).not.toBeTruthy();
                        expect((_a = cr.sendWithResults) === null || _a === void 0 ? void 0 : _a.length).toBe(1);
                        const [swr] = cr.sendWithResults;
                        expect(swr.status !== 'failed').toBe(true);
                    }
                }
            }
        }
        catch (err) {
            console.error('Error in 4_redeemServiceCharges test:', err);
            throw err;
        }
        await storage.destroy();
        await setup.wallet.destroy();
    });
    test.skip('4a_redeemServiceCharges optimized', async () => {
        var _a;
        if (TestUtilsWalletStorage_1._tu.noEnv('main'))
            return;
        const env = TestUtilsWalletStorage_1._tu.getEnv('main');
        if (!env.devKeys[env.commissionsIdentity]) {
            throw new Error('No dev key for commissions identity');
        }
        const knex = Setup_1.Setup.createMySQLKnex(process.env.MAIN_CLOUD_MYSQL_CONNECTION);
        const storage = new StorageKnex_1.StorageKnex({
            chain: env.chain,
            knex: knex,
            commissionSatoshis: 0,
            commissionPubKeyHex: undefined,
            feeModel: { model: 'sat/kb', value: 1 }
        });
        let setup;
        await storage.makeAvailable();
        setup = await TestUtilsWalletStorage_1._tu.createTestWalletWithStorageClient({
            chain: 'main',
            rootKeyHex: env.devKeys[env.commissionsIdentity]
        });
        storage.setServices(setup.services);
        try {
            const fca = {
                partial: { isRedeemed: false },
                paged: { limit: 400, offset: 0 }
            };
            for (;;) {
                const comms = [];
                let beef = new sdk_1.Beef();
                const chainTracker = await setup.services.getChainTracker();
                const inputs = [];
                // This query would be much faster and allow valid proofs to be merged into beef directly...
                // but will blow up if rawTx are large.
                // This is really a case where a full Beef isn't needed.
                // All the inputs will be from proven txs.
                // Processors should accept the aggregate rawTx without any other input rawTxs or merkle proofs.
                // i.e. All the input txids should be "known"
                const r = await storage.knex.raw(`
          SELECT c.*, t.provenTxId, p.height, p.index, p.merklePath, p.rawTx, p.blockHash, p.merkleRoot
          FROM commissions c
          JOIN transactions t ON c.transactionId = t.transactionId
          JOIN proven_txs p ON t.provenTxId = p.provenTxId
          WHERE c.isRedeemed = 0
          AND NOT t.provenTxId IS NOT NULL
          ORDER BY c.commissionId
          LIMIT ? OFFSET ?;
        `, [fca.paged.limit, fca.paged.offset]);
                for (; comms.length < fca.paged.limit;) {
                    const unredeemedComms = await storage.findCommissions(fca);
                    if (unredeemedComms.length < 1)
                        break;
                    for (const comm of unredeemedComms) {
                        fca.paged.offset += 1;
                        const tt = (0, utilityHelpers_1.verifyTruthy)(await storage.findTransactionById(comm.transactionId, undefined, true));
                        if (tt.provenTxId && tt.txid) {
                            await storage.getBeefForTransaction(tt.txid, { mergeToBeef: beef, chainTracker, skipInvalidProofs: true });
                            const tx = (0, utilityHelpers_1.verifyTruthy)(beef.findTxid(tt.txid)).tx;
                            const commVOut = tx.outputs.findIndex(o => o.satoshis === comm.satoshis && o.lockingScript.toHex() === sdk_1.Utils.toHex(comm.lockingScript));
                            const commOut = tx.outputs[commVOut];
                            const input = {
                                outpoint: `${tt.txid}.${commVOut}`,
                                inputDescription: `commId:${comm.commissionId}`,
                                unlockingScriptLength: 108
                            };
                            inputs.push(input);
                            comms.push(comm);
                            if (comms.length === fca.paged.limit)
                                break;
                        }
                    }
                }
                if (comms.length < fca.paged.limit)
                    // Only redeem full quota of commissions per cycle to avoid paying higher percentage of fees.
                    break;
                if (comms.length > 0) {
                    fca.paged.offset -= comms.length;
                    console.log(beef.toLogString());
                    const verified = await beef.verify(chainTracker, false);
                    expect(verified).toBe(true);
                    const cao = {
                        randomizeOutputs: false,
                        //signAndProcess: false,
                        noSend: true
                    };
                    const ca = {
                        description: 'redeem commissions',
                        inputs: inputs,
                        inputBEEF: beef.toBinary(),
                        options: cao
                    };
                    const car = await setup.wallet.createAction(ca);
                    expect(car.signableTransaction).toBeTruthy();
                    const st = car.signableTransaction;
                    expect(st.reference).toBeTruthy();
                    const atomicBeef = sdk_1.Beef.fromBinary(st.tx);
                    const txid = atomicBeef.txs[atomicBeef.txs.length - 1].txid;
                    const tx = atomicBeef.findTransactionForSigning(txid);
                    const priv = sdk_1.PrivateKey.fromHex(env.devKeys[env.commissionsIdentity]);
                    const pub = priv.toPublicKey();
                    const curve = new sdk_1.Curve();
                    const p2pkh = new sdk_1.P2PKH();
                    const spends = {};
                    let vin = 0;
                    // set an unlockingScriptTemplate for each commission input being redeemed in unsigned tx
                    for (const comm of comms) {
                        const { hashedSecret } = (0, offsetKey_1.keyOffsetToHashedSecret)(pub, comm.keyOffset);
                        const bn = priv.add(hashedSecret).mod(curve.n);
                        const offsetPrivKey = new sdk_1.PrivateKey(bn);
                        const unlock = p2pkh.unlock(offsetPrivKey, 'all', false);
                        tx.inputs[vin].unlockingScriptTemplate = unlock;
                        vin++;
                    }
                    // sign each input
                    await tx.sign();
                    vin = 0;
                    // extract all the signed unlocking scripts
                    for (const comm of comms) {
                        const script = tx.inputs[vin].unlockingScript;
                        const unlockingScript = script.toHex();
                        spends[vin] = { unlockingScript };
                        vin++;
                    }
                    const signArgs = {
                        reference: st.reference,
                        spends,
                        options: {
                            returnTXIDOnly: true,
                            noSend: true
                        }
                    };
                    // Forward all the unlocking scripts to storage and create the ProvenTxReq for the noSend txid.
                    const sr = await setup.wallet.signAction(signArgs);
                    expect(sr.txid).toBeTruthy();
                    // Update the commissions as redeemed in storage
                    for (const comm of comms) {
                        await storage.updateCommission(comm.commissionId, { isRedeemed: true });
                    }
                    {
                        // Get the transaction broadcast
                        const createArgs = {
                            description: `broadcasting noSend`,
                            options: {
                                acceptDelayedBroadcast: false,
                                sendWith: [sr.txid]
                            }
                        };
                        const cr = await setup.wallet.createAction(createArgs);
                        expect(cr.noSendChange).not.toBeTruthy();
                        expect((_a = cr.sendWithResults) === null || _a === void 0 ? void 0 : _a.length).toBe(1);
                        const [swr] = cr.sendWithResults;
                        expect(swr.status !== 'failed').toBe(true);
                    }
                }
            }
        }
        catch (err) {
            console.error('Error in 4_redeemServiceCharges test:', err);
            throw err;
        }
        await storage.destroy();
        await setup.wallet.destroy();
    });
});
//# sourceMappingURL=offsetKey.test.js.map