11package fr .acinq .eclair .channel
22
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 }
55import fr .acinq .bitcoin .scalacompat .Crypto .{PrivateKey , PublicKey }
66import fr .acinq .bitcoin .scalacompat .{ByteVector32 , ByteVector64 , Crypto , Musig2 , Satoshi , SatoshiLong , Script , Transaction , TxId }
77import fr .acinq .eclair .blockchain .fee .{FeeratePerByte , FeeratePerKw , FeeratesPerKw , OnChainFeeConf }
@@ -17,6 +17,7 @@ import fr.acinq.eclair.transactions.Transactions._
1717import fr .acinq .eclair .transactions ._
1818import fr .acinq .eclair .wire .protocol ._
1919import fr .acinq .eclair .{BlockHeight , CltvExpiry , CltvExpiryDelta , Features , MilliSatoshi , MilliSatoshiLong , payment }
20+ import grizzled .slf4j .Logging
2021import scodec .bits .ByteVector
2122
2223/** Static channel parameters shared by all commitments. */
@@ -226,11 +227,23 @@ case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig:
226227object LocalCommit {
227228 def fromCommitSig (keyManager : ChannelKeyManager , params : ChannelParams , fundingTxId : TxId ,
228229 fundingTxIndex : Long , remoteFundingPubKey : PublicKey , commitInput : InputInfo ,
229- commit : CommitSig , localCommitIndex : Long , spec : CommitmentSpec , localPerCommitmentPoint : PublicKey ): Either [ChannelException , LocalCommit ] = {
230+ commit : CommitSig , localCommitIndex : Long , spec : CommitmentSpec , localPerCommitmentPoint : PublicKey , localNonce_opt : Option [( SecretNonce , IndividualNonce )] ): Either [ChannelException , LocalCommit ] = {
230231 val (localCommitTx, htlcTxs) = Commitment .makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommitIndex, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, localPerCommitmentPoint, spec)
231232 if (! localCommitTx.checkSig(commit, remoteFundingPubKey, TxOwner .Remote , params.commitmentFormat)) {
232233 return Left (InvalidCommitmentSignature (params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
233234 }
235+ commit.sigOrPartialSig match {
236+ case Left (_) =>
237+ if (! localCommitTx.checkSig(commit, remoteFundingPubKey, TxOwner .Remote , params.commitmentFormat)) {
238+ return Left (InvalidCommitmentSignature (params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
239+ }
240+ case Right (psig) =>
241+ val fundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex).publicKey
242+ val Some (localNonce) = localNonce_opt
243+ if (! localCommitTx.checkPartialSignature(psig, fundingPubkey, localNonce._2, remoteFundingPubKey)) {
244+ return Left (InvalidCommitmentSignature (params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
245+ }
246+ }
234247 val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index)
235248 if (commit.htlcSignatures.size != sortedHtlcTxs.size) {
236249 return Left (HtlcSigCountMismatch (params.channelId, sortedHtlcTxs.size, commit.htlcSignatures.size))
@@ -249,7 +262,7 @@ object LocalCommit {
249262
250263/** The remote commitment maps to a commitment transaction that only our peer can sign and broadcast. */
251264case class RemoteCommit (index : Long , spec : CommitmentSpec , txid : TxId , remotePerCommitmentPoint : PublicKey ) {
252- def sign (keyManager : ChannelKeyManager , params : ChannelParams , fundingTxIndex : Long , remoteFundingPubKey : PublicKey , commitInput : InputInfo , remoteNonce_opt : Option [IndividualNonce ]): CommitSig = {
265+ def sign (keyManager : ChannelKeyManager , params : ChannelParams , fundingTxIndex : Long , remoteFundingPubKey : PublicKey , commitInput : InputInfo , remoteNonce_opt : Option [IndividualNonce ])( implicit log : LoggingAdapter ) : CommitSig = {
253266 val (remoteCommitTx, htlcTxs) = Commitment .makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, index, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, remotePerCommitmentPoint, spec)
254267 val (sig, tlvStream) = params.commitmentFormat match {
255268 case SimpleTaprootChannelsStagingCommitmentFormat =>
@@ -650,6 +663,7 @@ case class Commitment(fundingTxIndex: Long,
650663 val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
651664 val Some (remoteNonce) = nextRemoteNonce_opt
652665 val Right (psig) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner .Remote , localNonce, remoteNonce)
666+ log.debug(s " sendCommit: creating partial sig $psig for remote commit tx ${remoteCommitTx.tx.txid} with remote nonce $remoteNonce and remoteNextPerCommitmentPoint = $remoteNextPerCommitmentPoint" )
653667 Set (CommitSigTlv .PartialSignatureWithNonceTlv (PartialSignatureWithNonce (psig, localNonce._2)))
654668 case _ =>
655669 Set .empty
@@ -665,11 +679,12 @@ case class Commitment(fundingTxIndex: Long,
665679 val commitSig = CommitSig (params.channelId, sig, htlcSigs.toList, TlvStream (Set (
666680 if (batchSize > 1 ) Some (CommitSigTlv .BatchTlv (batchSize)) else None
667681 ).flatten[CommitSigTlv ] ++ partialSig))
682+ log.debug(s " sendCommit: setting remoteNextPerCommitmentPoint to $remoteNextPerCommitmentPoint" )
668683 val nextRemoteCommit = NextRemoteCommit (commitSig, RemoteCommit (remoteCommit.index + 1 , spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint))
669684 (copy(nextRemoteCommit_opt = Some (nextRemoteCommit)), commitSig)
670685 }
671686
672- def receiveCommit (keyManager : ChannelKeyManager , params : ChannelParams , changes : CommitmentChanges , localPerCommitmentPoint : PublicKey , commit : CommitSig )(implicit log : LoggingAdapter ): Either [ChannelException , Commitment ] = {
687+ def receiveCommit (keyManager : ChannelKeyManager , params : ChannelParams , changes : CommitmentChanges , localPerCommitmentPoint : PublicKey , commit : CommitSig , localNonce_opt : Option [( SecretNonce , IndividualNonce )] )(implicit log : LoggingAdapter ): Either [ChannelException , Commitment ] = {
673688 // they sent us a signature for *their* view of *our* next commit tx
674689 // so in terms of rev.hashes and indexes we have:
675690 // ourCommit.index -> our current revocation hash, which is about to become our old revocation hash
@@ -680,7 +695,7 @@ case class Commitment(fundingTxIndex: Long,
680695 // and will increment our index
681696 val localCommitIndex = localCommit.index + 1
682697 val spec = CommitmentSpec .reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed)
683- LocalCommit .fromCommitSig(keyManager, params, fundingTxId, fundingTxIndex, remoteFundingPubKey, commitInput, commit, localCommitIndex, spec, localPerCommitmentPoint).map { localCommit1 =>
698+ LocalCommit .fromCommitSig(keyManager, params, fundingTxId, fundingTxIndex, remoteFundingPubKey, commitInput, commit, localCommitIndex, spec, localPerCommitmentPoint, localNonce_opt ).map { localCommit1 =>
684699 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(" ," ))
685700 copy(localCommit = localCommit1)
686701 }
@@ -695,9 +710,10 @@ case class Commitment(fundingTxIndex: Long,
695710 addSigs(unsignedCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex).publicKey, remoteFundingPubKey, localSig, remoteSig)
696711 case Right (remotePartialSigWithNonce) =>
697712 val fundingPubKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex)
698- val localNonce = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, ChannelKeyManager .keyPath(fundingPubKey.publicKey), localCommit.index)
713+ val channelKeyPath = ChannelKeyManager .keyPath(keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex = 0L ))
714+ val localNonce = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, channelKeyPath, localCommit.index)
699715 val Right (partialSig) = keyManager.partialSign(unsignedCommitTx,
700- keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex = 0 ), remoteFundingPubKey,
716+ keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey,
701717 TxOwner .Local ,
702718 localNonce, remotePartialSigWithNonce.nonce)
703719 val Right (aggSig) = Musig2 .aggregateTaprootSignatures(
@@ -1034,11 +1050,17 @@ case class Commitments(params: ChannelParams,
10341050 }
10351051 }
10361052
1037- def sendCommit (keyManager : ChannelKeyManager , nextRemoteNonce_opt : Option [IndividualNonce ] = None )(implicit log : LoggingAdapter ): Either [ChannelException , (Commitments , Seq [CommitSig ])] = {
1053+ def sendCommit (keyManager : ChannelKeyManager , nextRemoteNonces : List [IndividualNonce ] = List .empty )(implicit log : LoggingAdapter ): Either [ChannelException , (Commitments , Seq [CommitSig ])] = {
10381054 remoteNextCommitInfo match {
10391055 case Right (_) if ! changes.localHasChanges => Left (CannotSignWithoutChanges (channelId))
10401056 case Right (remoteNextPerCommitmentPoint) =>
1041- val (active1, sigs) = active.map(_.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, nextRemoteNonce_opt)).unzip
1057+ val (active1, sigs) = this .params.commitmentFormat match {
1058+ case SimpleTaprootChannelsStagingCommitmentFormat =>
1059+ require(active.size <= nextRemoteNonces.size, s " we have ${active.size} commitments but ${nextRemoteNonces.size} remote musig2 nonces " )
1060+ active.zip(nextRemoteNonces).map { case (c, n) => c.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, Some (n)) } unzip
1061+ case _ =>
1062+ active.map(_.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, None )).unzip
1063+ }
10421064 val commitments1 = copy(
10431065 changes = changes.copy(
10441066 localChanges = changes.localChanges.copy(proposed = Nil , signed = changes.localChanges.proposed),
@@ -1063,7 +1085,11 @@ case class Commitments(params: ChannelParams,
10631085 val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitIndex + 1 )
10641086 // Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments.
10651087 val active1 = active.zip(commits).map { case (commitment, commit) =>
1066- commitment.receiveCommit(keyManager, params, changes, localPerCommitmentPoint, commit) match {
1088+ val localNonce_opt = params.commitmentFormat match {
1089+ case SimpleTaprootChannelsStagingCommitmentFormat => Some (keyManager.verificationNonce(params.localParams.fundingKeyPath, commitment.fundingTxIndex, channelKeyPath, localCommitIndex + 1 ))
1090+ case _ => None
1091+ }
1092+ commitment.receiveCommit(keyManager, params, changes, localPerCommitmentPoint, commit, localNonce_opt) match {
10671093 case Left (f) => return Left (f)
10681094 case Right (commitment1) => commitment1
10691095 }
@@ -1073,9 +1099,12 @@ case class Commitments(params: ChannelParams,
10731099 val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitIndex + 2 )
10741100 val tlvStream : TlvStream [RevokeAndAckTlv ] = params.commitmentFormat match {
10751101 case SimpleTaprootChannelsStagingCommitmentFormat =>
1076- val (_, nonce) = keyManager.verificationNonce(params.localParams.fundingKeyPath, this .latest.fundingTxIndex, channelKeyPath, localCommitIndex + 2 )
1077- log.debug(" generating our next local nonce with {} {} {} {}" , params.localParams.fundingKeyPath, this .latest.fundingTxIndex, channelKeyPath, localCommitIndex + 2 )
1078- TlvStream (RevokeAndAckTlv .NextLocalNonceTlv (nonce))
1102+ val nonces = this .active.map(c => {
1103+ val n = keyManager.verificationNonce(params.localParams.fundingKeyPath, c.fundingTxIndex, channelKeyPath, localCommitIndex + 2 )
1104+ log.debug(s " revokeandack: creating verification nonce ${n._2} fundingIndex = ${c.fundingTxIndex} commit index = ${localCommitIndex + 2 }" )
1105+ n
1106+ })
1107+ TlvStream (RevokeAndAckTlv .NextLocalNoncesTlv (nonces.map(_._2).toList))
10791108 case _ =>
10801109 TlvStream .empty
10811110 }
@@ -1100,7 +1129,7 @@ case class Commitments(params: ChannelParams,
11001129 remoteNextCommitInfo match {
11011130 case Right (_) => Left (UnexpectedRevocation (channelId))
11021131 case Left (_) if revocation.perCommitmentSecret.publicKey != active.head.remoteCommit.remotePerCommitmentPoint => Left (InvalidRevocation (channelId))
1103- case Left (_) if this .params.commitmentFormat == SimpleTaprootChannelsStagingCommitmentFormat && revocation.nexLocalNonce_opt .isEmpty => Left (MissingNextLocalNonce (channelId))
1132+ case Left (_) if this .params.commitmentFormat == SimpleTaprootChannelsStagingCommitmentFormat && revocation.nexLocalNonces .isEmpty => Left (MissingNextLocalNonce (channelId))
11041133 case Left (_) =>
11051134 // Since htlcs are shared across all commitments, we generate the actions only once based on the first commitment.
11061135 val receivedHtlcs = changes.remoteChanges.signed.collect {
0 commit comments