Skip to content

Commit 2873839

Browse files
committed
Better abstraction of sighash flags
We add the concept of a TxOwner and encapsulate the choice of sighash flags to use when signing thanks for that abstraction.
1 parent 7660ad4 commit 2873839

File tree

10 files changed

+147
-115
lines changed

10 files changed

+147
-115
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/Channel.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import fr.acinq.eclair.io.Peer
3232
import fr.acinq.eclair.payment._
3333
import fr.acinq.eclair.payment.relay.{Origin, Relayer}
3434
import fr.acinq.eclair.router.Announcements
35+
import fr.acinq.eclair.transactions.Transactions.TxOwner
3536
import fr.acinq.eclair.transactions._
3637
import fr.acinq.eclair.wire._
3738
import scodec.bits.ByteVector
@@ -387,7 +388,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
387388
// let's create the first commitment tx that spends the yet uncommitted funding tx
388389
val (localSpec, localCommitTx, remoteSpec, remoteCommitTx) = Funding.makeFirstCommitTxs(keyManager, channelVersion, temporaryChannelId, localParams, remoteParams, fundingAmount, pushMsat, initialFeeratePerKw, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint)
389390
require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!")
390-
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.fundingKeyPath))
391+
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(localParams.fundingKeyPath), TxOwner.Remote, channelVersion.commitmentFormat)
391392
// signature of their initial commitment tx that pays remote pushMsat
392393
val fundingCreated = FundingCreated(
393394
temporaryChannelId = temporaryChannelId,
@@ -430,12 +431,12 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
430431

431432
// check remote signature validity
432433
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
433-
val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey)
434+
val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey, TxOwner.Local, channelVersion.commitmentFormat)
434435
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
435436
Transactions.checkSpendable(signedLocalCommitTx) match {
436-
case Failure(cause) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx), d, None)
437+
case Failure(_) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx), d, None)
437438
case Success(_) =>
438-
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, fundingPubKey)
439+
val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, fundingPubKey, TxOwner.Remote, channelVersion.commitmentFormat)
439440
val channelId = toLongId(fundingTxHash, fundingTxOutputIndex)
440441
// watch the funding tx transaction
441442
val commitInput = localCommitTx.input
@@ -473,7 +474,7 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId
473474
case Event(msg@FundingSigned(_, remoteSig), d@DATA_WAIT_FOR_FUNDING_SIGNED(channelId, localParams, remoteParams, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, channelFlags, channelVersion, fundingCreated)) =>
474475
// we make sure that their sig checks out and that our first commit tx is spendable
475476
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath)
476-
val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey)
477+
val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey, TxOwner.Local, channelVersion.commitmentFormat)
477478
val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig)
478479
Transactions.checkSpendable(signedLocalCommitTx) match {
479480
case Failure(cause) =>

eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ package fr.acinq.eclair.channel
1818

1919
import akka.event.LoggingAdapter
2020
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey, sha256}
21-
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, SIGHASH_ALL}
21+
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto}
2222
import fr.acinq.eclair.blockchain.fee.OnChainFeeConf
2323
import fr.acinq.eclair.channel.Monitoring.Metrics
2424
import fr.acinq.eclair.crypto.{Generators, KeyManager, ShaChain, Sphinx}
@@ -466,12 +466,11 @@ object Commitments {
466466
// remote commitment will includes all local changes + remote acked changes
467467
val spec = CommitmentSpec.reduce(remoteCommit.spec, remoteChanges.acked, localChanges.proposed)
468468
val (remoteCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeRemoteTxs(keyManager, channelVersion, remoteCommit.index + 1, localParams, remoteParams, commitInput, remoteNextPerCommitmentPoint, spec)
469-
val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))
469+
val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath), TxOwner.Remote, commitmentFormat)
470470

471471
val sortedHtlcTxs: Seq[TransactionWithInputInfo] = (htlcTimeoutTxs ++ htlcSuccessTxs).sortBy(_.input.outPoint.index)
472472
val channelKeyPath = keyManager.channelKeyPath(commitments.localParams, commitments.channelVersion)
473-
// we sign the *remote* txs with different sighash flags depending on the commitment format
474-
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(channelKeyPath), remoteNextPerCommitmentPoint, Scripts.htlcRemoteSighash(commitmentFormat)))
473+
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(channelKeyPath), remoteNextPerCommitmentPoint, TxOwner.Remote, commitmentFormat))
475474

476475
// NB: IN/OUT htlcs are inverted because this is the remote commit
477476
log.info(s"built remote commit number=${remoteCommit.index + 1} toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${remoteCommitTx.tx.txid} tx={}", spec.htlcs.collect(outgoing).map(_.id).mkString(","), spec.htlcs.collect(incoming).map(_.id).mkString(","), remoteCommitTx.tx)
@@ -513,7 +512,7 @@ object Commitments {
513512
val channelKeyPath = keyManager.channelKeyPath(commitments.localParams, commitments.channelVersion)
514513
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitments.localCommit.index + 1)
515514
val (localCommitTx, htlcTimeoutTxs, htlcSuccessTxs) = makeLocalTxs(keyManager, channelVersion, localCommit.index + 1, localParams, remoteParams, commitInput, localPerCommitmentPoint, spec)
516-
val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))
515+
val sig = keyManager.sign(localCommitTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath), TxOwner.Local, commitmentFormat)
517516

518517
log.info(s"built local commit number=${localCommit.index + 1} toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.feeratePerKw} txid=${localCommitTx.tx.txid} tx={}", spec.htlcs.collect(incoming).map(_.id).mkString(","), spec.htlcs.collect(outgoing).map(_.id).mkString(","), localCommitTx.tx)
519518

