Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@ const originalTest = global.test;
const NUM_ITERATIONS = 10;

global.test = (name, fn, timeout) => {
for (let i = 0; i < NUM_ITERATIONS; i++) {
originalTest(`${name} (iteration ${i + 1})`, fn, timeout);
if (name.includes("@once")) {
originalTest(name, fn, timeout);
} else {
for (let i = 0; i < NUM_ITERATIONS; i++) {
originalTest(`${name} (iteration ${i + 1})`, fn, timeout);
}
}
};

const originalIt = global.it;

global.it = (name, fn, timeout) => {
for (let i = 0; i < NUM_ITERATIONS; i++) {
originalIt(`${name} (iteration ${i + 1})`, fn, timeout);
if (name.includes("@once")) {
originalIt(name, fn, timeout);
} else {
for (let i = 0; i < NUM_ITERATIONS; i++) {
originalIt(`${name} (iteration ${i + 1})`, fn, timeout);
}
}
};
3 changes: 2 additions & 1 deletion lib/covenantV1/locking.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ export declare function lockingTransaction(scripts: {
}, amount: number, changeAddress: string, inputUTXOs: UTXO[], network: networks.Network, feeRate: number, publicKeyNoCoord?: Buffer, lockHeight?: number): PsbtTransactionResult;
export declare function withdrawalTimeLockTransaction(scripts: {
lockingScript: Buffer;
}, lockingTransaction: Transaction, withdrawalAddress: string, feeRate: number, network: networks.Network, outputIndex?: number): {
}, lockingTransaction: Transaction, withdrawalAddress: string, feeRate: number, network: networks.Network, outputIndex?: number, timePosition?: number, currentHeight?: number): {
psbt: Psbt;
};
export declare function withdrawalUnbondingTransaction(scripts: {
lockingScript: Buffer;
dataEmbedScript?: Buffer;
}, lockingTransaction: Transaction, withdrawalAddress: string, feeRate: number, network: networks.Network, outputIndex?: number): {
psbt: Psbt;
};
59 changes: 26 additions & 33 deletions lib/covenantV1/locking.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,4 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildLockingScript = void 0;
exports.lockingTransaction = lockingTransaction;
Expand All @@ -33,8 +10,6 @@ Object.defineProperty(exports, "buildLockingScript", { enumerable: true, get: fu
const fee_1 = require("../utils/fee");
const constants_1 = require("../constants");
const feeV1_1 = require("../utils/feeV1");
const ecc = __importStar(require("@bitcoin-js/tiny-secp256k1-asmjs"));
(0, bitcoinjs_lib_1.initEccLib)(ecc);
function lockingTransaction(scripts, amount, changeAddress, inputUTXOs, network, feeRate, publicKeyNoCoord, lockHeight) {
// Check that amount and fee rate are non-negative integers greater than 0
if (!Number.isInteger(amount) || amount <= 0 || !Number.isInteger(feeRate) || feeRate <= 0) {
Expand All @@ -61,7 +36,7 @@ function lockingTransaction(scripts, amount, changeAddress, inputUTXOs, network,
];
const { selectedUTXOs, fee } = (0, feeV1_1.getSpendTxInputUTXOsAndFees)(network, inputUTXOs, amount, feeRate, psbtOutputs);
selectedUTXOs.forEach((input) => {
psbt.addInput({
const newInput = {
hash: input.txid,
index: input.vout,
witnessUtxo: {
Expand All @@ -71,7 +46,14 @@ function lockingTransaction(scripts, amount, changeAddress, inputUTXOs, network,
// this is needed only if the wallet is in taproot mode
...(publicKeyNoCoord && { tapInternalKey: publicKeyNoCoord }),
sequence: 0xfffffffd // Enable locktime by setting the sequence value to (RBF-able)
});
};
if (input.redeemScript) {
newInput.redeemScript = input.redeemScript;
}
if (input.rawTransaction) {
newInput.nonWitnessUtxo = Buffer.from(input.rawTransaction, "hex");
}
psbt.addInput(newInput);
});
// Add the locking output to the transaction
psbt.addOutputs(psbtOutputs);
Expand Down Expand Up @@ -106,16 +88,15 @@ function lockingTransaction(scripts, amount, changeAddress, inputUTXOs, network,
fee
};
}
function withdrawalTimeLockTransaction(scripts, lockingTransaction, withdrawalAddress, feeRate, network, outputIndex = 0) {
function withdrawalTimeLockTransaction(scripts, lockingTransaction, withdrawalAddress, feeRate, network, outputIndex = 0, timePosition = 5, currentHeight) {
if (feeRate <= 0) {
throw new Error("fee rate must be bigger than 0");
}
const decompiled = bitcoinjs_lib_1.script.decompile(scripts.lockingScript);
if (!decompiled) {
throw new Error("Timelock script is not valid");
}
// position of time in the timelock script
const timePosition = 5;
const isAbsoluteTimelock = decompiled.includes(bitcoinjs_lib_1.script.OPS.OP_CHECKLOCKTIMEVERIFY);
let timelock = 0;
if (Buffer.isBuffer(decompiled[timePosition])) {
const timeBuffer = decompiled[timePosition];
Expand All @@ -137,8 +118,8 @@ function withdrawalTimeLockTransaction(scripts, lockingTransaction, withdrawalAd
value: lockingTransaction.outs[outputIndex].value,
script: lockingTransaction.outs[outputIndex].script
},
witnessScript: scripts.lockingScript, // Adding witnessScript here
sequence: timelock
witnessScript: scripts.lockingScript,
sequence: isAbsoluteTimelock ? 0xfffffffe : timelock
});
const estimatedFee = (0, feeV1_1.getWithdrawTxFee)(feeRate, lockingTransaction.outs[outputIndex].script);
const outputValue = lockingTransaction.outs[outputIndex].value - estimatedFee;
Expand All @@ -152,6 +133,12 @@ function withdrawalTimeLockTransaction(scripts, lockingTransaction, withdrawalAd
address: withdrawalAddress,
value: outputValue
});
if (isAbsoluteTimelock) {
if (!currentHeight) {
throw new Error("Current height is required for absolute timelock");
}
psbt.setLocktime(currentHeight);
}
return { psbt };
}
function withdrawalUnbondingTransaction(scripts, lockingTransaction, withdrawalAddress, feeRate, network, outputIndex = 0) {
Expand All @@ -171,7 +158,7 @@ function withdrawalUnbondingTransaction(scripts, lockingTransaction, withdrawalA
},
witnessScript: scripts.lockingScript // Adding witnessScript here
});
const estimatedFee = (0, feeV1_1.getWithdrawTxFee)(feeRate, lockingTransaction.outs[outputIndex].script);
const estimatedFee = (0, feeV1_1.getWithdrawTxFee)(feeRate, lockingTransaction.outs[outputIndex].script, scripts.dataEmbedScript);
const outputValue = lockingTransaction.outs[outputIndex].value - estimatedFee;
if (outputValue < constants_1.BTC_DUST_SAT) {
throw new Error("Output value is smaller than dust");
Expand All @@ -180,5 +167,11 @@ function withdrawalUnbondingTransaction(scripts, lockingTransaction, withdrawalA
address: withdrawalAddress,
value: outputValue
});
if (scripts.dataEmbedScript) {
psbt.addOutput({
script: scripts.dataEmbedScript,
value: 0
});
}
return { psbt };
}
8 changes: 8 additions & 0 deletions lib/covenantV1/locking.script.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@
* @return {Buffer}
*/
export declare function buildLockingScript(evmAddress: Buffer, delegatorKey: Buffer, validatorKey: Buffer, transferTimeLock: number, validatorIndex: Buffer, nonce: Buffer): Buffer;
/**
* Script to validate pre-deposit transactions.
* This version only requires validator signature in the ELSE path.
* @param {Buffer} lockerKey - The public key of the locker (user).
* @param {number} transferTimeLock - The block count for the sequence verification.
* @return {Buffer}
*/
export declare function buildPreDepositLockingScript(lockerKey: Buffer, transferTimeLock: number): Buffer;
26 changes: 26 additions & 0 deletions lib/covenantV1/locking.script.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildLockingScript = buildLockingScript;
exports.buildPreDepositLockingScript = buildPreDepositLockingScript;
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
const constants_1 = require("../constants");
// @ts-ignore
Expand Down Expand Up @@ -62,3 +63,28 @@ function buildLockingScript(evmAddress, delegatorKey, validatorKey, transferTime
bitcoinjs_lib_1.opcodes.OP_ENDIF
]);
}
/**
* Script to validate pre-deposit transactions.
* This version only requires validator signature in the ELSE path.
* @param {Buffer} lockerKey - The public key of the locker (user).
* @param {number} transferTimeLock - The block count for the sequence verification.
* @return {Buffer}
*/
function buildPreDepositLockingScript(lockerKey, transferTimeLock) {
if (!Buffer.isBuffer(lockerKey)) {
throw new Error("Invalid input types");
}
if (lockerKey.length !== constants_1.PK_LENGTH) {
throw new Error("Invalid input lengths");
}
if (typeof transferTimeLock !== "number" || transferTimeLock < 0 || transferTimeLock > 65535) {
throw new Error("Invalid numeric inputs");
}
return bitcoinjs_lib_1.script.compile([
bitcoinjs_lib_1.script.number.encode(transferTimeLock),
bitcoinjs_lib_1.opcodes.OP_CHECKLOCKTIMEVERIFY,
bitcoinjs_lib_1.opcodes.OP_DROP,
lockerKey,
bitcoinjs_lib_1.opcodes.OP_CHECKSIG
]);
}
2 changes: 1 addition & 1 deletion lib/tsconfig.tsbuildinfo

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion lib/utils/feeV1/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export declare const calculateSpendAmountAndFee: (network: Network, availableUTX
*
* @param {number} feeRate - The fee rate in satoshis per vbyte.
* @param {Buffer} script - The scriptPubKey of the output being spent.
* @param {Buffer} dataEmbedScript - The script of the data embed output.
* @return {number} The estimated fee for a withdrawal transaction in satoshis.
*/
export declare const getWithdrawTxFee: (feeRate: number, script: Buffer) => number;
export declare const getWithdrawTxFee: (feeRate: number, script: Buffer, dataEmbedScript?: Buffer) => number;
8 changes: 6 additions & 2 deletions lib/utils/feeV1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,15 @@ exports.calculateSpendAmountAndFee = calculateSpendAmountAndFee;
*
* @param {number} feeRate - The fee rate in satoshis per vbyte.
* @param {Buffer} script - The scriptPubKey of the output being spent.
* @param {Buffer} dataEmbedScript - The script of the data embed output.
* @return {number} The estimated fee for a withdrawal transaction in satoshis.
*/
const getWithdrawTxFee = (feeRate, script) => {
const getWithdrawTxFee = (feeRate, script, dataEmbedScript) => {
const inputSize = (0, utils_1.getInputSizeByScript)(script);
const outputSize = (0, utils_1.getEstimatedChangeOutputSize)();
let outputSize = (0, utils_1.getEstimatedChangeOutputSize)();
if (dataEmbedScript && (0, utils_1.isOP_RETURN)(dataEmbedScript)) {
outputSize += dataEmbedScript.length + constants_2.OP_RETURN_OUTPUT_VALUE_SIZE + constants_2.OP_RETURN_VALUE_SERIALIZE_SIZE;
}
return (feeRate *
(inputSize +
outputSize +
Expand Down
2 changes: 1 addition & 1 deletion scripts/lockingV1.demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class LockingProtocol {
inputUTXOs,
network,
feeRate,
lockHeight
lockHeight as any
)

const signedLockingPsbtHex = await this.wallet.signPsbt(psbt.toHex());
Expand Down
Loading