Skip to content

Add HTLC support to BOLT 3 commitment transaction - #181

Open
NishantBansal2003 wants to merge 14 commits into
lnfuzz:masterfrom
NishantBansal2003:htlc-commitment
Open

Add HTLC support to BOLT 3 commitment transaction#181
NishantBansal2003 wants to merge 14 commits into
lnfuzz:masterfrom
NishantBansal2003:htlc-commitment

Conversation

@NishantBansal2003

Copy link
Copy Markdown
Contributor

ref: #111

Bolts ref:

Verification

Anchor test vectors (local signatures): https://github.com/ACINQ/eclair/blob/master/eclair-core/src/test/resources/bolt3-tx-test-vectors-anchor-outputs-zero-fee-htlc-tx-format.txt

Custom tests on top of commit ACINQ/eclair@26d035070 (after this commit, eclair removed support for non-anchor channels), with the diff below:

Eclair diff
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala
index 1931aa8b1..1623fb59e 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala
@@ -456,6 +456,199 @@ class StaticRemoteKeyTestVectorSpec extends TestVectorsSpec {
   override def filename: String = "/bolt3-tx-test-vectors-static-remotekey-format.txt"
   override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey)
   // @formatter:on
+
+  test("commitment_tx_with_htlc_amount_msat_not_multiple_of_1000_legacy") {
+    val name = "commitment_tx_with_htlc_amount_msat_not_multiple_of_1000_legacy"
+    val htlc = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 2000999 msat, ByteVector32.Zeroes, CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val spec = CommitmentSpec(Set(htlc), FeeratePerKw(647 sat), toLocal = (7000000000L - 2000999L) msat, toRemote = 3000000000L msat)
+    val dustLimit = 546.sat
+
+    println()
+    println(s"name: $name")
+
+    val outputs = Transactions.makeCommitTxOutputs(
+      Local.funding_pubkey,
+      Remote.funding_pubkey,
+      localCommitmentKeys.publicKeys,
+      payCommitTxFees = true,
+      dustLimit,
+      Local.toSelfDelay,
+      spec,
+      commitmentFormat
+    )
+    val unsignedCommitTx = Transactions.makeCommitTx(
+      commitTxInput = commitmentInput,
+      commitTxNumber = Local.commitTxNumber,
+      localPaymentBasePoint = Local.payment_basepoint,
+      remotePaymentBasePoint = Remote.payment_basepoint,
+      localIsChannelOpener = true,
+      outputs = outputs)
+    val local_sig = unsignedCommitTx.sign(Local.funding_privkey, Remote.funding_pubkey)
+    val remote_sig = unsignedCommitTx.sign(Remote.funding_privkey, Local.funding_pubkey)
+    println(s"local_signature: ${Scripts.der(local_sig.sig).dropRight(1).toHex}")
+    println(s"remote_signature: ${Scripts.der(remote_sig.sig).dropRight(1).toHex}")
+    val commitTx = unsignedCommitTx.aggregateSigs(Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig)
+    Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+
+    val htlcTxs = Transactions.makeHtlcTxs(commitTx, outputs, commitmentFormat).sortBy(_.input.outPoint.index)
+    println(s"num_htlcs: ${htlcTxs.length}")
+    htlcTxs.foreach {
+      case tx: UnsignedHtlcTimeoutTx =>
+        val remoteHtlcSig = tx.localSig(remoteCommitmentKeys)
+        val withRemoteSig = tx.addRemoteSig(localCommitmentKeys, remoteHtlcSig)
+        val localHtlcSig = withRemoteSig.localSig(WalletInputs(Nil, None))
+        println(s"# signature for output #${tx.input.outPoint.index} (htlc-timeout for htlc #${tx.htlcId})")
+        println(s"local_htlc_signature: ${Scripts.der(localHtlcSig).dropRight(1).toHex}")
+        println(s"remote_htlc_signature: ${Scripts.der(remoteHtlcSig).dropRight(1).toHex}")
+      case tx: UnsignedHtlcSuccessTx =>
+        fail(s"unexpected htlc-success tx for htlc #${tx.htlcId}")
+    }
+  }
+
+  test("commitment_tx_with_htlc_value_equals_non_htlc_value_legacy") {
+    val name = "commitment_tx_with_htlc_value_equals_non_htlc_value_legacy"
+    val htlc = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 2000000000 msat, ByteVector32.Zeroes, CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val spec = CommitmentSpec(Set(htlc), FeeratePerKw(15_000 sat), toLocal = (8000000000L - 2000000000L) msat, toRemote = 2000000000L msat)
+    val dustLimit = 546.sat
+
+    println()
+    println(s"name: $name")
+
+    val outputs = Transactions.makeCommitTxOutputs(
+      Local.funding_pubkey,
+      Remote.funding_pubkey,
+      localCommitmentKeys.publicKeys,
+      payCommitTxFees = true,
+      dustLimit,
+      Local.toSelfDelay,
+      spec,
+      commitmentFormat
+    )
+    val unsignedCommitTx = Transactions.makeCommitTx(
+      commitTxInput = commitmentInput,
+      commitTxNumber = Local.commitTxNumber,
+      localPaymentBasePoint = Local.payment_basepoint,
+      remotePaymentBasePoint = Remote.payment_basepoint,
+      localIsChannelOpener = true,
+      outputs = outputs)
+    val local_sig = unsignedCommitTx.sign(Local.funding_privkey, Remote.funding_pubkey)
+    val remote_sig = unsignedCommitTx.sign(Remote.funding_privkey, Local.funding_pubkey)
+    println(s"local_signature: ${Scripts.der(local_sig.sig).dropRight(1).toHex}")
+    println(s"remote_signature: ${Scripts.der(remote_sig.sig).dropRight(1).toHex}")
+    val commitTx = unsignedCommitTx.aggregateSigs(Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig)
+    Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+
+    val htlcTxs = Transactions.makeHtlcTxs(commitTx, outputs, commitmentFormat).sortBy(_.input.outPoint.index)
+    println(s"num_htlcs: ${htlcTxs.length}")
+    htlcTxs.foreach {
+      case tx: UnsignedHtlcTimeoutTx =>
+        val remoteHtlcSig = tx.localSig(remoteCommitmentKeys)
+        val withRemoteSig = tx.addRemoteSig(localCommitmentKeys, remoteHtlcSig)
+        val localHtlcSig = withRemoteSig.localSig(WalletInputs(Nil, None))
+        println(s"# signature for output #${tx.input.outPoint.index} (htlc-timeout for htlc #${tx.htlcId})")
+        println(s"local_htlc_signature: ${Scripts.der(localHtlcSig).dropRight(1).toHex}")
+        println(s"remote_htlc_signature: ${Scripts.der(remoteHtlcSig).dropRight(1).toHex}")
+      case tx: UnsignedHtlcSuccessTx =>
+        fail(s"unexpected htlc-success tx for htlc #${tx.htlcId}")
+    }
+  }
+
+  test("commitment_tx_with_non_htlc_outputs_between_htlc_outputs_legacy") {
+    val name = "commitment_tx_with_non_htlc_outputs_between_htlc_outputs_legacy"
+    val htlc1 = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 2000000000L msat, ByteVector32.Zeroes, CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val htlc2 = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 1, 4000000000L msat, ByteVector32.Zeroes, CltvExpiry(501), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val spec = CommitmentSpec(Set[DirectedHtlc](htlc1, htlc2), FeeratePerKw(15000 sat), toLocal = (7000000000L - 2000000000L - 4000000000L) msat, toRemote = 3000000000L msat)
+    val dustLimit = 546.sat
+
+    println()
+    println(s"name: $name")
+
+    val outputs = Transactions.makeCommitTxOutputs(
+      Local.funding_pubkey,
+      Remote.funding_pubkey,
+      localCommitmentKeys.publicKeys,
+      payCommitTxFees = true,
+      dustLimit,
+      Local.toSelfDelay,
+      spec,
+      commitmentFormat
+    )
+    val unsignedCommitTx = Transactions.makeCommitTx(
+      commitTxInput = commitmentInput,
+      commitTxNumber = Local.commitTxNumber,
+      localPaymentBasePoint = Local.payment_basepoint,
+      remotePaymentBasePoint = Remote.payment_basepoint,
+      localIsChannelOpener = true,
+      outputs = outputs)
+    val local_sig = unsignedCommitTx.sign(Local.funding_privkey, Remote.funding_pubkey)
+    val remote_sig = unsignedCommitTx.sign(Remote.funding_privkey, Local.funding_pubkey)
+    println(s"local_signature: ${Scripts.der(local_sig.sig).dropRight(1).toHex}")
+    println(s"remote_signature: ${Scripts.der(remote_sig.sig).dropRight(1).toHex}")
+    val commitTx = unsignedCommitTx.aggregateSigs(Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig)
+    Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+
+    val htlcTxs = Transactions.makeHtlcTxs(commitTx, outputs, commitmentFormat).sortBy(_.input.outPoint.index)
+    println(s"num_htlcs: ${htlcTxs.length}")
+    htlcTxs.foreach {
+      case tx: UnsignedHtlcTimeoutTx =>
+        val remoteHtlcSig = tx.localSig(remoteCommitmentKeys)
+        val withRemoteSig = tx.addRemoteSig(localCommitmentKeys, remoteHtlcSig)
+        val localHtlcSig = withRemoteSig.localSig(WalletInputs(Nil, None))
+        println(s"# signature for output #${tx.input.outPoint.index} (htlc-timeout for htlc #${tx.htlcId})")
+        println(s"local_htlc_signature: ${Scripts.der(localHtlcSig).dropRight(1).toHex}")
+        println(s"remote_htlc_signature: ${Scripts.der(remoteHtlcSig).dropRight(1).toHex}")
+      case tx: UnsignedHtlcSuccessTx =>
+        fail(s"unexpected htlc-success tx for htlc #${tx.htlcId}")
+    }
+  }
+
+  test("commitment_tx_with_asymmetric_dust_limits_legacy") {
+    val name = "commitment_tx_with_asymmetric_dust_limits_legacy"
+    val htlc = IncomingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 20000000 msat, ByteVector32.Zeroes, CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val spec = CommitmentSpec(Set[DirectedHtlc](htlc), FeeratePerKw(15000 sat), toLocal = 7000000000L msat, toRemote = (3000000000L - 20000000L) msat)
+    val dustLimit = 546.sat
+
+    println()
+    println(s"name: $name")
+
+    val outputs = Transactions.makeCommitTxOutputs(
+      Local.funding_pubkey,
+      Remote.funding_pubkey,
+      localCommitmentKeys.publicKeys,
+      payCommitTxFees = true,
+      dustLimit,
+      Local.toSelfDelay,
+      spec,
+      commitmentFormat
+    )
+    val unsignedCommitTx = Transactions.makeCommitTx(
+      commitTxInput = commitmentInput,
+      commitTxNumber = Local.commitTxNumber,
+      localPaymentBasePoint = Local.payment_basepoint,
+      remotePaymentBasePoint = Remote.payment_basepoint,
+      localIsChannelOpener = true,
+      outputs = outputs)
+    val local_sig = unsignedCommitTx.sign(Local.funding_privkey, Remote.funding_pubkey)
+    val remote_sig = unsignedCommitTx.sign(Remote.funding_privkey, Local.funding_pubkey)
+    println(s"local_signature: ${Scripts.der(local_sig.sig).dropRight(1).toHex}")
+    println(s"remote_signature: ${Scripts.der(remote_sig.sig).dropRight(1).toHex}")
+    val commitTx = unsignedCommitTx.aggregateSigs(Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig)
+    Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+
+    val htlcTxs = Transactions.makeHtlcTxs(commitTx, outputs, commitmentFormat).sortBy(_.input.outPoint.index)
+    println(s"num_htlcs: ${htlcTxs.length}")
+    htlcTxs.foreach {
+      case tx: UnsignedHtlcSuccessTx =>
+        val remoteHtlcSig = tx.localSig(remoteCommitmentKeys)
+        val redeemScript = Scripts.htlcReceived(localCommitmentKeys.publicKeys, tx.paymentHash, tx.htlcExpiry, commitmentFormat)
+        val localHtlcSig = Transaction.signInput(tx.tx, 0, redeemScript, fr.acinq.bitcoin.SigHash.SIGHASH_ALL, tx.input.txOut.amount, fr.acinq.bitcoin.SigVersion.SIGVERSION_WITNESS_V0, Local.htlc_privkey)
+        println(s"# signature for output #${tx.input.outPoint.index} (htlc-success for htlc #${tx.htlcId})")
+        println(s"local_htlc_signature: ${localHtlcSig.dropRight(1).toHex}")
+        println(s"remote_htlc_signature: ${Scripts.der(remoteHtlcSig).dropRight(1).toHex}")
+      case tx: UnsignedHtlcTimeoutTx =>
+        fail(s"unexpected htlc-timeout tx for htlc #${tx.htlcId}")
+    }
+  }
 }
 
 class AnchorOutputsTestVectorSpec extends TestVectorsSpec {
@@ -470,4 +663,245 @@ class AnchorOutputsZeroFeeHtlcTxTestVectorSpec extends TestVectorsSpec {
   override def filename: String = "/bolt3-tx-test-vectors-anchor-outputs-zero-fee-htlc-tx-format.txt"
   override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)
   // @formatter:on
+
+  test("commitment_tx_with_htlc_amount_msat_not_multiple_of_1000_anchor") {
+    val name = "commitment_tx_with_htlc_amount_msat_not_multiple_of_1000_anchor"
+    val htlc = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 2000999 msat, ByteVector32.Zeroes, CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val spec = CommitmentSpec(Set(htlc), FeeratePerKw(647 sat), toLocal = (7000000000L - 2000999L) msat, toRemote = 3000000000L msat)
+    val dustLimit = 546.sat
+
+    println()
+    println(s"name: $name")
+
+    val outputs = Transactions.makeCommitTxOutputs(
+      Local.funding_pubkey,
+      Remote.funding_pubkey,
+      localCommitmentKeys.publicKeys,
+      payCommitTxFees = true,
+      dustLimit,
+      Local.toSelfDelay,
+      spec,
+      commitmentFormat
+    )
+    val unsignedCommitTx = Transactions.makeCommitTx(
+      commitTxInput = commitmentInput,
+      commitTxNumber = Local.commitTxNumber,
+      localPaymentBasePoint = Local.payment_basepoint,
+      remotePaymentBasePoint = Remote.payment_basepoint,
+      localIsChannelOpener = true,
+      outputs = outputs)
+    val local_sig = unsignedCommitTx.sign(Local.funding_privkey, Remote.funding_pubkey)
+    val remote_sig = unsignedCommitTx.sign(Remote.funding_privkey, Local.funding_pubkey)
+    println(s"local_signature: ${Scripts.der(local_sig.sig).dropRight(1).toHex}")
+    println(s"remote_signature: ${Scripts.der(remote_sig.sig).dropRight(1).toHex}")
+    val commitTx = unsignedCommitTx.aggregateSigs(Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig)
+    Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+
+    val htlcTxs = Transactions.makeHtlcTxs(commitTx, outputs, commitmentFormat).sortBy(_.input.outPoint.index)
+    println(s"num_htlcs: ${htlcTxs.length}")
+    htlcTxs.foreach {
+      case tx: UnsignedHtlcTimeoutTx =>
+        val remoteHtlcSig = tx.localSig(remoteCommitmentKeys)
+        val withRemoteSig = tx.addRemoteSig(localCommitmentKeys, remoteHtlcSig)
+        val localHtlcSig = withRemoteSig.localSig(WalletInputs(Nil, None))
+        println(s"# signature for output #${tx.input.outPoint.index} (htlc-timeout for htlc #${tx.htlcId})")
+        println(s"local_htlc_signature: ${Scripts.der(localHtlcSig).dropRight(1).toHex}")
+        println(s"remote_htlc_signature: ${Scripts.der(remoteHtlcSig).dropRight(1).toHex}")
+      case tx: UnsignedHtlcSuccessTx =>
+        fail(s"unexpected htlc-success tx for htlc #${tx.htlcId}")
+    }
+  }
+
+  test("commitment_tx_with_htlc_value_equals_non_htlc_value_anchor") {
+    val name = "commitment_tx_with_htlc_value_equals_non_htlc_value_anchor"
+    val htlc = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 2000000000L msat, ByteVector32.Zeroes, CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val spec = CommitmentSpec(Set(htlc), FeeratePerKw(15000 sat), toLocal = (8000000000L - 2000000000L) msat, toRemote = 2000000000L msat)
+    val dustLimit = 546.sat
+
+    println()
+    println(s"name: $name")
+
+    val outputs = Transactions.makeCommitTxOutputs(
+      Local.funding_pubkey,
+      Remote.funding_pubkey,
+      localCommitmentKeys.publicKeys,
+      payCommitTxFees = true,
+      dustLimit,
+      Local.toSelfDelay,
+      spec,
+      commitmentFormat
+    )
+    val unsignedCommitTx = Transactions.makeCommitTx(
+      commitTxInput = commitmentInput,
+      commitTxNumber = Local.commitTxNumber,
+      localPaymentBasePoint = Local.payment_basepoint,
+      remotePaymentBasePoint = Remote.payment_basepoint,
+      localIsChannelOpener = true,
+      outputs = outputs)
+    val local_sig = unsignedCommitTx.sign(Local.funding_privkey, Remote.funding_pubkey)
+    val remote_sig = unsignedCommitTx.sign(Remote.funding_privkey, Local.funding_pubkey)
+    println(s"local_signature: ${Scripts.der(local_sig.sig).dropRight(1).toHex}")
+    println(s"remote_signature: ${Scripts.der(remote_sig.sig).dropRight(1).toHex}")
+    val commitTx = unsignedCommitTx.aggregateSigs(Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig)
+    Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+
+    val htlcTxs = Transactions.makeHtlcTxs(commitTx, outputs, commitmentFormat).sortBy(_.input.outPoint.index)
+    println(s"num_htlcs: ${htlcTxs.length}")
+    htlcTxs.foreach {
+      case tx: UnsignedHtlcTimeoutTx =>
+        val remoteHtlcSig = tx.localSig(remoteCommitmentKeys)
+        val withRemoteSig = tx.addRemoteSig(localCommitmentKeys, remoteHtlcSig)
+        val localHtlcSig = withRemoteSig.localSig(WalletInputs(Nil, None))
+        println(s"# signature for output #${tx.input.outPoint.index} (htlc-timeout for htlc #${tx.htlcId})")
+        println(s"local_htlc_signature: ${Scripts.der(localHtlcSig).dropRight(1).toHex}")
+        println(s"remote_htlc_signature: ${Scripts.der(remoteHtlcSig).dropRight(1).toHex}")
+      case tx: UnsignedHtlcSuccessTx =>
+        fail(s"unexpected htlc-success tx for htlc #${tx.htlcId}")
+    }
+  }
+
+  test("commitment_tx_with_non_htlc_outputs_between_htlc_outputs_anchor") {
+    val name = "commitment_tx_with_non_htlc_outputs_between_htlc_outputs_anchor"
+    val htlc1 = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 2000000000L msat, ByteVector32.Zeroes, CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val htlc2 = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 1, 4000000000L msat, ByteVector32.Zeroes, CltvExpiry(501), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val spec = CommitmentSpec(Set[DirectedHtlc](htlc1, htlc2), FeeratePerKw(15000 sat), toLocal = (7000000000L - 2000000000L - 4000000000L) msat, toRemote = 3000000000L msat)
+    val dustLimit = 546.sat
+
+    println()
+    println(s"name: $name")
+
+    val outputs = Transactions.makeCommitTxOutputs(
+      Local.funding_pubkey,
+      Remote.funding_pubkey,
+      localCommitmentKeys.publicKeys,
+      payCommitTxFees = true,
+      dustLimit,
+      Local.toSelfDelay,
+      spec,
+      commitmentFormat
+    )
+    val unsignedCommitTx = Transactions.makeCommitTx(
+      commitTxInput = commitmentInput,
+      commitTxNumber = Local.commitTxNumber,
+      localPaymentBasePoint = Local.payment_basepoint,
+      remotePaymentBasePoint = Remote.payment_basepoint,
+      localIsChannelOpener = true,
+      outputs = outputs)
+    val local_sig = unsignedCommitTx.sign(Local.funding_privkey, Remote.funding_pubkey)
+    val remote_sig = unsignedCommitTx.sign(Remote.funding_privkey, Local.funding_pubkey)
+    println(s"local_signature: ${Scripts.der(local_sig.sig).dropRight(1).toHex}")
+    println(s"remote_signature: ${Scripts.der(remote_sig.sig).dropRight(1).toHex}")
+    val commitTx = unsignedCommitTx.aggregateSigs(Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig)
+    Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+
+    val htlcTxs = Transactions.makeHtlcTxs(commitTx, outputs, commitmentFormat).sortBy(_.input.outPoint.index)
+    println(s"num_htlcs: ${htlcTxs.length}")
+    htlcTxs.foreach {
+      case tx: UnsignedHtlcTimeoutTx =>
+        val remoteHtlcSig = tx.localSig(remoteCommitmentKeys)
+        val withRemoteSig = tx.addRemoteSig(localCommitmentKeys, remoteHtlcSig)
+        val localHtlcSig = withRemoteSig.localSig(WalletInputs(Nil, None))
+        println(s"# signature for output #${tx.input.outPoint.index} (htlc-timeout for htlc #${tx.htlcId})")
+        println(s"local_htlc_signature: ${Scripts.der(localHtlcSig).dropRight(1).toHex}")
+        println(s"remote_htlc_signature: ${Scripts.der(remoteHtlcSig).dropRight(1).toHex}")
+      case tx: UnsignedHtlcSuccessTx =>
+        fail(s"unexpected htlc-success tx for htlc #${tx.htlcId}")
+    }
+  }
+
+  test("commitment_tx_with_asymmetric_dust_limits_anchor") {
+    val name = "commitment_tx_with_asymmetric_dust_limits_anchor"
+    val htlc = IncomingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 15000000 msat, ByteVector32.Zeroes, CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val spec = CommitmentSpec(Set[DirectedHtlc](htlc), FeeratePerKw(15000 sat), toLocal = 7000000000L msat, toRemote = (3000000000L - 15000000L) msat)
+    val dustLimit = 546.sat
+
+    println()
+    println(s"name: $name")
+
+    val outputs = Transactions.makeCommitTxOutputs(
+      Local.funding_pubkey,
+      Remote.funding_pubkey,
+      localCommitmentKeys.publicKeys,
+      payCommitTxFees = true,
+      dustLimit,
+      Local.toSelfDelay,
+      spec,
+      commitmentFormat
+    )
+    val unsignedCommitTx = Transactions.makeCommitTx(
+      commitTxInput = commitmentInput,
+      commitTxNumber = Local.commitTxNumber,
+      localPaymentBasePoint = Local.payment_basepoint,
+      remotePaymentBasePoint = Remote.payment_basepoint,
+      localIsChannelOpener = true,
+      outputs = outputs)
+    val local_sig = unsignedCommitTx.sign(Local.funding_privkey, Remote.funding_pubkey)
+    val remote_sig = unsignedCommitTx.sign(Remote.funding_privkey, Local.funding_pubkey)
+    println(s"local_signature: ${Scripts.der(local_sig.sig).dropRight(1).toHex}")
+    println(s"remote_signature: ${Scripts.der(remote_sig.sig).dropRight(1).toHex}")
+    val commitTx = unsignedCommitTx.aggregateSigs(Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig)
+    Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+
+    val htlcTxs = Transactions.makeHtlcTxs(commitTx, outputs, commitmentFormat).sortBy(_.input.outPoint.index)
+    println(s"num_htlcs: ${htlcTxs.length}")
+    htlcTxs.foreach {
+      case tx: UnsignedHtlcSuccessTx =>
+        val remoteHtlcSig = tx.localSig(remoteCommitmentKeys)
+        val redeemScript = Scripts.htlcReceived(localCommitmentKeys.publicKeys, tx.paymentHash, tx.htlcExpiry, commitmentFormat)
+        val localHtlcSig = Transaction.signInput(tx.tx, 0, redeemScript, fr.acinq.bitcoin.SigHash.SIGHASH_ALL, tx.input.txOut.amount, fr.acinq.bitcoin.SigVersion.SIGVERSION_WITNESS_V0, Local.htlc_privkey)
+        println(s"# signature for output #${tx.input.outPoint.index} (htlc-success for htlc #${tx.htlcId})")
+        println(s"local_htlc_signature: ${localHtlcSig.dropRight(1).toHex}")
+        println(s"remote_htlc_signature: ${Scripts.der(remoteHtlcSig).dropRight(1).toHex}")
+      case tx: UnsignedHtlcTimeoutTx =>
+        fail(s"unexpected htlc-timeout tx for htlc #${tx.htlcId}")
+    }
+  }
+
+  test("commitment_tx_with_main_output_trimmed_but_htlcs_present_anchor") {
+    val name = "commitment_tx_with_main_output_trimmed_but_htlcs_present_anchor"
+    val htlc = OutgoingHtlc(UpdateAddHtlc(ByteVector32.Zeroes, 0, 5000000 msat, ByteVector32.Zeroes, CltvExpiry(500), TestConstants.emptyOnionPacket, None, Reputation.maxEndorsement, None))
+    val spec = CommitmentSpec(Set(htlc), FeeratePerKw(15000 sat), toLocal = (10000000000L - 5000000L) msat, toRemote = 0 msat)
+    val dustLimit = 546.sat
+
+    println()
+    println(s"name: $name")
+
+    val outputs = Transactions.makeCommitTxOutputs(
+      Local.funding_pubkey,
+      Remote.funding_pubkey,
+      localCommitmentKeys.publicKeys,
+      payCommitTxFees = true,
+      dustLimit,
+      Local.toSelfDelay,
+      spec,
+      commitmentFormat
+    )
+    val unsignedCommitTx = Transactions.makeCommitTx(
+      commitTxInput = commitmentInput,
+      commitTxNumber = Local.commitTxNumber,
+      localPaymentBasePoint = Local.payment_basepoint,
+      remotePaymentBasePoint = Remote.payment_basepoint,
+      localIsChannelOpener = true,
+      outputs = outputs)
+    val local_sig = unsignedCommitTx.sign(Local.funding_privkey, Remote.funding_pubkey)
+    val remote_sig = unsignedCommitTx.sign(Remote.funding_privkey, Local.funding_pubkey)
+    println(s"local_signature: ${Scripts.der(local_sig.sig).dropRight(1).toHex}")
+    println(s"remote_signature: ${Scripts.der(remote_sig.sig).dropRight(1).toHex}")
+    val commitTx = unsignedCommitTx.aggregateSigs(Local.funding_pubkey, Remote.funding_pubkey, local_sig, remote_sig)
+    Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+
+    val htlcTxs = Transactions.makeHtlcTxs(commitTx, outputs, commitmentFormat).sortBy(_.input.outPoint.index)
+    println(s"num_htlcs: ${htlcTxs.length}")
+    htlcTxs.foreach {
+      case tx: UnsignedHtlcTimeoutTx =>
+        val remoteHtlcSig = tx.localSig(remoteCommitmentKeys)
+        val withRemoteSig = tx.addRemoteSig(localCommitmentKeys, remoteHtlcSig)
+        val localHtlcSig = withRemoteSig.localSig(WalletInputs(Nil, None))
+        println(s"# signature for output #${tx.input.outPoint.index} (htlc-timeout for htlc #${tx.htlcId})")
+        println(s"local_htlc_signature: ${Scripts.der(localHtlcSig).dropRight(1).toHex}")
+        println(s"remote_htlc_signature: ${Scripts.der(remoteHtlcSig).dropRight(1).toHex}")
+      case tx: UnsignedHtlcSuccessTx =>
+        fail(s"unexpected htlc-success tx for htlc #${tx.htlcId}")
+    }
+  }
 }

Note: I split this into smaller commits, so some functions and fields temporarily use #[allow(dead_code)] so that each commit is clippy clean. These annotations are removed in later commits

Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant