@bsv/wallet-toolbox
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
382 lines • 20.2 kB
JavaScript
"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