@@ -16,6 +16,7 @@ import fr.acinq.lightning.channel.states.ChannelContext
1616import fr.acinq.lightning.crypto.ChannelKeys
1717import fr.acinq.lightning.crypto.KeyManager
1818import fr.acinq.lightning.crypto.LocalCommitmentKeys
19+ import fr.acinq.lightning.crypto.NonceGenerator
1920import fr.acinq.lightning.crypto.RemoteCommitmentKeys
2021import fr.acinq.lightning.crypto.ShaChain
2122import fr.acinq.lightning.logging.MDCLogger
@@ -34,6 +35,7 @@ import fr.acinq.lightning.transactions.incomings
3435import fr.acinq.lightning.transactions.outgoings
3536import fr.acinq.lightning.utils.*
3637import fr.acinq.lightning.wire.*
38+ import kotlinx.serialization.Transient
3739import kotlin.math.min
3840
3941/* * Static channel parameters shared by all commitments. */
@@ -63,7 +65,7 @@ data class RemoteChanges(val proposed: List<UpdateMessage>, val acked: List<Upda
6365 val all: List <UpdateMessage > get() = proposed + signed + acked
6466}
6567
66- /* * Changes are applied to all commitments, and must be be valid for all commitments. */
68+ /* * Changes are applied to all commitments, and must be valid for all commitments. */
6769data class CommitmentChanges (val localChanges : LocalChanges , val remoteChanges : RemoteChanges , val localNextHtlcId : Long , val remoteNextHtlcId : Long ) {
6870 fun addLocalProposal (proposal : UpdateMessage ): CommitmentChanges = copy(localChanges = localChanges.copy(proposed = localChanges.proposed + proposal))
6971
@@ -130,7 +132,20 @@ data class LocalCommit(val index: Long, val spec: CommitmentSpec, val txId: TxId
130132 commitmentFormat = commitmentFormat,
131133 spec = spec,
132134 )
133- if (! localCommitTx.checkRemoteSig(fundingKey.publicKey(), remoteFundingPubKey, commit.signature)) {
135+ val remoteSigOk = when (commitmentFormat) {
136+ Transactions .CommitmentFormat .SimpleTaprootChannels ->
137+ when (commit.sigOrPartialSig) {
138+ is ChannelSpendSignature .PartialSignatureWithNonce -> {
139+ val localNonce = NonceGenerator .verificationNonce(commitInput.outPoint.txid, fundingKey, remoteFundingPubKey, localCommitIndex)
140+ localCommitTx.checkRemotePartialSignature(fundingKey.publicKey(), remoteFundingPubKey, commit.sigOrPartialSig, localNonce.publicNonce)
141+ }
142+
143+ is ChannelSpendSignature .IndividualSignature -> false
144+ }
145+
146+ else -> localCommitTx.checkRemoteSig(fundingKey.publicKey(), remoteFundingPubKey, commit.signature)
147+ }
148+ if (! remoteSigOk) {
134149 log.error { " remote signature $commit is invalid" }
135150 return Either .Left (InvalidCommitmentSignature (channelParams.channelId, localCommitTx.tx.txid))
136151 }
@@ -142,7 +157,7 @@ data class LocalCommit(val index: Long, val spec: CommitmentSpec, val txId: TxId
142157 return Either .Left (InvalidHtlcSignature (channelParams.channelId, htlcTx.tx.txid))
143158 }
144159 }
145- return Either .Right (LocalCommit (localCommitIndex, spec, localCommitTx.tx.txid, commit.signature , commit.htlcSignatures))
160+ return Either .Right (LocalCommit (localCommitIndex, spec, localCommitTx.tx.txid, commit.sigOrPartialSig , commit.htlcSignatures))
146161 }
147162 }
148163}
@@ -157,6 +172,7 @@ data class RemoteCommit(val index: Long, val spec: CommitmentSpec, val txid: TxI
157172 remoteFundingPubKey : PublicKey ,
158173 commitInput : Transactions .InputInfo ,
159174 commitmentFormat : Transactions .CommitmentFormat ,
175+ remoteNonce : IndividualNonce ? ,
160176 batchSize : Int = 1
161177 ): CommitSig {
162178 val fundingKey = channelKeys.fundingKey(fundingTxIndex)
@@ -172,23 +188,28 @@ data class RemoteCommit(val index: Long, val spec: CommitmentSpec, val txid: TxI
172188 commitmentFormat = commitmentFormat,
173189 spec = spec
174190 )
175- val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubKey)
176- val htlcSigs = sortedHtlcsTxs.map { it.localSig(commitKeys) }
177- val tlvs = buildSet<CommitSigTlv > {
178- if (batchSize > 1 ) add(CommitSigTlv .Batch (batchSize))
191+ val sig = when (commitmentFormat) {
192+ is Transactions .CommitmentFormat .SimpleTaprootChannels -> {
193+ val localNonce = NonceGenerator .signingNonce(fundingKey.publicKey(), remoteFundingPubKey, commitInput.outPoint.txid)
194+ remoteCommitTx.partialSign(fundingKey, remoteFundingPubKey, mapOf (), localNonce, listOf (localNonce.publicNonce, remoteNonce!! )).right!! // FIXME
195+ }
196+
197+ else -> remoteCommitTx.sign(fundingKey, remoteFundingPubKey)
179198 }
180- return CommitSig (channelParams.channelId, sig, htlcSigs.toList(), TlvStream (tlvs))
199+ val htlcSigs = sortedHtlcsTxs.map { it.localSig(commitKeys) }
200+ return CommitSig (channelParams.channelId, sig, htlcSigs.toList(), batchSize)
181201 }
182202
183- fun sign (channelParams : ChannelParams , channelKeys : ChannelKeys , signingSession : InteractiveTxSigningSession ): CommitSig {
203+ fun sign (channelParams : ChannelParams , channelKeys : ChannelKeys , signingSession : InteractiveTxSigningSession , remoteNonce : IndividualNonce ? ): CommitSig {
184204 return sign(
185205 channelParams,
186206 signingSession.remoteCommitParams,
187207 channelKeys,
188208 signingSession.fundingTxIndex,
189209 signingSession.fundingParams.remoteFundingPubkey,
190210 signingSession.commitInput(channelKeys),
191- signingSession.fundingParams.commitmentFormat
211+ signingSession.fundingParams.commitmentFormat,
212+ remoteNonce
192213 )
193214 }
194215}
@@ -255,7 +276,14 @@ data class Commitment(
255276 val localSig = unsignedCommitTx.sign(fundingKey, remoteFundingPubkey)
256277 unsignedCommitTx.aggregateSigs(fundingKey.publicKey(), remoteFundingPubkey, localSig, remoteSig)
257278 }
258- else -> throw IllegalArgumentException (" not implemented" ) // FIXME
279+ is ChannelSpendSignature .PartialSignatureWithNonce -> {
280+ val localNonce = NonceGenerator .verificationNonce(fundingTxId, fundingKey, remoteFundingPubkey, localCommit.index)
281+ // We have already validated the remote nonce and partial signature when we received it, so we're guaranteed
282+ // that the following code cannot produce an error.
283+ val localSig = unsignedCommitTx.partialSign(fundingKey, remoteFundingPubkey, mapOf (), localNonce, listOf (localNonce.publicNonce, remoteSig.nonce)).right!!
284+ val signedTx = unsignedCommitTx.aggregateSigs(fundingKey.publicKey(), remoteFundingPubkey, localSig, remoteSig, mapOf ()).right!!
285+ signedTx
286+ }
259287 }
260288 }
261289
@@ -518,7 +546,16 @@ data class Commitment(
518546 }
519547 }
520548
521- fun sendCommit (params : ChannelParams , channelKeys : ChannelKeys , commitKeys : RemoteCommitmentKeys , changes : CommitmentChanges , remoteNextPerCommitmentPoint : PublicKey , batchSize : Int , log : MDCLogger ): Pair <Commitment , CommitSig > {
549+ fun sendCommit (
550+ params : ChannelParams ,
551+ channelKeys : ChannelKeys ,
552+ commitKeys : RemoteCommitmentKeys ,
553+ changes : CommitmentChanges ,
554+ remoteNextPerCommitmentPoint : PublicKey ,
555+ batchSize : Int ,
556+ nextRemoteNonce : IndividualNonce ? ,
557+ log : MDCLogger
558+ ): Pair <Commitment , CommitSig > {
522559 val fundingKey = localFundingKey(channelKeys)
523560 // remote commitment will include all local changes + remote acked changes
524561 val spec = CommitmentSpec .reduce(remoteCommit.spec, changes.remoteChanges.acked, changes.localChanges.proposed)
@@ -533,7 +570,18 @@ data class Commitment(
533570 commitmentFormat = commitmentFormat,
534571 spec = spec
535572 )
536- val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubkey)
573+ val sig = when (commitmentFormat) {
574+ Transactions .CommitmentFormat .SimpleTaprootChannels -> ChannelSpendSignature .IndividualSignature (ByteVector64 .Zeroes )
575+ else -> remoteCommitTx.sign(fundingKey, remoteFundingPubkey)
576+ }
577+ val partialSig = when (commitmentFormat) {
578+ Transactions .CommitmentFormat .SimpleTaprootChannels -> {
579+ val localNonce = NonceGenerator .signingNonce(fundingKey.publicKey(), remoteFundingPubkey, remoteCommitTx.input.outPoint.txid)
580+ remoteCommitTx.partialSign(fundingKey, remoteFundingPubkey, mapOf (), localNonce, listOf (localNonce.publicNonce, nextRemoteNonce!! )).right!!
581+ }
582+
583+ else -> null
584+ }
537585 val htlcSigs = sortedHtlcTxs.map { it.localSig(commitKeys) }
538586
539587 // NB: IN/OUT htlcs are inverted because this is the remote commit
@@ -566,6 +614,7 @@ data class Commitment(
566614 if (batchSize > 1 ) {
567615 add(CommitSigTlv .Batch (batchSize))
568616 }
617+ partialSig?.let { add(CommitSigTlv .PartialSignatureWithNonce (it)) }
569618 }
570619 val commitSig = CommitSig (params.channelId, sig, htlcSigs.toList(), TlvStream (tlvs))
571620 val commitment1 = copy(nextRemoteCommit = RemoteCommit (remoteCommit.index + 1 , spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint))
@@ -644,6 +693,10 @@ data class Commitments(
644693 val payments : Map <Long , UUID >, // for outgoing htlcs, maps to paymentId
645694 val remoteNextCommitInfo : Either <WaitingForRevocation , PublicKey >, // this one is tricky, it must be kept in sync with Commitment.nextRemoteCommit
646695 val remotePerCommitmentSecrets : ShaChain ,
696+ @Transient val remoteCommitNonces : Map <TxId , IndividualNonce >,
697+ @Transient val localCloseeNonce : Transactions .LocalNonce ? ,
698+ @Transient val remoteCloseeNonce : IndividualNonce ? ,
699+ @Transient val localCloserNonces : Transactions .CloserNonces ? ,
647700) {
648701 init {
649702 require(active.isNotEmpty()) { " there must be at least one active commitment" }
@@ -674,6 +727,10 @@ data class Commitments(
674727 addAll(active)
675728 })
676729
730+ fun addRemoteCommitNonce (fundingTxId : TxId , nonce : IndividualNonce ? ): Commitments = nonce?.let { this .copy(remoteCommitNonces = this .remoteCommitNonces + (fundingTxId to it)) } ? : this
731+
732+ fun resetNonces (): Commitments = copy(remoteCommitNonces = emptyMap(), localCloseeNonce = null , remoteCloseeNonce = null , localCloserNonces = null )
733+
677734 fun channelKeys (keyManager : KeyManager ): ChannelKeys = channelParams.localParams.channelKeys(keyManager)
678735
679736 fun isMoreRecent (other : Commitments ): Boolean {
@@ -838,7 +895,7 @@ data class Commitments(
838895 val remoteNextPerCommitmentPoint = remoteNextCommitInfo.right ? : return Either .Left (CannotSignBeforeRevocation (channelId))
839896 val commitKeys = channelKeys.remoteCommitmentKeys(channelParams, remoteNextPerCommitmentPoint)
840897 if (! changes.localHasChanges()) return Either .Left (CannotSignWithoutChanges (channelId))
841- val (active1, sigs) = active.map { it.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size, log) }.unzip()
898+ val (active1, sigs) = active.map { it.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size, remoteCommitNonces.get(it.fundingTxId), log) }.unzip()
842899 val commitments1 = copy(
843900 active = active1,
844901 remoteNextCommitInfo = Either .Left (WaitingForRevocation (localCommitIndex)),
@@ -868,7 +925,12 @@ data class Commitments(
868925 // we will send our revocation preimage + our next revocation hash
869926 val localPerCommitmentSecret = channelKeys.commitmentSecret(localCommitIndex)
870927 val localNextPerCommitmentPoint = channelKeys.commitmentPoint(localCommitIndex + 2 )
871- val revocation = RevokeAndAck (channelId, localPerCommitmentSecret, localNextPerCommitmentPoint)
928+ val localCommitNonces = active.filter { it.commitmentFormat == Transactions .CommitmentFormat .SimpleTaprootChannels }.map {
929+ val localNonce = NonceGenerator .verificationNonce(it.fundingTxId, it.localFundingKey(channelKeys), it.remoteFundingPubkey, localCommitIndex + 2 )
930+ it.fundingTxId to localNonce.publicNonce
931+ }
932+ val tlvs: Set <RevokeAndAckTlv > = if (localCommitNonces.isEmpty()) setOf () else setOf (RevokeAndAckTlv .NextLocalNonces (localCommitNonces))
933+ val revocation = RevokeAndAck (channelId, localPerCommitmentSecret, localNextPerCommitmentPoint, TlvStream (tlvs))
872934 val commitments1 = copy(
873935 active = active1,
874936 changes = changes.copy(
@@ -929,10 +991,22 @@ data class Commitments(
929991 remoteNextCommitInfo = Either .Right (revocation.nextPerCommitmentPoint),
930992 remotePerCommitmentSecrets = remotePerCommitmentSecrets.addHash(revocation.perCommitmentSecret.value, 0xFFFFFFFFFFFFL - remoteCommitIndex),
931993 payments = payments1,
994+ remoteCommitNonces = revocation.nextCommitNonces
932995 )
933996 return Either .Right (Pair (commitments1, actions.toList()))
934997 }
935998
999+ fun createShutdown (channelKeys : ChannelKeys , finalScriptPubKey : ByteVector ): Pair <Commitments , Shutdown > = when (latest.commitmentFormat) {
1000+ is Transactions .CommitmentFormat .SimpleTaprootChannels -> {
1001+ // We create a fresh local closee nonce every time we send shutdown.
1002+ val localFundingPubKey = channelKeys.fundingKey(latest.fundingTxIndex).publicKey()
1003+ val localCloseeNonce = NonceGenerator .signingNonce(localFundingPubKey, latest.remoteFundingPubkey, latest.fundingTxId)
1004+ this .copy(localCloseeNonce = localCloseeNonce) to Shutdown (channelId, finalScriptPubKey, TlvStream <ShutdownTlv >(ShutdownTlv .ShutdownNonce (localCloseeNonce.publicNonce)))
1005+ }
1006+
1007+ else -> this to Shutdown (channelId, finalScriptPubKey)
1008+ }
1009+
9361010 private fun ChannelContext.updateFundingStatus (fundingTxId : TxId , updateMethod : (Commitment , Long ) -> Commitment ): Either <Commitments , Pair <Commitments , Commitment >> {
9371011 return when (val c = all.find { it.fundingTxId == fundingTxId }) {
9381012 is Commitment -> {
0 commit comments