1
1
package fr .acinq .eclair .channel
2
2
3
- import akka .event .LoggingAdapter
4
- import fr .acinq .bitcoin .crypto .musig2 .IndividualNonce
3
+ import akka .event .{ DiagnosticLoggingAdapter , LoggingAdapter }
4
+ import fr .acinq .bitcoin .crypto .musig2 .{ IndividualNonce , SecretNonce }
5
5
import fr .acinq .bitcoin .scalacompat .Crypto .{PrivateKey , PublicKey }
6
6
import fr .acinq .bitcoin .scalacompat .{ByteVector32 , ByteVector64 , Crypto , Musig2 , Satoshi , SatoshiLong , Script , Transaction , TxId }
7
7
import fr .acinq .eclair .blockchain .fee .{FeeratePerByte , FeeratePerKw , FeeratesPerKw , OnChainFeeConf }
@@ -17,6 +17,7 @@ import fr.acinq.eclair.transactions.Transactions._
17
17
import fr .acinq .eclair .transactions ._
18
18
import fr .acinq .eclair .wire .protocol ._
19
19
import fr .acinq .eclair .{BlockHeight , CltvExpiry , CltvExpiryDelta , Features , MilliSatoshi , MilliSatoshiLong , payment }
20
+ import grizzled .slf4j .Logging
20
21
import scodec .bits .ByteVector
21
22
22
23
/** Static channel parameters shared by all commitments. */
@@ -229,11 +230,23 @@ case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig:
229
230
object LocalCommit {
230
231
def fromCommitSig (keyManager : ChannelKeyManager , params : ChannelParams , fundingTxId : TxId ,
231
232
fundingTxIndex : Long , remoteFundingPubKey : PublicKey , commitInput : InputInfo ,
232
- commit : CommitSig , localCommitIndex : Long , spec : CommitmentSpec , localPerCommitmentPoint : PublicKey ): Either [ChannelException , LocalCommit ] = {
233
+ commit : CommitSig , localCommitIndex : Long , spec : CommitmentSpec , localPerCommitmentPoint : PublicKey , localNonce_opt : Option [( SecretNonce , IndividualNonce )] ): Either [ChannelException , LocalCommit ] = {
233
234
val (localCommitTx, htlcTxs) = Commitment .makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommitIndex, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, localPerCommitmentPoint, spec)
234
235
if (! localCommitTx.checkSig(commit, remoteFundingPubKey, TxOwner .Remote , params.commitmentFormat)) {
235
236
return Left (InvalidCommitmentSignature (params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
236
237
}
238
+ commit.sigOrPartialSig match {
239
+ case Left (_) =>
240
+ if (! localCommitTx.checkSig(commit, remoteFundingPubKey, TxOwner .Remote , params.commitmentFormat)) {
241
+ return Left (InvalidCommitmentSignature (params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
242
+ }
243
+ case Right (psig) =>
244
+ val fundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex).publicKey
245
+ val Some (localNonce) = localNonce_opt
246
+ if (! localCommitTx.checkPartialSignature(psig, fundingPubkey, localNonce._2, remoteFundingPubKey)) {
247
+ return Left (InvalidCommitmentSignature (params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
248
+ }
249
+ }
237
250
val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index)
238
251
if (commit.htlcSignatures.size != sortedHtlcTxs.size) {
239
252
return Left (HtlcSigCountMismatch (params.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size))
@@ -252,7 +265,7 @@ object LocalCommit {
252
265
253
266
/** The remote commitment maps to a commitment transaction that only our peer can sign and broadcast. */
254
267
case class RemoteCommit (index : Long , spec : CommitmentSpec , txid : TxId , remotePerCommitmentPoint : PublicKey ) {
255
- def sign (keyManager : ChannelKeyManager , params : ChannelParams , fundingTxIndex : Long , remoteFundingPubKey : PublicKey , commitInput : InputInfo , remoteNonce_opt : Option [IndividualNonce ]): CommitSig = {
268
+ def sign (keyManager : ChannelKeyManager , params : ChannelParams , fundingTxIndex : Long , remoteFundingPubKey : PublicKey , commitInput : InputInfo , remoteNonce_opt : Option [IndividualNonce ])( implicit log : LoggingAdapter ) : CommitSig = {
256
269
val (remoteCommitTx, htlcTxs) = Commitment .makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, index, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, remotePerCommitmentPoint, spec)
257
270
val (sig, tlvStream) = params.commitmentFormat match {
258
271
case SimpleTaprootChannelsStagingCommitmentFormat =>
@@ -653,6 +666,7 @@ case class Commitment(fundingTxIndex: Long,
653
666
val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
654
667
val Some (remoteNonce) = nextRemoteNonce_opt
655
668
val Right (psig) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner .Remote , localNonce, remoteNonce)
669
+ log.debug(s " sendCommit: creating partial sig $psig for remote commit tx ${remoteCommitTx.tx.txid} with remote nonce $remoteNonce and remoteNextPerCommitmentPoint = $remoteNextPerCommitmentPoint" )
656
670
Set (CommitSigTlv .PartialSignatureWithNonceTlv (PartialSignatureWithNonce (psig, localNonce._2)))
657
671
case _ =>
658
672
Set .empty
@@ -668,11 +682,12 @@ case class Commitment(fundingTxIndex: Long,
668
682
val commitSig = CommitSig (params.channelId, sig, htlcSigs.toList, TlvStream (Set (
669
683
if (batchSize > 1 ) Some (CommitSigTlv .BatchTlv (batchSize)) else None
670
684
).flatten[CommitSigTlv ] ++ partialSig))
685
+ log.debug(s " sendCommit: setting remoteNextPerCommitmentPoint to $remoteNextPerCommitmentPoint" )
671
686
val nextRemoteCommit = NextRemoteCommit (commitSig, RemoteCommit (remoteCommit.index + 1 , spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint))
672
687
(copy(nextRemoteCommit_opt = Some (nextRemoteCommit)), commitSig)
673
688
}
674
689
675
- def receiveCommit (keyManager : ChannelKeyManager , params : ChannelParams , changes : CommitmentChanges , localPerCommitmentPoint : PublicKey , commit : CommitSig )(implicit log : LoggingAdapter ): Either [ChannelException , Commitment ] = {
690
+ def receiveCommit (keyManager : ChannelKeyManager , params : ChannelParams , changes : CommitmentChanges , localPerCommitmentPoint : PublicKey , commit : CommitSig , localNonce_opt : Option [( SecretNonce , IndividualNonce )] )(implicit log : LoggingAdapter ): Either [ChannelException , Commitment ] = {
676
691
// they sent us a signature for *their* view of *our* next commit tx
677
692
// so in terms of rev.hashes and indexes we have:
678
693
// ourCommit.index -> our current revocation hash, which is about to become our old revocation hash
@@ -683,7 +698,7 @@ case class Commitment(fundingTxIndex: Long,
683
698
// and will increment our index
684
699
val localCommitIndex = localCommit.index + 1
685
700
val spec = CommitmentSpec .reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed)
686
- LocalCommit .fromCommitSig(keyManager, params, fundingTxId, fundingTxIndex, remoteFundingPubKey, commitInput, commit, localCommitIndex, spec, localPerCommitmentPoint).map { localCommit1 =>
701
+ LocalCommit .fromCommitSig(keyManager, params, fundingTxId, fundingTxIndex, remoteFundingPubKey, commitInput, commit, localCommitIndex, spec, localPerCommitmentPoint, localNonce_opt ).map { localCommit1 =>
687
702
log.info(s " built local commit number= $localCommitIndex toLocalMsat= ${spec.toLocal.toLong} toRemoteMsat= ${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw= ${spec.commitTxFeerate} txid= ${localCommit1.commitTxAndRemoteSig.commitTx.tx.txid} fundingTxId= $fundingTxId" , spec.htlcs.collect(DirectedHtlc .incoming).map(_.id).mkString(" ," ), spec.htlcs.collect(DirectedHtlc .outgoing).map(_.id).mkString(" ," ))
688
703
copy(localCommit = localCommit1)
689
704
}
@@ -698,9 +713,10 @@ case class Commitment(fundingTxIndex: Long,
698
713
addSigs(unsignedCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex).publicKey, remoteFundingPubKey, localSig, remoteSig)
699
714
case Right (remotePartialSigWithNonce) =>
700
715
val fundingPubKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex)
701
- val localNonce = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, ChannelKeyManager .keyPath(fundingPubKey.publicKey), localCommit.index)
716
+ val channelKeyPath = ChannelKeyManager .keyPath(keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex = 0L ))
717
+ val localNonce = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, channelKeyPath, localCommit.index)
702
718
val Right (partialSig) = keyManager.partialSign(unsignedCommitTx,
703
- keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex = 0 ), remoteFundingPubKey,
719
+ keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey,
704
720
TxOwner .Local ,
705
721
localNonce, remotePartialSigWithNonce.nonce)
706
722
val Right (aggSig) = Musig2 .aggregateTaprootSignatures(
@@ -1037,11 +1053,17 @@ case class Commitments(params: ChannelParams,
1037
1053
}
1038
1054
}
1039
1055
1040
- def sendCommit (keyManager : ChannelKeyManager , nextRemoteNonce_opt : Option [IndividualNonce ] = None )(implicit log : LoggingAdapter ): Either [ChannelException , (Commitments , Seq [CommitSig ])] = {
1056
+ def sendCommit (keyManager : ChannelKeyManager , nextRemoteNonces : List [IndividualNonce ] = List .empty )(implicit log : LoggingAdapter ): Either [ChannelException , (Commitments , Seq [CommitSig ])] = {
1041
1057
remoteNextCommitInfo match {
1042
1058
case Right (_) if ! changes.localHasChanges => Left (CannotSignWithoutChanges (channelId))
1043
1059
case Right (remoteNextPerCommitmentPoint) =>
1044
- val (active1, sigs) = active.map(_.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, nextRemoteNonce_opt)).unzip
1060
+ val (active1, sigs) = this .params.commitmentFormat match {
1061
+ case SimpleTaprootChannelsStagingCommitmentFormat =>
1062
+ require(active.size <= nextRemoteNonces.size, s " we have ${active.size} commitments but ${nextRemoteNonces.size} remote musig2 nonces " )
1063
+ active.zip(nextRemoteNonces).map { case (c, n) => c.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, Some (n)) } unzip
1064
+ case _ =>
1065
+ active.map(_.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, None )).unzip
1066
+ }
1045
1067
val commitments1 = copy(
1046
1068
changes = changes.copy(
1047
1069
localChanges = changes.localChanges.copy(proposed = Nil , signed = changes.localChanges.proposed),
@@ -1066,7 +1088,11 @@ case class Commitments(params: ChannelParams,
1066
1088
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitIndex + 1 )
1067
1089
// Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments.
1068
1090
val active1 = active.zip(commits).map { case (commitment, commit) =>
1069
- commitment.receiveCommit(keyManager, params, changes, localPerCommitmentPoint, commit) match {
1091
+ val localNonce_opt = params.commitmentFormat match {
1092
+ case SimpleTaprootChannelsStagingCommitmentFormat => Some (keyManager.verificationNonce(params.localParams.fundingKeyPath, commitment.fundingTxIndex, channelKeyPath, localCommitIndex + 1 ))
1093
+ case _ => None
1094
+ }
1095
+ commitment.receiveCommit(keyManager, params, changes, localPerCommitmentPoint, commit, localNonce_opt) match {
1070
1096
case Left (f) => return Left (f)
1071
1097
case Right (commitment1) => commitment1
1072
1098
}
@@ -1076,9 +1102,12 @@ case class Commitments(params: ChannelParams,
1076
1102
val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitIndex + 2 )
1077
1103
val tlvStream : TlvStream [RevokeAndAckTlv ] = params.commitmentFormat match {
1078
1104
case SimpleTaprootChannelsStagingCommitmentFormat =>
1079
- val (_, nonce) = keyManager.verificationNonce(params.localParams.fundingKeyPath, this .latest.fundingTxIndex, channelKeyPath, localCommitIndex + 2 )
1080
- log.debug(" generating our next local nonce with {} {} {} {}" , params.localParams.fundingKeyPath, this .latest.fundingTxIndex, channelKeyPath, localCommitIndex + 2 )
1081
- TlvStream (RevokeAndAckTlv .NextLocalNonceTlv (nonce))
1105
+ val nonces = this .active.map(c => {
1106
+ val n = keyManager.verificationNonce(params.localParams.fundingKeyPath, c.fundingTxIndex, channelKeyPath, localCommitIndex + 2 )
1107
+ log.debug(s " revokeandack: creating verification nonce ${n._2} fundingIndex = ${c.fundingTxIndex} commit index = ${localCommitIndex + 2 }" )
1108
+ n
1109
+ })
1110
+ TlvStream (RevokeAndAckTlv .NextLocalNoncesTlv (nonces.map(_._2).toList))
1082
1111
case _ =>
1083
1112
TlvStream .empty
1084
1113
}
@@ -1103,7 +1132,7 @@ case class Commitments(params: ChannelParams,
1103
1132
remoteNextCommitInfo match {
1104
1133
case Right (_) => Left (UnexpectedRevocation (channelId))
1105
1134
case Left (_) if revocation.perCommitmentSecret.publicKey != active.head.remoteCommit.remotePerCommitmentPoint => Left (InvalidRevocation (channelId))
1106
- case Left (_) if this .params.commitmentFormat == SimpleTaprootChannelsStagingCommitmentFormat && revocation.nexLocalNonce_opt .isEmpty => Left (MissingNextLocalNonce (channelId))
1135
+ case Left (_) if this .params.commitmentFormat == SimpleTaprootChannelsStagingCommitmentFormat && revocation.nexLocalNonces .isEmpty => Left (MissingNextLocalNonce (channelId))
1107
1136
case Left (_) =>
1108
1137
// Since htlcs are shared across all commitments, we generate the actions only once based on the first commitment.
1109
1138
val receivedHtlcs = changes.remoteChanges.signed.collect {
0 commit comments