Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) ::
Expand All @@ -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) ::
Expand All @@ -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) ::
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
36 changes: 36 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/wire/OpenTlv.scala
Original file line number Diff line number Diff line change
@@ -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])
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down