diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala index 140e633a44..0e143a2775 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageCodecs.scala @@ -20,9 +20,9 @@ import fr.acinq.eclair.wire.CommonCodecs._ import fr.acinq.eclair.{KamonExt, wire} import kamon.Kamon import kamon.tag.TagSet -import scodec.bits.{BitVector, ByteVector} +import scodec.bits.{BitVector, ByteVector, HexStringSyntax} import scodec.codecs._ -import scodec.{Attempt, Codec} +import scodec.{Attempt, Codec, DecodeResult} /** * Created by PM on 15/11/2016. @@ -59,7 +59,30 @@ object LightningMessageCodecs { ("yourLastPerCommitmentSecret" | optional(bitsRemaining, privateKey)) :: ("myCurrentPerCommitmentPoint" | optional(bitsRemaining, publicKey))).as[ChannelReestablish] - val openChannelCodec: Codec[OpenChannel] = ( + // Legacy nodes may encode an empty upfront_shutdown_script (0x0000) even if we didn't advertise support for option_upfront_shutdown_script. + // To allow extending all messages with TLV streams, the upfront_shutdown_script field was made mandatory in https://github.com/lightningnetwork/lightning-rfc/pull/714. + // This codec decodes both legacy and new versions, while always encoding with an upfront_shutdown_script (of length 0 if none actually provided). + private val shutdownScriptGuard = Codec[Boolean]( + // Similar to bitsRemaining but encodes 0x0000 for an empty upfront_shutdown_script. + (included: Boolean) => if (included) Attempt.Successful(BitVector.empty) else Attempt.Successful(hex"0000".bits), + // Bolt 2 specifies that upfront_shutdown_scripts must be P2PKH/P2SH or segwit-v0 P2WPK/P2WSH. + // The length of such scripts will always start with 0x00. + // On top of that, since TLV records start with a varint, a TLV stream will never start with 0x00 unless the spec + // assigns TLV type 0 to a new record. If that happens, that record should be the upfront_shutdown_script to allow + // easy backwards-compatibility (as proposed here: https://github.com/lightningnetwork/lightning-rfc/pull/714). + // That means we can discriminate on byte 0x00 to know whether we're decoding an upfront_shutdown_script or a TLV + // stream. + (b: BitVector) => Attempt.successful(DecodeResult(b.startsWith(hex"00".bits), b)) + ) + + private def emptyToNone(script: Option[ByteVector]): Option[ByteVector] = script match { + case Some(s) if s.nonEmpty => script + case _ => None + } + + private val upfrontShutdownScript = optional(shutdownScriptGuard, variableSizeBytes(uint16, bytes)).xmap(emptyToNone, emptyToNone) + + private def openChannelCodec_internal(upfrontShutdownScriptCodec: Codec[Option[ByteVector]]): Codec[OpenChannel] = ( ("chainHash" | bytes32) :: ("temporaryChannelId" | bytes32) :: ("fundingSatoshis" | satoshi) :: @@ -77,7 +100,20 @@ object LightningMessageCodecs { ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: ("firstPerCommitmentPoint" | publicKey) :: - ("channelFlags" | byte)).as[OpenChannel] + ("channelFlags" | byte) :: + ("upfront_shutdown_script" | upfrontShutdownScriptCodec) :: + ("tlvStream_opt" | optional(bitsRemaining, OpenTlv.openTlvCodec))).as[OpenChannel] + + val openChannelCodec = Codec[OpenChannel]( + (open: OpenChannel) => { + // Phoenix versions <= 1.1.0 don't support the upfront_shutdown_script field (they interpret it as a tlv stream + // with an unknown tlv record). For these channels we use an encoding that omits the upfront_shutdown_script for + // backwards-compatibility (once enough Phoenix users have upgraded, we can remove work-around). + val upfrontShutdownScriptCodec = if (open.tlvStream_opt.isDefined) provide(Option.empty[ByteVector]) else upfrontShutdownScript + openChannelCodec_internal(upfrontShutdownScriptCodec).encode(open) + }, + (bits: BitVector) => openChannelCodec_internal(upfrontShutdownScript).decode(bits) + ) val acceptChannelCodec: Codec[AcceptChannel] = ( ("temporaryChannelId" | bytes32) :: @@ -93,7 +129,8 @@ object LightningMessageCodecs { ("paymentBasepoint" | publicKey) :: ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: - ("firstPerCommitmentPoint" | publicKey)).as[AcceptChannel] + ("firstPerCommitmentPoint" | publicKey) :: + ("upfront_shutdown_script" | upfrontShutdownScript)).as[AcceptChannel] val fundingCreatedCodec: Codec[FundingCreated] = ( ("temporaryChannelId" | bytes32) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala index be88cfda64..091ac46e93 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/LightningMessageTypes.scala @@ -84,7 +84,9 @@ case class OpenChannel(chainHash: ByteVector32, delayedPaymentBasepoint: PublicKey, htlcBasepoint: PublicKey, firstPerCommitmentPoint: PublicKey, - channelFlags: Byte) extends ChannelMessage with HasTemporaryChannelId with HasChainHash + channelFlags: Byte, + upfrontShutdownScript: Option[ByteVector] = None, + tlvStream_opt: Option[TlvStream[OpenTlv]] = None) extends ChannelMessage with HasTemporaryChannelId with HasChainHash case class AcceptChannel(temporaryChannelId: ByteVector32, dustLimitSatoshis: Satoshi, @@ -99,7 +101,8 @@ case class AcceptChannel(temporaryChannelId: ByteVector32, paymentBasepoint: PublicKey, delayedPaymentBasepoint: PublicKey, htlcBasepoint: PublicKey, - firstPerCommitmentPoint: PublicKey) extends ChannelMessage with HasTemporaryChannelId + firstPerCommitmentPoint: PublicKey, + upfrontShutdownScript: Option[ByteVector] = None) extends ChannelMessage with HasTemporaryChannelId case class FundingCreated(temporaryChannelId: ByteVector32, fundingTxid: ByteVector32, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/OpenTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/OpenTlv.scala new file mode 100644 index 0000000000..0e11e1e5fb --- /dev/null +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/OpenTlv.scala @@ -0,0 +1,36 @@ +/* + * Copyright 2019 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.eclair.wire + +import fr.acinq.eclair.UInt64 +import fr.acinq.eclair.wire.CommonCodecs._ +import fr.acinq.eclair.wire.TlvCodecs.tlvStream +import scodec.Codec +import scodec.bits.ByteVector +import scodec.codecs._ + +sealed trait OpenTlv extends Tlv + +object OpenTlv { + + case class Placeholder(b: ByteVector) extends OpenTlv + + val openTlvCodec: Codec[TlvStream[OpenTlv]] = tlvStream(discriminated.by(varint) + .typecase(UInt64(65717), variableSizeBytesLong(varintoverflow, bytes).as[Placeholder]) + ) + +} \ No newline at end of file diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala index 7b037fdb99..40991b4967 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/LightningMessageCodecsSpec.scala @@ -87,6 +87,75 @@ class LightningMessageCodecsSpec extends FunSuite { assert(bin === bin2) } + test("encode/decode open_channel") { + val defaultOpen = OpenChannel(ByteVector32.Zeroes, ByteVector32.Zeroes, 1 sat, 1 msat, 1 sat, UInt64(1), 1 sat, 1 msat, 1, CltvExpiryDelta(1), 1, publicKey(1), point(2), point(3), point(4), point(5), point(6), 0.toByte) + // Default encoding that completely omits the upfront_shutdown_script and trailing tlv stream. + // To allow extending all messages with TLV streams, the upfront_shutdown_script was made mandatory in https://github.com/lightningnetwork/lightning-rfc/pull/714 + val defaultEncoded = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000100000000000000010000000000000001000000000000000100000000000000010000000100010001031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00" + case class TestCase(encoded: ByteVector, decoded: OpenChannel, reEncoded: Option[ByteVector] = None) + val testCases = Seq( + // legacy encoding without upfront_shutdown_script + TestCase(defaultEncoded, defaultOpen, Some(defaultEncoded ++ hex"0000")), + // empty upfront_shutdown_script + TestCase(defaultEncoded ++ hex"0000", defaultOpen), + // non-empty upfront_shutdown_script + TestCase(defaultEncoded ++ hex"0004 01abcdef", defaultOpen.copy(upfrontShutdownScript = Some(hex"01abcdef"))), + // missing upfront_shutdown_script + unknown odd tlv records + TestCase(defaultEncoded ++ hex"0302002a 050102", defaultOpen.copy(tlvStream_opt = Some(TlvStream(Nil, Seq(GenericTlv(UInt64(3), hex"002a"), GenericTlv(UInt64(5), hex"02")))))), + // empty upfront_shutdown_script + unknown odd tlv records: we don't encode the upfront_shutdown_script when a tlv stream is provided + TestCase(defaultEncoded ++ hex"0000 0302002a 050102", defaultOpen.copy(tlvStream_opt = Some(TlvStream(Nil, Seq(GenericTlv(UInt64(3), hex"002a"), GenericTlv(UInt64(5), hex"02"))))), Some(defaultEncoded ++ hex"0302002a 050102")), + // non-empty upfront_shutdown_script + unknown odd tlv records: we don't encode the upfront_shutdown_script when a tlv stream is provided + TestCase(defaultEncoded ++ hex"0002 1234 0303010203", defaultOpen.copy(upfrontShutdownScript = Some(hex"1234"), tlvStream_opt = Some(TlvStream(Nil, Seq(GenericTlv(UInt64(3), hex"010203"))))), Some(defaultEncoded ++ hex"0303010203")) + ) + + for (testCase <- testCases) { + val decoded = openChannelCodec.decode(testCase.encoded.bits).require.value + assert(decoded === testCase.decoded) + val reEncoded = openChannelCodec.encode(decoded).require.bytes + assert(reEncoded === testCase.reEncoded.getOrElse(testCase.encoded)) + } + } + + test("decode invalid open_channel") { + val defaultEncoded = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000100000000000000010000000000000001000000000000000100000000000000010000000100010001031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a00" + val testCases = Seq( + defaultEncoded ++ hex"00", // truncated length + defaultEncoded ++ hex"01", // truncated length + defaultEncoded ++ hex"0004 123456", // truncated script + defaultEncoded ++ hex"0000 02012a", // invalid tlv stream (unknown even record) + defaultEncoded ++ hex"0000 01012a 030201", // invalid tlv stream (truncated) + defaultEncoded ++ hex"02012a", // invalid tlv stream (unknown even record) + defaultEncoded ++ hex"01012a 030201" // invalid tlv stream (truncated) + ) + + for (testCase <- testCases) { + assert(openChannelCodec.decode(testCase.bits).isFailure, testCase.toHex) + } + } + + test("encode/decode accept_channel") { + val defaultAccept = AcceptChannel(ByteVector32.Zeroes, 1 sat, UInt64(1), 1 sat, 1 msat, 1, CltvExpiryDelta(1), 1, publicKey(1), point(2), point(3), point(4), point(5), point(6)) + // Default encoding that completely omits the upfront_shutdown_script (nodes were supposed to encode it only if both + // sides advertised support for option_upfront_shutdown_script). + // To allow extending all messages with TLV streams, the upfront_shutdown_script was made mandatory in https://github.com/lightningnetwork/lightning-rfc/pull/714 + val defaultEncoded = hex"000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000000100000000000000010000000100010001031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d076602531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1fe33703462779ad4aad39514614751a71085f2f10e1c7a593e4e030efb5b8721ce55b0b0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f703f006a18d5653c4edf5391ff23a61f03ff83d237e880ee61187fa9f379a028e0a" + case class TestCase(encoded: ByteVector, decoded: AcceptChannel, reEncoded: Option[ByteVector] = None) + val testCases = Seq( + TestCase(defaultEncoded, defaultAccept, Some(defaultEncoded ++ hex"0000")), // legacy encoding without upfront_shutdown_script + TestCase(defaultEncoded ++ hex"0000", defaultAccept), // empty upfront_shutdown_script + TestCase(defaultEncoded ++ hex"0004 01abcdef", defaultAccept.copy(upfrontShutdownScript = Some(hex"01abcdef"))), // non-empty upfront_shutdown_script + TestCase(defaultEncoded ++ hex"0000 010202a 030102", defaultAccept, Some(defaultEncoded ++ hex"0000")), // empty upfront_shutdown_script + unknown odd tlv records + TestCase(defaultEncoded ++ hex"0002 1234 0303010203", defaultAccept.copy(upfrontShutdownScript = Some(hex"1234")), Some(defaultEncoded ++ hex"0002 1234")) // non-empty upfront_shutdown_script + unknown odd tlv records + ) + + for (testCase <- testCases) { + val decoded = acceptChannelCodec.decode(testCase.encoded.bits).require.value + assert(decoded === testCase.decoded) + val reEncoded = acceptChannelCodec.encode(decoded).require.bytes + assert(reEncoded === testCase.reEncoded.getOrElse(testCase.encoded)) + } + } + test("encode/decode all channel messages") { val open = OpenChannel(randomBytes32, randomBytes32, 3 sat, 4 msat, 5 sat, UInt64(6), 7 sat, 8 msat, 9, CltvExpiryDelta(10), 11, publicKey(1), point(2), point(3), point(4), point(5), point(6), 0.toByte) val accept = AcceptChannel(randomBytes32, 3 sat, UInt64(4), 5 sat, 6 msat, 7, CltvExpiryDelta(8), 9, publicKey(1), point(2), point(3), point(4), point(5), point(6))