@@ -527,8 +526,7 @@ object Commitments {
527526
if (commit.htlcSignatures.size != sortedHtlcTxs.size) {
528527
throw HtlcSigCountMismatch(commitments.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size)
529528
}
530-
// we always sign our *local* txs with SIGHASH_ALL (otherwise relaying nodes could malleate the tx)
531-
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL))
529+
val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(channelKeyPath), localPerCommitmentPoint, TxOwner.Local, commitmentFormat))
532530
val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint)
533531
// combine the sigs to make signed txes
534532
val htlcTxsAndSigs = (sortedHtlcTxs, htlcSigs, commit.htlcSignatures).zipped.toList.collect {
@@ -539,7 +537,8 @@ object Commitments {
539537
HtlcTxAndSigs(htlcTx, localSig, remoteSig)
540538
case (htlcTx: HtlcSuccessTx, localSig, remoteSig) =>
541539
// we can't check that htlc-success tx are spendable because we need the payment preimage; thus we only check the remote sig
542-
if (!Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, Scripts.htlcRemoteSighash(commitmentFormat))) {
540+
// we verify the signature from their point of view, where it is a remote tx
541+
if (!Transactions.checkSig(htlcTx, remoteSig, remoteHtlcPubkey, TxOwner.Remote, commitmentFormat)) {
543542
throw InvalidHtlcSignature(commitments.channelId, htlcTx.tx)
544543
}
545544
HtlcTxAndSigs(htlcTx, localSig, remoteSig)

eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ object Helpers {
475475
log.debug("making closing tx with closingFee={} and commitments:\n{}", closingFee, Commitments.specs2String(commitments))
476476
val dustLimitSatoshis = localParams.dustLimit.max(remoteParams.dustLimit)
477477
val closingTx = Transactions.makeClosingTx(commitInput, localScriptPubkey, remoteScriptPubkey, localParams.isFunder, dustLimitSatoshis, closingFee, localCommit.spec)
478-
val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath))
478+
val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitments.localParams.fundingKeyPath), TxOwner.Local, commitmentFormat)
479479
val closingSigned = ClosingSigned(channelId, closingFee, localClosingSig)
480480
log.info(s"signed closing txid=${closingTx.tx.txid} with closingFeeSatoshis=${closingSigned.feeSatoshis}")
481481
log.debug(s"closingTxid=${closingTx.tx.txid} closingTx=${closingTx.tx}}")
@@ -534,7 +534,7 @@ object Helpers {
534534
// first we will claim our main output as soon as the delay is over
535535
val mainDelayedTx = generateTx("main-delayed-output") {
536536
Transactions.makeClaimDelayedOutputTx(tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed).right.map(claimDelayed => {
537-
val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL)
537+
val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint, TxOwner.Local, commitmentFormat)
538538
Transactions.addSigs(claimDelayed, sig)
539539
})
540540
}
@@ -564,7 +564,7 @@ object Helpers {
564564
txinfo: TransactionWithInputInfo =>
565565
generateTx("claim-htlc-delayed") {
566566
Transactions.makeClaimDelayedOutputTx(txinfo.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwDelayed).right.map(claimDelayed => {
567-
val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint, SIGHASH_ALL)
567+
val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint, TxOwner.Local, commitmentFormat)
568568
Transactions.addSigs(claimDelayed, sig)
569569
})
570570
}
@@ -615,7 +615,7 @@ object Helpers {
615615
case OutgoingHtlc(add: UpdateAddHtlc) if preimages.exists(r => sha256(r) == add.paymentHash) => generateTx("claim-htlc-success") {
616616
val preimage = preimages.find(r => sha256(r) == add.paymentHash).get
617617
Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, outputs, localParams.dustLimit, localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc, commitments.commitmentFormat).right.map(txinfo => {
618-
val sig = keyManager.sign(txinfo, keyManager.htlcPoint(channelKeyPath), remoteCommit.remotePerCommitmentPoint, SIGHASH_ALL)
618+
val sig = keyManager.sign(txinfo, keyManager.htlcPoint(channelKeyPath), remoteCommit.remotePerCommitmentPoint, TxOwner.Local, commitments.commitmentFormat)
619619
Transactions.addSigs(txinfo, sig, preimage)
620620
})
621621
}
@@ -625,7 +625,7 @@ object Helpers {
625625
// outgoing htlc: they may or may not have the preimage, the only thing to do is try to get back our funds after timeout
626626
case IncomingHtlc(add: UpdateAddHtlc) => generateTx("claim-htlc-timeout") {
627627
Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, outputs, localParams.dustLimit, localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, add, feeratePerKwHtlc, commitments.commitmentFormat).right.map(txinfo => {
628-
val sig = keyManager.sign(txinfo, keyManager.htlcPoint(channelKeyPath), remoteCommit.remotePerCommitmentPoint, SIGHASH_ALL)
628+
val sig = keyManager.sign(txinfo, keyManager.htlcPoint(channelKeyPath), remoteCommit.remotePerCommitmentPoint, TxOwner.Local, commitments.commitmentFormat)
629629
Transactions.addSigs(txinfo, sig)
630630
})
631631
}
@@ -665,7 +665,7 @@ object Helpers {
665665

666666
val mainTx = generateTx("claim-p2wpkh-output") {
667667
Transactions.makeClaimP2WPKHOutputTx(tx, commitments.localParams.dustLimit, localPubkey, commitments.localParams.defaultFinalScriptPubKey, feeratePerKwMain).right.map(claimMain => {
668-
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL)
668+
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), remotePerCommitmentPoint, TxOwner.Local, commitments.commitmentFormat)
669669
Transactions.addSigs(claimMain, localPubkey, sig)
670670
})
671671
}
@@ -720,7 +720,7 @@ object Helpers {
720720
None
721721
case _ => generateTx("claim-p2wpkh-output") {
722722
Transactions.makeClaimP2WPKHOutputTx(tx, localParams.dustLimit, localPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwMain).right.map(claimMain => {
723-
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), remotePerCommitmentPoint, SIGHASH_ALL)
723+
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), remotePerCommitmentPoint, TxOwner.Local, commitmentFormat)
724724
Transactions.addSigs(claimMain, localPaymentPubkey, sig)
725725
})
726726
}
@@ -729,7 +729,7 @@ object Helpers {
729729
// then we punish them by stealing their main output
730730
val mainPenaltyTx = generateTx("main-penalty") {
731731
Transactions.makeMainPenaltyTx(tx, localParams.dustLimit, remoteRevocationPubkey, localParams.defaultFinalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePerKwPenalty).right.map(txinfo => {
732-
val sig = keyManager.sign(txinfo, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret)
732+
val sig = keyManager.sign(txinfo, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat)
733733
Transactions.addSigs(txinfo, sig)
734734
})
735735
}
@@ -749,7 +749,7 @@ object Helpers {
749749
val htlcRedeemScript = htlcsRedeemScripts(txOut.publicKeyScript)
750750
generateTx("htlc-penalty") {
751751
Transactions.makeHtlcPenaltyTx(tx, outputIndex, htlcRedeemScript, localParams.dustLimit, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty).right.map(htlcPenalty => {
752-
val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret)
752+
val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat)
753753
Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey)
754754
})
755755
}
@@ -800,7 +800,7 @@ object Helpers {
800800

801801
generateTx("claim-htlc-delayed-penalty") {
802802
Transactions.makeClaimDelayedOutputPenaltyTx(htlcTx, localParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localParams.defaultFinalScriptPubKey, feeratePerKwPenalty).right.map(htlcDelayedPenalty => {
803-
val sig = keyManager.sign(htlcDelayedPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret)
803+
val sig = keyManager.sign(htlcDelayedPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat)
804804
val signedTx = Transactions.addSigs(htlcDelayedPenalty, sig)
805805
// we need to make sure that the tx is indeed valid
806806
Transaction.correctlySpends(signedTx.tx, Seq(htlcTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)

eclair-core/src/main/scala/fr/acinq/eclair/crypto/KeyManager.scala

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
2323
import fr.acinq.bitcoin.DeterministicWallet.ExtendedPublicKey
2424
import fr.acinq.bitcoin.{ByteVector32, ByteVector64, Crypto, DeterministicWallet, Protocol}
2525
import fr.acinq.eclair.channel.{ChannelVersion, LocalParams}
26-
import fr.acinq.eclair.transactions.Transactions.TransactionWithInputInfo
26+
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, TransactionWithInputInfo, TxOwner}
2727
import fr.acinq.eclair.{Features, ShortChannelId}
2828

2929
trait KeyManager {
@@ -62,35 +62,40 @@ trait KeyManager {
6262
def newFundingKeyPath(isFunder: Boolean): DeterministicWallet.KeyPath
6363

6464
/**
65-
* @param tx input transaction
66-
* @param publicKey extended public key
65+
* @param tx input transaction
66+
* @param publicKey extended public key
67+
* @param txOwner owner of the transaction (local/remote)
68+
* @param commitmentFormat format of the commitment tx
6769
* @return a signature generated with the private key that matches the input
6870
* extended public key
6971
*/
70-
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey): ByteVector64
72+
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64
7173

7274
/**
7375
* This method is used to spend funds sent to htlc keys/delayed keys
7476
*
75-
* @param tx input transaction
76-
* @param publicKey extended public key
77-
* @param remotePoint remote point
78-
* @param sighashType sighash flags
77+
* @param tx input transaction
78+
* @param publicKey extended public key
79+
* @param remotePoint remote point
80+
* @param txOwner owner of the transaction (local/remote)
81+
* @param commitmentFormat format of the commitment tx
7982
* @return a signature generated with a private key generated from the input keys's matching
8083
* private key and the remote point.
8184
*/
82-
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: PublicKey, sighashType: Int): ByteVector64
85+
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64
8386

8487
/**
8588
* Ths method is used to spend revoked transactions, with the corresponding revocation key
8689
*
87-
* @param tx input transaction
88-
* @param publicKey extended public key
89-
* @param remoteSecret remote secret
90+
* @param tx input transaction
91+
* @param publicKey extended public key
92+
* @param remoteSecret remote secret
93+
* @param txOwner owner of the transaction (local/remote)
94+
* @param commitmentFormat format of the commitment tx
9095
* @return a signature generated with a private key generated from the input keys's matching
9196
* private key and the remote secret.
9297
*/
93-
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: PrivateKey): ByteVector64
98+
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remoteSecret: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64
9499

95100
/**
96101
* Sign a channel announcement message

0 commit comments

Comments
 (0)