diff --git a/src/commonMain/kotlin/fr/acinq/lightning/Features.kt b/src/commonMain/kotlin/fr/acinq/lightning/Features.kt index a819b9ecb..3f04d4902 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/Features.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/Features.kt @@ -140,8 +140,6 @@ sealed class Feature { override val scopes: Set get() = setOf(FeatureScope.Invoice) } - // The following features have not been standardised, hence the high feature bits to avoid conflicts. - // We historically used the following feature bit in our invoices. // However, the spec assigned the same feature bit to `option_scid_alias` (https://github.com/lightning/bolts/pull/910). // We're moving this feature bit to 148, but we have to keep supporting it until enough wallet users have migrated, then we can remove it. @@ -153,6 +151,15 @@ sealed class Feature { override val scopes: Set get() = setOf(FeatureScope.Init, FeatureScope.Node, FeatureScope.Invoice) } + @Serializable + object SimpleClose : Feature() { + override val rfcName get() = "option_simple_close" + override val mandatory get() = 60 + override val scopes: Set get() = setOf(FeatureScope.Init, FeatureScope.Node) + } + + // The following features have not been standardised, hence the high feature bits to avoid conflicts. + /** This feature bit should be activated when a node accepts having their channel reserve set to 0. */ @Serializable object ZeroReserveChannels : Feature() { @@ -324,6 +331,7 @@ data class Features(val activated: Map, val unknown: Se Feature.ChannelType, Feature.PaymentMetadata, Feature.TrampolinePayment, + Feature.SimpleClose, Feature.ExperimentalTrampolinePayment, Feature.ZeroReserveChannels, Feature.ZeroConfChannels, @@ -367,6 +375,7 @@ data class Features(val activated: Map, val unknown: Se Feature.PaymentSecret to listOf(Feature.VariableLengthOnion), Feature.BasicMultiPartPayment to listOf(Feature.PaymentSecret), Feature.AnchorOutputs to listOf(Feature.StaticRemoteKey), + Feature.SimpleClose to listOf(Feature.ShutdownAnySegwit), Feature.TrampolinePayment to listOf(Feature.PaymentSecret), Feature.ExperimentalTrampolinePayment to listOf(Feature.PaymentSecret) ) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/wire/ChannelTlv.kt b/src/commonMain/kotlin/fr/acinq/lightning/wire/ChannelTlv.kt index bd1aa3e0d..76a268efd 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/wire/ChannelTlv.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/wire/ChannelTlv.kt @@ -340,6 +340,96 @@ sealed class ClosingSignedTlv : Tlv { } } +sealed class ClosingCompleteTlv : Tlv { + /** Signature for a closing transaction containing only the closer's output. */ + data class CloserNoClosee(val sig: ByteVector64) : ClosingCompleteTlv() { + override val tag: Long get() = CloserNoClosee.tag + override fun write(out: Output) = LightningCodecs.writeBytes(sig.toByteArray(), out) + + companion object : TlvValueReader { + const val tag: Long = 1 + override fun read(input: Input): CloserNoClosee = CloserNoClosee(LightningCodecs.bytes(input, 64).toByteVector64()) + } + } + + /** Signature for a closing transaction containing only the closee's output. */ + data class NoCloserClosee(val sig: ByteVector64) : ClosingCompleteTlv() { + override val tag: Long get() = NoCloserClosee.tag + override fun write(out: Output) = LightningCodecs.writeBytes(sig.toByteArray(), out) + + companion object : TlvValueReader { + const val tag: Long = 2 + override fun read(input: Input): NoCloserClosee = NoCloserClosee(LightningCodecs.bytes(input, 64).toByteVector64()) + } + } + + /** Signature for a closing transaction containing the closer and closee's outputs. */ + data class CloserAndClosee(val sig: ByteVector64) : ClosingCompleteTlv() { + override val tag: Long get() = CloserAndClosee.tag + override fun write(out: Output) = LightningCodecs.writeBytes(sig.toByteArray(), out) + + companion object : TlvValueReader { + const val tag: Long = 3 + override fun read(input: Input): CloserAndClosee = CloserAndClosee(LightningCodecs.bytes(input, 64).toByteVector64()) + } + } + + data class ChannelData(val ecb: EncryptedChannelData) : ClosingCompleteTlv() { + override val tag: Long get() = ChannelData.tag + override fun write(out: Output) = LightningCodecs.writeBytes(ecb.data, out) + + companion object : TlvValueReader { + const val tag: Long = 0x47010000 + override fun read(input: Input): ChannelData = ChannelData(EncryptedChannelData(LightningCodecs.bytes(input, input.availableBytes).toByteVector())) + } + } +} + +sealed class ClosingSigTlv : Tlv { + /** Signature for a closing transaction containing only the closer's output. */ + data class CloserNoClosee(val sig: ByteVector64) : ClosingSigTlv() { + override val tag: Long get() = CloserNoClosee.tag + override fun write(out: Output) = LightningCodecs.writeBytes(sig.toByteArray(), out) + + companion object : TlvValueReader { + const val tag: Long = 1 + override fun read(input: Input): CloserNoClosee = CloserNoClosee(LightningCodecs.bytes(input, 64).toByteVector64()) + } + } + + /** Signature for a closing transaction containing only the closee's output. */ + data class NoCloserClosee(val sig: ByteVector64) : ClosingSigTlv() { + override val tag: Long get() = NoCloserClosee.tag + override fun write(out: Output) = LightningCodecs.writeBytes(sig.toByteArray(), out) + + companion object : TlvValueReader { + const val tag: Long = 2 + override fun read(input: Input): NoCloserClosee = NoCloserClosee(LightningCodecs.bytes(input, 64).toByteVector64()) + } + } + + /** Signature for a closing transaction containing the closer and closee's outputs. */ + data class CloserAndClosee(val sig: ByteVector64) : ClosingSigTlv() { + override val tag: Long get() = CloserAndClosee.tag + override fun write(out: Output) = LightningCodecs.writeBytes(sig.toByteArray(), out) + + companion object : TlvValueReader { + const val tag: Long = 3 + override fun read(input: Input): CloserAndClosee = CloserAndClosee(LightningCodecs.bytes(input, 64).toByteVector64()) + } + } + + data class ChannelData(val ecb: EncryptedChannelData) : ClosingSigTlv() { + override val tag: Long get() = ChannelData.tag + override fun write(out: Output) = LightningCodecs.writeBytes(ecb.data, out) + + companion object : TlvValueReader { + const val tag: Long = 0x47010000 + override fun read(input: Input): ChannelData = ChannelData(EncryptedChannelData(LightningCodecs.bytes(input, input.availableBytes).toByteVector())) + } + } +} + sealed class PleaseOpenChannelTlv : Tlv { // NB: this is a temporary tlv that is only used to ensure a smooth migration to lightning-kmp for the android version of Phoenix. data class GrandParents(val outpoints: List) : PleaseOpenChannelTlv() { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/wire/LightningMessages.kt b/src/commonMain/kotlin/fr/acinq/lightning/wire/LightningMessages.kt index 598be3826..d5ba2728b 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/wire/LightningMessages.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/wire/LightningMessages.kt @@ -76,6 +76,8 @@ interface LightningMessage { ChannelUpdate.type -> ChannelUpdate.read(stream) Shutdown.type -> Shutdown.read(stream) ClosingSigned.type -> ClosingSigned.read(stream) + ClosingComplete.type -> ClosingComplete.read(stream) + ClosingSig.type -> ClosingSig.read(stream) OnionMessage.type -> OnionMessage.read(stream) PayToOpenRequest.type -> PayToOpenRequest.read(stream) PayToOpenResponse.type -> PayToOpenResponse.read(stream) @@ -1548,6 +1550,88 @@ data class ClosingSigned( } } +data class ClosingComplete( + override val channelId: ByteVector32, + val fees: Satoshi, + val lockTime: Long, + val tlvStream: TlvStream = TlvStream.empty() +) : ChannelMessage, HasChannelId, HasEncryptedChannelData { + override val type: Long get() = ClosingComplete.type + + val closerNoCloseeSig: ByteVector64? = tlvStream.get()?.sig + val noCloserCloseeSig: ByteVector64? = tlvStream.get()?.sig + val closerAndCloseeSig: ByteVector64? = tlvStream.get()?.sig + + override val channelData: EncryptedChannelData get() = tlvStream.get()?.ecb ?: EncryptedChannelData.empty + override fun withNonEmptyChannelData(ecd: EncryptedChannelData): ClosingComplete = copy(tlvStream = tlvStream.addOrUpdate(ClosingCompleteTlv.ChannelData(ecd))) + + override fun write(out: Output) { + LightningCodecs.writeBytes(channelId, out) + LightningCodecs.writeU64(fees.toLong(), out) + LightningCodecs.writeU32(lockTime.toInt(), out) + TlvStreamSerializer(false, readers).write(tlvStream, out) + } + + companion object : LightningMessageReader { + const val type: Long = 40 + + @Suppress("UNCHECKED_CAST") + val readers = mapOf( + ClosingCompleteTlv.CloserNoClosee.tag to ClosingCompleteTlv.CloserNoClosee.Companion as TlvValueReader, + ClosingCompleteTlv.NoCloserClosee.tag to ClosingCompleteTlv.NoCloserClosee.Companion as TlvValueReader, + ClosingCompleteTlv.CloserAndClosee.tag to ClosingCompleteTlv.CloserAndClosee.Companion as TlvValueReader, + ClosingCompleteTlv.ChannelData.tag to ClosingCompleteTlv.ChannelData.Companion as TlvValueReader + ) + + override fun read(input: Input): ClosingComplete { + return ClosingComplete( + LightningCodecs.bytes(input, 32).byteVector32(), + LightningCodecs.u64(input).sat, + LightningCodecs.u32(input).toLong(), + TlvStreamSerializer(false, readers).read(input) + ) + } + } +} + +data class ClosingSig( + override val channelId: ByteVector32, + val tlvStream: TlvStream = TlvStream.empty() +) : ChannelMessage, HasChannelId, HasEncryptedChannelData { + override val type: Long get() = ClosingSig.type + + val closerNoCloseeSig: ByteVector64? = tlvStream.get()?.sig + val noCloserCloseeSig: ByteVector64? = tlvStream.get()?.sig + val closerAndCloseeSig: ByteVector64? = tlvStream.get()?.sig + + override val channelData: EncryptedChannelData get() = tlvStream.get()?.ecb ?: EncryptedChannelData.empty + override fun withNonEmptyChannelData(ecd: EncryptedChannelData): ClosingSig = copy(tlvStream = tlvStream.addOrUpdate(ClosingSigTlv.ChannelData(ecd))) + + override fun write(out: Output) { + LightningCodecs.writeBytes(channelId, out) + TlvStreamSerializer(false, readers).write(tlvStream, out) + } + + companion object : LightningMessageReader { + const val type: Long = 41 + + @Suppress("UNCHECKED_CAST") + val readers = mapOf( + ClosingSigTlv.CloserNoClosee.tag to ClosingSigTlv.CloserNoClosee.Companion as TlvValueReader, + ClosingSigTlv.NoCloserClosee.tag to ClosingSigTlv.NoCloserClosee.Companion as TlvValueReader, + ClosingSigTlv.CloserAndClosee.tag to ClosingSigTlv.CloserAndClosee.Companion as TlvValueReader, + ClosingSigTlv.ChannelData.tag to ClosingSigTlv.ChannelData.Companion as TlvValueReader + ) + + override fun read(input: Input): ClosingSig { + return ClosingSig( + LightningCodecs.bytes(input, 32).byteVector32(), + TlvStreamSerializer(false, readers).read(input) + ) + } + } +} + data class OnionMessage( val blindingKey: PublicKey, val onionRoutingPacket: OnionRoutingPacket diff --git a/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt index acb976052..3cd5db8ab 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/wire/LightningCodecsTestsCommon.kt @@ -660,6 +660,34 @@ class LightningCodecsTestsCommon : LightningTestSuite() { } } + @Test + fun `encode - decode closing messages`() { + val channelId = ByteVector32("58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86") + val sig1 = ByteVector64("01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101") + val sig2 = ByteVector64("02020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202") + val sig3 = ByteVector64("03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303") + val testCases = mapOf( + // @formatter:off + Hex.decode("0028 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86 0000000000000451 00000000") to ClosingComplete(channelId, 1105.sat, 0), + Hex.decode("0028 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86 0000000000000451 000c96a8 024001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101") to ClosingComplete(channelId, 1105.sat, 825_000, TlvStream(ClosingCompleteTlv.NoCloserClosee(sig1))), + Hex.decode("0028 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86 0000000000000451 00000000 034001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101") to ClosingComplete(channelId, 1105.sat, 0, TlvStream(ClosingCompleteTlv.CloserAndClosee(sig1))), + Hex.decode("0028 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86 0000000000000451 00000000 014001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101 034002020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202") to ClosingComplete(channelId, 1105.sat, 0, TlvStream(ClosingCompleteTlv.CloserNoClosee(sig1), ClosingCompleteTlv.CloserAndClosee(sig2))), + Hex.decode("0028 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86 0000000000000451 00000000 014001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101 024002020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202 034003030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303") to ClosingComplete(channelId, 1105.sat, 0, TlvStream(ClosingCompleteTlv.CloserNoClosee(sig1), ClosingCompleteTlv.NoCloserClosee(sig2), ClosingCompleteTlv.CloserAndClosee(sig3))), + Hex.decode("0029 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86") to ClosingSig(channelId), + Hex.decode("0029 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86 024001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101") to ClosingSig(channelId, TlvStream(ClosingSigTlv.NoCloserClosee(sig1))), + Hex.decode("0029 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86 034001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101") to ClosingSig(channelId, TlvStream(ClosingSigTlv.CloserAndClosee(sig1))), + Hex.decode("0029 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86 014001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101 034002020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202") to ClosingSig(channelId, TlvStream(ClosingSigTlv.CloserNoClosee(sig1), ClosingSigTlv.CloserAndClosee(sig2))), + Hex.decode("0029 58a00a6f14e69a2e97b18cf76f755c8551fea9947cf7b6ece9d641013eba5f86 014001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101 024002020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202 034003030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303") to ClosingSig(channelId, TlvStream(ClosingSigTlv.CloserNoClosee(sig1), ClosingSigTlv.NoCloserClosee(sig2), ClosingSigTlv.CloserAndClosee(sig3))), + // @formatter:on + ) + testCases.forEach { + val decoded = LightningMessage.decode(it.key) + assertEquals(it.value, decoded) + val encoded = LightningMessage.encode(it.value) + assertArrayEquals(it.key, encoded) + } + } + @Test fun `nonreg backup channel data`() { val channelId = randomBytes32() @@ -711,6 +739,12 @@ class LightningCodecsTestsCommon : LightningTestSuite() { Hex.decode("0027") + channelId.toByteArray() + Hex.decode("00000000075bcd15") + signature.toByteArray() + Hex.decode("03 02 0102") + Hex.decode("fe47010000 00") to ClosingSigned(channelId, 123456789.sat, signature, TlvStream(setOf(ClosingSignedTlv.ChannelData(EncryptedChannelData.empty)), setOf(GenericTlv(3, ByteVector("0102"))))), Hex.decode("0027") + channelId.toByteArray() + Hex.decode("00000000075bcd15") + signature.toByteArray() + Hex.decode("fe47010000 07 cccccccccccccc") to ClosingSigned(channelId, 123456789.sat, signature).withChannelData(ByteVector("cccccccccccccc")), Hex.decode("0027") + channelId.toByteArray() + Hex.decode("00000000075bcd15") + signature.toByteArray() + Hex.decode("03 02 0102") + Hex.decode("fe47010000 07 cccccccccccccc") to ClosingSigned(channelId, 123456789.sat, signature, TlvStream(setOf(ClosingSignedTlv.ChannelData(EncryptedChannelData(ByteVector("cccccccccccccc")))), setOf(GenericTlv(3, ByteVector("0102"))))) + // closing_complete + Hex.decode("0028") + channelId.toByteArray() + Hex.decode("0000000000000451 00000000") + Hex.decode("fe47010000 00") to ClosingComplete(channelId, 1105.sat, 0, TlvStream(ClosingCompleteTlv.ChannelData(EncryptedChannelData.empty))), + Hex.decode("0028") + channelId.toByteArray() + Hex.decode("0000000000000451 00000000") + Hex.decode("fe47010000 07 cccccccccccccc") to ClosingComplete(channelId, 1105.sat, 0).withChannelData(ByteVector("cccccccccccccc")), + // closing_sig + Hex.decode("0029") + channelId.toByteArray() + Hex.decode("fe47010000 00") to ClosingSig(channelId, TlvStream(ClosingSigTlv.ChannelData(EncryptedChannelData.empty))), + Hex.decode("0029") + channelId.toByteArray() + Hex.decode("fe47010000 07 cccccccccccccc") to ClosingSig(channelId).withChannelData(ByteVector("cccccccccccccc")), ) // @formatter:on @@ -734,6 +768,8 @@ class LightningCodecsTestsCommon : LightningTestSuite() { RevokeAndAck(randomBytes32(), randomKey(), randomKey().publicKey()), Shutdown(randomBytes32(), ByteVector("deadbeef")), ClosingSigned(randomBytes32(), 0.sat, randomBytes64()), + ClosingComplete(randomBytes32(), 250.sat, 0), + ClosingSig(randomBytes32()), ) messages.forEach { assertEquals(it.withChannelData(belowLimit).channelData, belowLimit) @@ -741,15 +777,6 @@ class LightningCodecsTestsCommon : LightningTestSuite() { } } - @Test - fun `skip backup channel data when message is too large`() { - val channelData = EncryptedChannelData(ByteVector(ByteArray(59500) { 42 })) - val smallCommit = CommitSig(randomBytes32(), randomBytes64(), listOf()) - assertEquals(smallCommit.withChannelData(channelData).channelData, channelData) - val largeCommit = CommitSig(randomBytes32(), randomBytes64(), List(50) { randomBytes64() }) - assertTrue(largeCommit.withChannelData(channelData).channelData.isEmpty()) - } - @Test fun `encode - decode pay-to-open messages`() { val testCases = listOf(