UNPKG

@node-dlc/core

Version:
222 lines 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TxFactory = void 0; const bitcoin_1 = require("@node-dlc/bitcoin"); const CommitmentNumber_1 = require("./CommitmentNumber"); const HtlcDirection_1 = require("./HtlcDirection"); const ScriptFactory_1 = require("./ScriptFactory"); class TxFactory { /** * Creates a TxOut to attach to a funding transaction. This includes * the P2WSH-P2MS script that uses 2-2MS. The open and accept funding * pubkeys are sorted lexicographcially to create the script. * @param builder */ static createFundingOutput(value, openPubKey, acceptPubKey) { const script = bitcoin_1.Script.p2wshLock(ScriptFactory_1.ScriptFactory.fundingScript(openPubKey, acceptPubKey)); return new bitcoin_1.TxOut(value, script); } /** * Constructs an unsigned commitment transaction according to BOLT3. * This method is a low level commitment transaction builder, meaning * it accepts primatives and constructs a commitment transaction * accordingly. The proper inputs are determiend * * @param isFunderLocal True when the funding node is local. This * is used to determine which output pays fees (to_local/to_remote). * @param commitmentNumber The commitment number of the transaction * which is used to generate the obscurred commitment number. * @param openPaymentBasePoint The basepoint sent in open_channel * which is used to generate the obscurred commitment number. * @param acceptPaymentBasePoint The basepoitn sent in accept_channel * which is used to generate the obscurred commitment number. * @param fundingOutPoint The outpoint of the funding transaction * which was established in funding_created. * @param dustLimitSatoshi The dust limit in sats after which outputs * will be prune * @param feePerKw The fee rate per kiloweight which will be deducted * from the funding node's output * @param localDelay The delay applied to the to_local output * @param localValue Value paid to the to_local RSMC output * @param remoteValue Value paid to the to_emote P2WPKH output * @param revocationPubKey The revocation public key used to in the * to_local and HTLC outputs * @param delayedPubKey The delayed public key used to spend the * to_local output * @param remotePubKey The public key used to spend the to_remote * output * @param reverseHtlcs True when the HTLC direction needs to be * inverted because the holder of this commitment transaction is * our counterparty. * @param localHtlcPubKey The public key used to spend HTLC outputs * by the commitment holder. * @param remoteHtlcPubKey The public key used to spend HTLC outputs * by the commitment counterparty. * @param htlcs A full list of HTLCs that will be selectively * included in the commitment transaction based on the feePerKw. */ static createCommitment(isFunderLocal, commitmentNumber, openPaymentBasePoint, acceptPaymentBasePoint, fundingOutPoint, dustLimitSatoshi, feePerKw, localDelay, localValue, remoteValue, revocationPubKey, delayedPubKey, remotePubKey, reverseHtlcs, localHtlcPubKey, remoteHtlcPubKey, htlcs = []) { const obscuredCommitmentNumber = CommitmentNumber_1.CommitmentNumber.obscure(commitmentNumber, openPaymentBasePoint, acceptPaymentBasePoint); // 1. add the input as the funding outpoint and set the nSequence const tx = new bitcoin_1.TxBuilder(); tx.version = 2; tx.addInput(fundingOutPoint, CommitmentNumber_1.CommitmentNumber.getSequence(obscuredCommitmentNumber)); // 2. set the locktime to the obscurred commitment number tx.locktime = CommitmentNumber_1.CommitmentNumber.getLockTime(obscuredCommitmentNumber); // 3. find unpruned outputs const unprunedHtlcs = []; for (const htlc of htlcs) { const valueInSats = htlc.value.sats; let feeWeight; // HtlcDirection refers to the local nodes perception of the HTLC. // When isLocal, offered uses the HTLC-Timeout weight of 663. When // remote, the commitment is for the remote counterparty and an // offered HTLC is received and will be spent by the remote // counterparty using the HTLC-Success transaction with a weight of 703 if (reverseHtlcs) { feeWeight = htlc.direction === HtlcDirection_1.HtlcDirection.Offered ? BigInt(703) : BigInt(663); } else { feeWeight = htlc.direction === HtlcDirection_1.HtlcDirection.Offered ? BigInt(663) : BigInt(703); } // Calculate the HTLC less fees const feeInSats = (feeWeight * feePerKw) / BigInt(1000); const satsLessFee = valueInSats - feeInSats; // Only keep HTLCs greater than the dustLimitSatoshi for the tx if (satsLessFee >= dustLimitSatoshi.sats) { unprunedHtlcs.push(htlc); } } // 4. calculate base fee const weight = 724 + unprunedHtlcs.length * 172; const baseFee = (BigInt(weight) * feePerKw) / BigInt(1000); // 5. substract base fee from funding node if (isFunderLocal) { const newValue = localValue.sats - baseFee; if (newValue > BigInt(0)) { localValue = bitcoin_1.Value.fromSats(newValue); } else { localValue = bitcoin_1.Value.zero(); } } else { const newValue = remoteValue.sats - baseFee; if (newValue > BigInt(0)) { remoteValue = bitcoin_1.Value.fromSats(newValue); } else { remoteValue = bitcoin_1.Value.zero(); } } // 6/7. add unpruned offered/received HTLCs const txouts = []; for (const htlc of unprunedHtlcs) { const witnessScript = (!reverseHtlcs && htlc.direction === HtlcDirection_1.HtlcDirection.Offered) || (reverseHtlcs && htlc.direction === HtlcDirection_1.HtlcDirection.Accepted) ? ScriptFactory_1.ScriptFactory.offeredHtlcScript(htlc.paymentHash, revocationPubKey, localHtlcPubKey, remoteHtlcPubKey) : ScriptFactory_1.ScriptFactory.receivedHtlcScript(htlc.paymentHash, htlc.cltvExpiry, revocationPubKey, localHtlcPubKey, remoteHtlcPubKey); const txout = new bitcoin_1.TxOut(htlc.value, bitcoin_1.Script.p2wshLock(witnessScript)); txouts.push([txout, htlc]); } // 8. add local if unpruned if (localValue.sats >= dustLimitSatoshi.sats) { txouts.push([ new bitcoin_1.TxOut(localValue, bitcoin_1.Script.p2wshLock(ScriptFactory_1.ScriptFactory.toLocalScript(revocationPubKey, delayedPubKey, localDelay))), ]); } // 9. add remote if unpruned if (remoteValue.sats >= dustLimitSatoshi.sats) { txouts.push([new bitcoin_1.TxOut(remoteValue, bitcoin_1.Script.p2wpkhLock(remotePubKey))]); } // 10. sort outputs using bip69 and using cltv for htlc tiebreaks txouts.sort((a, b) => { // compare on value const value = Number(a[0].value.sats - b[0].value.sats); if (value !== 0) return value; // compare on script const scriptCompare = a[0].scriptPubKey .serializeCmds() .compare(b[0].scriptPubKey.serializeCmds()); if (scriptCompare !== 0) return scriptCompare; // tie-break on htlcs return b[1].cltvExpiry - a[1].cltvExpiry; }); // add hte outputs in sorted order const sortedHtlcs = []; for (const [txout, htlc] of txouts) { tx.addOutput(txout); sortedHtlcs.push(htlc); } // return the tuple with the sorted htlcs return [tx, sortedHtlcs]; } /** * Constructs an HTLC-Timeout transaction as defined in BOLT3. This * transaction spends an offered HTLC from the commitment transaction * and outputs the HTLC value less the fee. The output is spendable * via an RSMC that is sequence locked for the received by the * transaction owner. Finally this transaction has an absolute * locktime of the HTLC's cltv expiry. * @param commitmentTx * @param outputIndex * @param localDelay * @param revocationPubKey * @param delayedPubKey * @param feePerKw * @param htlc */ static createHtlcTimeout(commitmentTx, outputIndex, localDelay, revocationPubKey, delayedPubKey, feePerKw, htlc) { const tx = new bitcoin_1.TxBuilder(); // Input points to the commmitment transaction and the BIP69 // sorted index of the HTLC. nSequence is set to zero. tx.addInput(new bitcoin_1.OutPoint(commitmentTx, outputIndex), bitcoin_1.Sequence.zero()); // calc value less fees for this transaction const weight = BigInt(663); const fees = (weight * feePerKw) / BigInt(1000); const sats = fees > htlc.value.sats ? 0 : htlc.value.sats - fees; // Spends a P2WSH RSMC tx.addOutput(bitcoin_1.Value.fromSats(sats), bitcoin_1.Script.p2wshLock(ScriptFactory_1.ScriptFactory.toLocalScript(revocationPubKey, delayedPubKey, localDelay))); // nLocktime is set to the cltvExpiry of the HTLC. This prevents // the HTLC-Timeout from being broadcast until after the expiry // has been reached. tx.locktime = new bitcoin_1.LockTime(htlc.cltvExpiry); return tx; } /** * Constructs an HTLC-Success transaction as defined in BOLT3. This * transaction spends a received HTLC form the commitment transaction * and outputs the HTLC value less the fee. The output is spendable * via an RSMC that is sequence locked for the received by the * transaction owner. * @param commitmentTx * @param outputIndex * @param localDelay * @param revocationPubKey * @param delayedPubKey * @param feePerKw * @param htlc */ static createHtlcSuccess(commitmentTx, outputIndex, localDelay, revocationPubKey, delayedPubKey, feePerKw, htlc) { const tx = new bitcoin_1.TxBuilder(); // Input points to the commmitment transaction and the BIP69 // sorted index of the HTLC. nSequence is set to zero. tx.addInput(new bitcoin_1.OutPoint(commitmentTx, outputIndex), bitcoin_1.Sequence.zero()); // calc value less fees for this transaction const weight = BigInt(703); const fees = (weight * feePerKw) / BigInt(1000); const sats = fees > htlc.value.sats ? 0 : htlc.value.sats - fees; // Spends a P2WSH RSMC tx.addOutput(bitcoin_1.Value.fromSats(sats), bitcoin_1.Script.p2wshLock(ScriptFactory_1.ScriptFactory.toLocalScript(revocationPubKey, delayedPubKey, localDelay))); // nLockTime is zero since the tx owner can immediately spend // this transaction if they have the preimage tx.locktime = bitcoin_1.LockTime.zero(); return tx; } } exports.TxFactory = TxFactory; //# sourceMappingURL=TxFactory.js.map