Skip to content

Commit 6e40c49

Browse files
committed
Modify codecs to handle new fields for HtlcSuccessTx, HtlcTimeoutTx and ClaimHtlcTimeoutTx
We previously saved the redeem script in the InputInfo class: we'll extract these fields from the redeem script (which would be an HTLC offered or received script) or witness of data persisted with older codecs.
1 parent c75326f commit 6e40c49

File tree

13 files changed

+339
-83
lines changed

13 files changed

+339
-83
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/transactions/Scripts.scala

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,18 @@ object Scripts {
204204
*/
205205
def witnessAnchor(localSig: ByteVector64, anchorScript: ByteVector): ScriptWitness = ScriptWitness(der(localSig) :: anchorScript :: Nil)
206206

207-
def htlcOffered(keys: CommitmentPublicKeys, paymentHash: ByteVector32, commitmentFormat: CommitmentFormat): Seq[ScriptElt] = {
207+
case class RipemdOfPaymentHash(ripemdHash: ByteVector) {
208+
require(ripemdHash.size == 20)
209+
}
210+
211+
object RipemdOfPaymentHash {
212+
def apply(paymentHash: ByteVector32): RipemdOfPaymentHash = RipemdOfPaymentHash(Crypto.ripemd160(paymentHash))
213+
214+
// this value cannot be generated with a real payment hash
215+
val empty: RipemdOfPaymentHash = RipemdOfPaymentHash(ByteVector.fill(20)(0))
216+
}
217+
218+
def htlcOffered(keys: CommitmentPublicKeys, paymentHash: RipemdOfPaymentHash, commitmentFormat: CommitmentFormat): Seq[ScriptElt] = {
208219
val addCsvDelay = commitmentFormat match {
209220
case DefaultCommitmentFormat => false
210221
case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => true
@@ -220,7 +231,7 @@ object Scripts {
220231
// To me via HTLC-timeout transaction (timelocked).
221232
OP_DROP :: OP_2 :: OP_SWAP :: OP_PUSHDATA(keys.localHtlcPublicKey) :: OP_2 :: OP_CHECKMULTISIG ::
222233
OP_ELSE ::
223-
OP_HASH160 :: OP_PUSHDATA(Crypto.ripemd160(paymentHash)) :: OP_EQUALVERIFY ::
234+
OP_HASH160 :: OP_PUSHDATA(paymentHash.ripemdHash) :: OP_EQUALVERIFY ::
224235
OP_CHECKSIG ::
225236
OP_ENDIF ::
226237
(if (addCsvDelay) {
@@ -232,6 +243,46 @@ object Scripts {
232243
// @formatter:on
233244
}
234245

246+
def htlcOffered(keys: CommitmentPublicKeys, paymentHash: ByteVector32, commitmentFormat: CommitmentFormat): Seq[ScriptElt] =
247+
htlcOffered(keys, RipemdOfPaymentHash(paymentHash), commitmentFormat)
248+
249+
// @formatter::off
250+
def extractHtlcInfoFromHtlcOfferedScript(script: Seq[ScriptElt]): Option[RipemdOfPaymentHash] = script match {
251+
case OP_DUP :: OP_HASH160 :: OP_PUSHDATA(_, _) :: OP_EQUAL ::
252+
OP_IF ::
253+
OP_CHECKSIG ::
254+
OP_ELSE ::
255+
OP_PUSHDATA(_, _) :: OP_SWAP :: OP_SIZE :: OP_PUSHDATA(_, _) :: OP_EQUAL ::
256+
OP_NOTIF ::
257+
// To me via HTLC-timeout transaction (timelocked).
258+
OP_DROP :: OP_2 :: OP_SWAP :: OP_PUSHDATA(_, _) :: OP_2 :: OP_CHECKMULTISIG ::
259+
OP_ELSE ::
260+
OP_HASH160 :: OP_PUSHDATA(ripemdOfPaymentHash, _) :: OP_EQUALVERIFY :: OP_CHECKSIG ::
261+
OP_ENDIF ::
262+
OP_ENDIF :: Nil if ripemdOfPaymentHash.size == 20 => {
263+
Some(RipemdOfPaymentHash(ripemdOfPaymentHash))
264+
}
265+
case OP_DUP :: OP_HASH160 :: OP_PUSHDATA(_, _) :: OP_EQUAL ::
266+
OP_IF ::
267+
OP_CHECKSIG ::
268+
OP_ELSE ::
269+
OP_PUSHDATA(_, _) :: OP_SWAP :: OP_SIZE :: OP_PUSHDATA(_, _) :: OP_EQUAL ::
270+
OP_NOTIF ::
271+
// To me via HTLC-timeout transaction (timelocked).
272+
OP_DROP :: OP_2 :: OP_SWAP :: OP_PUSHDATA(_, _) :: OP_2 :: OP_CHECKMULTISIG ::
273+
OP_ELSE ::
274+
OP_HASH160 :: OP_PUSHDATA(ripemdOfPaymentHash, _) :: OP_EQUALVERIFY :: OP_CHECKSIG ::
275+
OP_ENDIF ::
276+
OP_1 :: OP_CHECKSEQUENCEVERIFY :: OP_DROP ::
277+
OP_ENDIF :: Nil if ripemdOfPaymentHash.size == 20 => {
278+
Some(RipemdOfPaymentHash(ripemdOfPaymentHash))
279+
}
280+
case _ => None
281+
}
282+
// @formatter::on
283+
284+
def extractHtlcInfoFromHtlcOfferedScript(script: ByteVector): Option[RipemdOfPaymentHash] = extractHtlcInfoFromHtlcOfferedScript(Script.parse(script))
285+
235286
/**
236287
* This is the witness script of the 2nd-stage HTLC Success transaction (consumes htlcOffered script from commit tx)
237288
*/
@@ -261,7 +312,7 @@ object Scripts {
261312
/** Extract payment preimages from a (potentially batched) claim HTLC transaction's witnesses. */
262313
def extractPreimagesFromClaimHtlcSuccess(tx: Transaction): Set[ByteVector32] = tx.txIn.map(_.witness).collect(extractPreimageFromClaimHtlcSuccess).toSet
263314

264-
def htlcReceived(keys: CommitmentPublicKeys, paymentHash: ByteVector32, lockTime: CltvExpiry, commitmentFormat: CommitmentFormat): Seq[ScriptElt] = {
315+
def htlcReceived(keys: CommitmentPublicKeys, paymentHash: RipemdOfPaymentHash, lockTime: CltvExpiry, commitmentFormat: CommitmentFormat): Seq[ScriptElt] = {
265316
val addCsvDelay = commitmentFormat match {
266317
case DefaultCommitmentFormat => false
267318
case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => true
@@ -275,7 +326,7 @@ object Scripts {
275326
OP_PUSHDATA(keys.remoteHtlcPublicKey) :: OP_SWAP :: OP_SIZE :: encodeNumber(32) :: OP_EQUAL ::
276327
OP_IF ::
277328
// To me via HTLC-success transaction.
278-
OP_HASH160 :: OP_PUSHDATA(Crypto.ripemd160(paymentHash)) :: OP_EQUALVERIFY ::
329+
OP_HASH160 :: OP_PUSHDATA(paymentHash.ripemdHash) :: OP_EQUALVERIFY ::
279330
OP_2 :: OP_SWAP :: OP_PUSHDATA(keys.localHtlcPublicKey) :: OP_2 :: OP_CHECKMULTISIG ::
280331
OP_ELSE ::
281332
// To you after timeout.
@@ -291,6 +342,48 @@ object Scripts {
291342
// @formatter:on
292343
}
293344

345+
// @formatter:off
346+
def extractHtlcInfoFromHtlcReceived(script: Seq[ScriptElt]): Option[(RipemdOfPaymentHash, CltvExpiry)] = script match {
347+
case OP_DUP :: OP_HASH160 :: OP_PUSHDATA(_, _) :: OP_EQUAL ::
348+
OP_IF ::
349+
OP_CHECKSIG ::
350+
OP_ELSE ::
351+
OP_PUSHDATA(_, _) :: OP_SWAP :: OP_SIZE :: OP_PUSHDATA(_, _) :: OP_EQUAL ::
352+
OP_IF ::
353+
// To me via HTLC-success transaction.
354+
OP_HASH160 :: OP_PUSHDATA(ripemdHash, _) :: OP_EQUALVERIFY ::
355+
OP_2 :: OP_SWAP :: OP_PUSHDATA(_, _) :: OP_2 :: OP_CHECKMULTISIG ::
356+
OP_ELSE ::
357+
// To you after timeout.
358+
OP_DROP :: OP_PUSHDATA(rawExpiry, _) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP ::
359+
OP_CHECKSIG ::
360+
OP_ENDIF ::
361+
OP_1 :: OP_CHECKSEQUENCEVERIFY :: OP_DROP ::
362+
OP_ENDIF :: Nil if ripemdHash.size == 20 => Some(RipemdOfPaymentHash(ripemdHash), CltvExpiry(Script.decodeNumber(rawExpiry, false)))
363+
case OP_DUP :: OP_HASH160 :: OP_PUSHDATA(_, _) :: OP_EQUAL ::
364+
OP_IF ::
365+
OP_CHECKSIG ::
366+
OP_ELSE ::
367+
OP_PUSHDATA(_, _) :: OP_SWAP :: OP_SIZE :: OP_PUSHDATA(_, _) :: OP_EQUAL ::
368+
OP_IF ::
369+
// To me via HTLC-success transaction.
370+
OP_HASH160 :: OP_PUSHDATA(ripemdHash, _) :: OP_EQUALVERIFY ::
371+
OP_2 :: OP_SWAP :: OP_PUSHDATA(_, _) :: OP_2 :: OP_CHECKMULTISIG ::
372+
OP_ELSE ::
373+
// To you after timeout.
374+
OP_DROP :: OP_PUSHDATA(rawExpiry, _) :: OP_CHECKLOCKTIMEVERIFY :: OP_DROP ::
375+
OP_CHECKSIG ::
376+
OP_ENDIF ::
377+
OP_ENDIF :: Nil if ripemdHash.size == 20 => Some(RipemdOfPaymentHash(ripemdHash), CltvExpiry(Script.decodeNumber(rawExpiry, false)))
378+
case _ => None
379+
}
380+
// @formatter:on
381+
382+
def extractHtlcInfoFromHtlcReceived(script: ByteVector): Option[(RipemdOfPaymentHash, CltvExpiry)] = extractHtlcInfoFromHtlcReceived(Script.parse(script))
383+
384+
def htlcReceived(keys: CommitmentPublicKeys, paymentHash: ByteVector32, lockTime: CltvExpiry, commitmentFormat: CommitmentFormat): Seq[ScriptElt] =
385+
htlcReceived(keys, RipemdOfPaymentHash(paymentHash), lockTime, commitmentFormat)
386+
294387
/**
295388
* This is the witness script of the 2nd-stage HTLC Timeout transaction (consumes htlcOffered script from commit tx)
296389
*/
@@ -445,10 +538,10 @@ object Scripts {
445538
*
446539
* @return a script used to create a "spend offered HTLC" leaf in a script tree
447540
*/
448-
private def offeredHtlcSuccess(keys: CommitmentPublicKeys, paymentHash: ByteVector32): Seq[ScriptElt] = {
541+
private def offeredHtlcSuccess(keys: CommitmentPublicKeys, paymentHash: RipemdOfPaymentHash): Seq[ScriptElt] = {
449542
// @formatter:off
450543
OP_SIZE :: encodeNumber(32) :: OP_EQUALVERIFY ::
451-
OP_HASH160 :: OP_PUSHDATA(Crypto.ripemd160(paymentHash)) :: OP_EQUALVERIFY ::
544+
OP_HASH160 :: OP_PUSHDATA(paymentHash.ripemdHash) :: OP_EQUALVERIFY ::
452545
OP_PUSHDATA(keys.remoteHtlcPublicKey.xOnly) :: OP_CHECKSIGVERIFY ::
453546
OP_1 :: OP_CHECKSEQUENCEVERIFY :: Nil
454547
// @formatter:on
@@ -457,13 +550,15 @@ object Scripts {
457550
/**
458551
* Script tree used for offered HTLCs.
459552
*/
460-
def offeredHtlcScriptTree(keys: CommitmentPublicKeys, paymentHash: ByteVector32): ScriptTree.Branch = {
553+
def offeredHtlcScriptTree(keys: CommitmentPublicKeys, paymentHash: RipemdOfPaymentHash): ScriptTree.Branch = {
461554
new ScriptTree.Branch(
462555
new ScriptTree.Leaf(offeredHtlcTimeout(keys)),
463556
new ScriptTree.Leaf(offeredHtlcSuccess(keys, paymentHash)),
464557
)
465558
}
466559

560+
def offeredHtlcScriptTree(keys: CommitmentPublicKeys, paymentHash: ByteVector32): ScriptTree.Branch = offeredHtlcScriptTree(keys, RipemdOfPaymentHash(paymentHash))
561+
467562
/**
468563
* Script used for offered HTLCs.
469564
*/
@@ -491,10 +586,10 @@ object Scripts {
491586
*
492587
* miniscript: and_v(v:hash160(H),and_v(v:pk(local_key),pk(remote_key)))
493588
*/
494-
private def receivedHtlcSuccess(keys: CommitmentPublicKeys, paymentHash: ByteVector32): Seq[ScriptElt] = {
589+
private def receivedHtlcSuccess(keys: CommitmentPublicKeys, paymentHash: RipemdOfPaymentHash): Seq[ScriptElt] = {
495590
// @formatter:off
496591
OP_SIZE :: encodeNumber(32) :: OP_EQUALVERIFY ::
497-
OP_HASH160 :: OP_PUSHDATA(Crypto.ripemd160(paymentHash)) :: OP_EQUALVERIFY ::
592+
OP_HASH160 :: OP_PUSHDATA(paymentHash.ripemdHash) :: OP_EQUALVERIFY ::
498593
OP_PUSHDATA(keys.localHtlcPublicKey.xOnly) :: OP_CHECKSIGVERIFY ::
499594
OP_PUSHDATA(keys.remoteHtlcPublicKey.xOnly) :: OP_CHECKSIG :: Nil
500595
// @formatter:on
@@ -503,13 +598,16 @@ object Scripts {
503598
/**
504599
* Script tree used for received HTLCs.
505600
*/
506-
def receivedHtlcScriptTree(keys: CommitmentPublicKeys, paymentHash: ByteVector32, expiry: CltvExpiry): ScriptTree.Branch = {
601+
def receivedHtlcScriptTree(keys: CommitmentPublicKeys, paymentHash: RipemdOfPaymentHash, expiry: CltvExpiry): ScriptTree.Branch = {
507602
new ScriptTree.Branch(
508603
new ScriptTree.Leaf(receivedHtlcTimeout(keys, expiry)),
509604
new ScriptTree.Leaf(receivedHtlcSuccess(keys, paymentHash)),
510605
)
511606
}
512607

608+
def receivedHtlcScriptTree(keys: CommitmentPublicKeys, paymentHash: ByteVector32, expiry: CltvExpiry): ScriptTree.Branch =
609+
receivedHtlcScriptTree(keys, RipemdOfPaymentHash(paymentHash), expiry)
610+
513611
/**
514612
* Script used for received HTLCs.
515613
*/

eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,14 @@ object Transactions {
131131

132132
case class InputInfo(outPoint: OutPoint, txOut: TxOut)
133133

134-
case class InputInfoWithRedeemInfo(outPoint: OutPoint, txOut: TxOut, redeemInfo: RedeemInfo) {
134+
case class InputInfoWithRedeemScript(outPoint: OutPoint, txOut: TxOut, redeemScript: Seq[ScriptElt]) {
135+
def this(inputInfo: InputInfo) = this(inputInfo.outPoint, inputInfo.txOut, Nil)
135136
val inputInfo: InputInfo = InputInfo(outPoint, txOut)
136137
}
137138

138-
object InputInfoWithRedeemInfo {
139-
def apply(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector): InputInfoWithRedeemInfo = InputInfoWithRedeemInfo(outPoint, txOut, SegwitV0(Script.parse(redeemScript)))
140-
def apply(outPoint: OutPoint, txOut: TxOut, redeemScript: Seq[ScriptElt]): InputInfoWithRedeemInfo = InputInfoWithRedeemInfo(outPoint, txOut, SegwitV0(redeemScript))
141-
def apply(outPoint: OutPoint, txOut: TxOut, internalKey: XonlyPublicKey, scriptTree: ScriptTree, leafHash: ByteVector32): InputInfoWithRedeemInfo = InputInfoWithRedeemInfo(outPoint, txOut, RedeemInfo.TaprootScriptPath(internalKey, scriptTree, leafHash))
139+
object InputInfoWithRedeemScript {
140+
def apply(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector): InputInfoWithRedeemScript = new InputInfoWithRedeemScript(outPoint, txOut, Script.parse(redeemScript))
141+
def apply(outPoint: OutPoint, txOut: TxOut): InputInfoWithRedeemScript = new InputInfoWithRedeemScript(outPoint, txOut, Nil)
142142
}
143143

144144
/** Owner of a given transaction (local/remote). */
@@ -292,7 +292,14 @@ object Transactions {
292292
override val desc: String = "htlc-success"
293293
}
294294

295-
case class HtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute, paymentHash: ByteVector32) extends HtlcTx {
295+
case class LegacyHtlcSuccessTx(input: InputInfoWithRedeemScript, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) {
296+
def this(htlcSuccessTx: HtlcSuccessTx) = this(new InputInfoWithRedeemScript(htlcSuccessTx.input), htlcSuccessTx.tx, htlcSuccessTx.paymentHash, htlcSuccessTx.htlcId, htlcSuccessTx.confirmationTarget)
297+
298+
val expiry: CltvExpiry = extractHtlcInfoFromHtlcReceived(input.redeemScript).map(_._2).getOrElse(CltvExpiry(0))
299+
val htlcSuccessTx: HtlcSuccessTx = HtlcSuccessTx(input.inputInfo, tx, paymentHash, expiry, htlcId, confirmationTarget)
300+
}
301+
302+
case class HtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute, paymentHash: RipemdOfPaymentHash) extends HtlcTx {
296303
override val desc: String = "htlc-timeout"
297304
}
298305

@@ -317,10 +324,17 @@ object Transactions {
317324
override val desc: String = "claim-htlc-success"
318325
}
319326

320-
case class ClaimHtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, paymentHash: ByteVector32, cltvExpiry: CltvExpiry, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx {
327+
case class ClaimHtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, paymentHash: RipemdOfPaymentHash, cltvExpiry: CltvExpiry, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx {
321328
override val desc: String = "claim-htlc-timeout"
322329
}
323330

331+
case class LegacyClaimHtlcTimeoutTx(input: InputInfoWithRedeemScript, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) {
332+
def this(claimHtlcTimeOutTx: ClaimHtlcTimeoutTx) = this(new InputInfoWithRedeemScript(claimHtlcTimeOutTx.input), claimHtlcTimeOutTx.tx, claimHtlcTimeOutTx.htlcId, claimHtlcTimeOutTx.confirmationTarget)
333+
334+
val (ripemdOfPaymentHash, expiry) = Scripts.extractHtlcInfoFromHtlcReceived(input.redeemScript).getOrElse(RipemdOfPaymentHash.empty -> CltvExpiry(0))
335+
val claimHtlcTimeOutTx: ClaimHtlcTimeoutTx = ClaimHtlcTimeoutTx(input.inputInfo, tx, htlcId, ripemdOfPaymentHash, expiry, confirmationTarget)
336+
}
337+
324338
case class ClaimAnchorOutputTx(input: InputInfo, tx: Transaction, confirmationTarget: ConfirmationTarget) extends ReplaceableTransactionWithInputInfo {
325339
override val desc: String = "local-anchor"
326340
}
@@ -641,7 +655,7 @@ object Transactions {
641655
txOut = output.htlcTimeoutOutput.txOut :: Nil,
642656
lockTime = htlc.cltvExpiry.toLong
643657
)
644-
Right(HtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)), htlc.paymentHash))
658+
Right(HtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)), RipemdOfPaymentHash(htlc.paymentHash)))
645659
}
646660

647661
private def makeHtlcSuccessTx(commitTx: Transaction,
@@ -716,12 +730,12 @@ object Transactions {
716730
txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil,
717731
lockTime = htlc.cltvExpiry.toLong
718732
)
719-
val unsignedClaimTx = ClaimHtlcTimeoutTx(input, unsignedTx, htlc.id, htlc.paymentHash, htlc.cltvExpiry, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))
733+
val unsignedClaimTx = ClaimHtlcTimeoutTx(input, unsignedTx, htlc.id, RipemdOfPaymentHash(htlc.paymentHash), htlc.cltvExpiry, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))
720734
val solver = Solver.ClaimHtlcTimeout(RemoteCommitmentKeys(Left(PlaceHolderPubKey), PlaceHolderPubKey, PlaceHolderPubKey, PrivateKey(ByteVector32.One), PlaceHolderPubKey, PlaceHolderPubKey), unsignedClaimTx, commitmentFormat)
721735
val dummySignedTx = solver.addSig(unsignedClaimTx, SolverData.SingleSig(PlaceHolderSig))
722736
skipTxIfBelowDust(dummySignedTx, feeratePerKw, dustLimit).map { amount =>
723737
val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil)
724-
ClaimHtlcTimeoutTx(input, tx, htlc.id, htlc.paymentHash, htlc.cltvExpiry, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))
738+
ClaimHtlcTimeoutTx(input, tx, htlc.id, RipemdOfPaymentHash(htlc.paymentHash), htlc.cltvExpiry, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))
725739
}
726740
}.getOrElse(Left(OutputNotFound))
727741
}

0 commit comments

Comments
 (0)