Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class SqliteWalletDb(sqlite: Connection) extends WalletDb {
object SqliteWalletDb {

import fr.acinq.eclair.wire.ChannelCodecs._
import fr.acinq.eclair.wire.LightningMessageCodecs._
import fr.acinq.eclair.wire.CommonCodecs._
import scodec.Codec
import scodec.bits.BitVector
import scodec.codecs._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package fr.acinq.eclair.crypto

import fr.acinq.bitcoin._
import fr.acinq.eclair.wire.LightningMessageCodecs
import fr.acinq.eclair.wire.CommonCodecs
import scodec.Codec

import scala.annotation.tailrec
Expand Down Expand Up @@ -117,7 +117,7 @@ object ShaChain {
import scodec.codecs._

// codec for a single map entry (i.e. Vector[Boolean] -> ByteVector
val entryCodec = vectorOfN(uint16, bool) ~ variableSizeBytes(uint16, LightningMessageCodecs.bytes32)
val entryCodec = vectorOfN(uint16, bool) ~ variableSizeBytes(uint16, CommonCodecs.bytes32)

// codec for a Map[Vector[Boolean], ByteVector]: write all k -> v pairs using the codec defined above
val mapCodec: Codec[Map[Vector[Boolean], ByteVector32]] = Codec[Map[Vector[Boolean], ByteVector32]](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import SqliteUtils.ExtendedResultSet._
}

override def addOrUpdatePeer(nodeId: Crypto.PublicKey, nodeaddress: NodeAddress): Unit = {
val data = LightningMessageCodecs.nodeaddress.encode(nodeaddress).require.toByteArray
val data = CommonCodecs.nodeaddress.encode(nodeaddress).require.toByteArray
using(sqlite.prepareStatement("UPDATE peers SET data=? WHERE node_id=?")) { update =>
update.setBytes(1, data)
update.setBytes(2, nodeId.value.toArray)
Expand All @@ -65,7 +65,7 @@ import SqliteUtils.ExtendedResultSet._
var m: Map[PublicKey, NodeAddress] = Map()
while (rs.next()) {
val nodeid = PublicKey(rs.getByteVector("node_id"))
val nodeaddress = LightningMessageCodecs.nodeaddress.decode(BitVector(rs.getBytes("data"))).require.value
val nodeaddress = CommonCodecs.nodeaddress.decode(BitVector(rs.getBytes("data"))).require.value
m += (nodeid -> nodeaddress)
}
m
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package fr.acinq.eclair.payment

import java.math.BigInteger

import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.bitcoin.{MilliSatoshi, _}
import fr.acinq.eclair.ShortChannelId
Expand Down Expand Up @@ -303,7 +301,7 @@ object PaymentRequest {

object Codecs {

import fr.acinq.eclair.wire.LightningMessageCodecs._
import fr.acinq.eclair.wire.CommonCodecs._
import scodec.bits.BitVector
import scodec.codecs._
import scodec.{Attempt, Codec, DecodeResult}
Expand Down
42 changes: 21 additions & 21 deletions eclair-core/src/main/scala/fr/acinq/eclair/wire/ChannelCodecs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import fr.acinq.eclair.crypto.ShaChain
import fr.acinq.eclair.payment.{Local, Origin, Relayed}
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire.CommonCodecs._
import fr.acinq.eclair.wire.LightningMessageCodecs._
import grizzled.slf4j.Logging
import scodec.bits.BitVector
Expand All @@ -35,7 +36,6 @@ import scodec.{Attempt, Codec}
import scala.compat.Platform
import scala.concurrent.duration._


/**
* Created by PM on 02/06/2017.
*/
Expand All @@ -53,10 +53,10 @@ object ChannelCodecs extends Logging {
val localParamsCodec: Codec[LocalParams] = (
("nodeId" | publicKey) ::
("channelPath" | keyPathCodec) ::
("dustLimitSatoshis" | uint64) ::
("maxHtlcValueInFlightMsat" | uint64ex) ::
("channelReserveSatoshis" | uint64) ::
("htlcMinimumMsat" | uint64) ::
("dustLimitSatoshis" | uint64overflow) ::
("maxHtlcValueInFlightMsat" | uint64) ::
("channelReserveSatoshis" | uint64overflow) ::
("htlcMinimumMsat" | uint64overflow) ::
("toSelfDelay" | uint16) ::
("maxAcceptedHtlcs" | uint16) ::
("isFunder" | bool) ::
Expand All @@ -66,10 +66,10 @@ object ChannelCodecs extends Logging {

val remoteParamsCodec: Codec[RemoteParams] = (
("nodeId" | publicKey) ::
("dustLimitSatoshis" | uint64) ::
("maxHtlcValueInFlightMsat" | uint64ex) ::
("channelReserveSatoshis" | uint64) ::
("htlcMinimumMsat" | uint64) ::
("dustLimitSatoshis" | uint64overflow) ::
("maxHtlcValueInFlightMsat" | uint64) ::
("channelReserveSatoshis" | uint64overflow) ::
("htlcMinimumMsat" | uint64overflow) ::
("toSelfDelay" | uint16) ::
("maxAcceptedHtlcs" | uint16) ::
("fundingPubKey" | publicKey) ::
Expand Down Expand Up @@ -97,14 +97,14 @@ object ChannelCodecs extends Logging {
val commitmentSpecCodec: Codec[CommitmentSpec] = (
("htlcs" | setCodec(htlcCodec)) ::
("feeratePerKw" | uint32) ::
("toLocalMsat" | uint64) ::
("toRemoteMsat" | uint64)).as[CommitmentSpec]
("toLocalMsat" | uint64overflow) ::
("toRemoteMsat" | uint64overflow)).as[CommitmentSpec]

def outPointCodec: Codec[OutPoint] = variableSizeBytes(uint16, bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d)))
val outPointCodec: Codec[OutPoint] = variableSizeBytes(uint16, bytes.xmap(d => OutPoint.read(d.toArray), d => OutPoint.write(d)))

def txOutCodec: Codec[TxOut] = variableSizeBytes(uint16, bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d)))
val txOutCodec: Codec[TxOut] = variableSizeBytes(uint16, bytes.xmap(d => TxOut.read(d.toArray), d => TxOut.write(d)))

def txCodec: Codec[Transaction] = variableSizeBytes(uint16, bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)))
val txCodec: Codec[Transaction] = variableSizeBytes(uint16, bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)))

val inputInfoCodec: Codec[InputInfo] = (
("outPoint" | outPointCodec) ::
Expand Down Expand Up @@ -142,12 +142,12 @@ object ChannelCodecs extends Logging {
("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs]

val localCommitCodec: Codec[LocalCommit] = (
("index" | uint64) ::
("index" | uint64overflow) ::
("spec" | commitmentSpecCodec) ::
("publishableTxs" | publishableTxsCodec)).as[LocalCommit]

val remoteCommitCodec: Codec[RemoteCommit] = (
("index" | uint64) ::
("index" | uint64overflow) ::
("spec" | commitmentSpecCodec) ::
("txid" | bytes32) ::
("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit]
Expand All @@ -167,7 +167,7 @@ object ChannelCodecs extends Logging {
val waitingForRevocationCodec: Codec[WaitingForRevocation] = (
("nextRemoteCommit" | remoteCommitCodec) ::
("sent" | commitSigCodec) ::
("sentAfterLocalCommitIndex" | uint64) ::
("sentAfterLocalCommitIndex" | uint64overflow) ::
("reSignAsap" | bool)).as[WaitingForRevocation]

val localCodec: Codec[Local] = (
Expand All @@ -178,8 +178,8 @@ object ChannelCodecs extends Logging {
val relayedCodec: Codec[Relayed] = (
("originChannelId" | bytes32) ::
("originHtlcId" | int64) ::
("amountMsatIn" | uint64) ::
("amountMsatOut" | uint64)).as[Relayed]
("amountMsatIn" | uint64overflow) ::
("amountMsatOut" | uint64overflow)).as[Relayed]

// this is for backward compatibility to handle legacy payments that didn't have identifiers
val UNKNOWN_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000")
Expand Down Expand Up @@ -211,8 +211,8 @@ object ChannelCodecs extends Logging {
("remoteCommit" | remoteCommitCodec) ::
("localChanges" | localChangesCodec) ::
("remoteChanges" | remoteChangesCodec) ::
("localNextHtlcId" | uint64) ::
("remoteNextHtlcId" | uint64) ::
("localNextHtlcId" | uint64overflow) ::
("remoteNextHtlcId" | uint64overflow) ::
("originChannels" | originsMapCodec) ::
("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, publicKey)) ::
("commitInput" | inputInfoCodec) ::
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
package fr.acinq.eclair.wire

import fr.acinq.eclair.channel.{CMD_FAIL_HTLC, CMD_FAIL_MALFORMED_HTLC, CMD_FULFILL_HTLC, Command}
import fr.acinq.eclair.wire.CommonCodecs._
import fr.acinq.eclair.wire.FailureMessageCodecs.failureMessageCodec
import fr.acinq.eclair.wire.LightningMessageCodecs._
import scodec.Codec
import scodec.codecs._

Expand Down
128 changes: 128 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/wire/CommonCodecs.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* 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 java.net.{Inet4Address, Inet6Address, InetAddress}

import fr.acinq.bitcoin.{ByteVector32, ByteVector64}
import fr.acinq.bitcoin.Crypto.{PrivateKey, PublicKey}
import fr.acinq.eclair.{ShortChannelId, UInt64}
import org.apache.commons.codec.binary.Base32
import scodec.{Attempt, Codec, DecodeResult, Err, SizeBound}
import scodec.bits.{BitVector, ByteVector}
import scodec.codecs._

import scala.util.Try

/**
* Created by t-bast on 20/06/2019.
*/

object CommonCodecs {

/**
* Discriminator codec with a default fallback codec (of the same type).
*/
def discriminatorWithDefault[A](discriminator: Codec[A], fallback: Codec[A]): Codec[A] = new Codec[A] {
def sizeBound: SizeBound = discriminator.sizeBound | fallback.sizeBound

def encode(e: A): Attempt[BitVector] = discriminator.encode(e).recoverWith { case _ => fallback.encode(e) }

def decode(b: BitVector): Attempt[DecodeResult[A]] = discriminator.decode(b).recoverWith {
case _: KnownDiscriminatorType[_]#UnknownDiscriminator => fallback.decode(b)
}
}

// this codec can be safely used for values < 2^63 and will fail otherwise
// (for something smarter see https://github.com/yzernik/bitcoin-scodec/blob/master/src/main/scala/io/github/yzernik/bitcoinscodec/structures/UInt64.scala)
val uint64overflow: Codec[Long] = int64.narrow(l => if (l >= 0) Attempt.Successful(l) else Attempt.failure(Err(s"overflow for value $l")), l => l)

val uint64: Codec[UInt64] = bytes(8).xmap(b => UInt64(b), a => a.toByteVector.padLeft(8))

val uint64L: Codec[UInt64] = bytes(8).xmap(b => UInt64(b.reverse), a => a.toByteVector.padLeft(8).reverse)
Comment thread
pm47 marked this conversation as resolved.

/**
* We impose a minimal encoding on varint values to ensure that signed hashes can be reproduced easily.
* If a value could be encoded with less bytes, it's considered invalid and results in a failed decoding attempt.
*
* @param codec the integer codec (depends on the value).
* @param min the minimal value that should be encoded.
*/
def uint64min(codec: Codec[UInt64], min: UInt64): Codec[UInt64] = codec.exmap({
case i if i < min => Attempt.failure(Err("varint was not minimally encoded"))
case i => Attempt.successful(i)
}, Attempt.successful)

// Bitcoin-style varint codec (CompactSize).
// See https://bitcoin.org/en/developer-reference#compactsize-unsigned-integers for reference.
val varint: Codec[UInt64] = discriminatorWithDefault(
discriminated[UInt64].by(uint8L)
.\(0xff) { case i if i >= UInt64(0x100000000L) => i }(uint64min(uint64L, UInt64(0x100000000L)))
.\(0xfe) { case i if i >= UInt64(0x10000) => i }(uint64min(uint32L.xmap(UInt64(_), _.toBigInt.toLong), UInt64(0x10000)))
.\(0xfd) { case i if i >= UInt64(0xfd) => i }(uint64min(uint16L.xmap(UInt64(_), _.toBigInt.toInt), UInt64(0xfd))),
uint8L.xmap(UInt64(_), _.toBigInt.toInt)
)

// This codec can be safely used for values < 2^63 and will fail otherwise.
// It is useful in combination with variableSizeBytesLong to encode/decode TLV lengths because those will always be < 2^63.
val varintoverflow: Codec[Long] = varint.narrow(l => if (l <= UInt64(Long.MaxValue)) Attempt.successful(l.toBigInt.toLong) else Attempt.failure(Err(s"overflow for value $l")), l => UInt64(l))

val bytes32: Codec[ByteVector32] = limitedSizeBytes(32, bytesStrict(32).xmap(d => ByteVector32(d), d => d.bytes))

val bytes64: Codec[ByteVector64] = limitedSizeBytes(64, bytesStrict(64).xmap(d => ByteVector64(d), d => d.bytes))

val sha256: Codec[ByteVector32] = bytes32

val varsizebinarydata: Codec[ByteVector] = variableSizeBytes(uint16, bytes)

val listofsignatures: Codec[List[ByteVector64]] = listOfN(uint16, bytes64)

val ipv4address: Codec[Inet4Address] = bytes(4).xmap(b => InetAddress.getByAddress(b.toArray).asInstanceOf[Inet4Address], a => ByteVector(a.getAddress))

val ipv6address: Codec[Inet6Address] = bytes(16).exmap(b => Attempt.fromTry(Try(Inet6Address.getByAddress(null, b.toArray, null))), a => Attempt.fromTry(Try(ByteVector(a.getAddress))))
Comment thread
pm47 marked this conversation as resolved.

def base32(size: Int): Codec[String] = bytes(size).xmap(b => new Base32().encodeAsString(b.toArray).toLowerCase, a => ByteVector(new Base32().decode(a.toUpperCase())))

val nodeaddress: Codec[NodeAddress] =
discriminated[NodeAddress].by(uint8)
.typecase(1, (ipv4address :: uint16).as[IPv4])
.typecase(2, (ipv6address :: uint16).as[IPv6])
.typecase(3, (base32(10) :: uint16).as[Tor2])
.typecase(4, (base32(35) :: uint16).as[Tor3])

// this one is a bit different from most other codecs: the first 'len' element is *not* the number of items
// in the list but rather the number of bytes of the encoded list. The rationale is once we've read this
// number of bytes we can just skip to the next field
val listofnodeaddresses: Codec[List[NodeAddress]] = variableSizeBytes(uint16, list(nodeaddress))

val shortchannelid: Codec[ShortChannelId] = int64.xmap(l => ShortChannelId(l), s => s.toLong)

val privateKey: Codec[PrivateKey] = Codec[PrivateKey](
(priv: PrivateKey) => bytes(32).encode(priv.value),
(wire: BitVector) => bytes(32).decode(wire).map(_.map(b => PrivateKey(b)))
)

val publicKey: Codec[PublicKey] = Codec[PublicKey](
(pub: PublicKey) => bytes(33).encode(pub.value),
(wire: BitVector) => bytes(33).decode(wire).map(_.map(b => PublicKey(b)))
)

val rgb: Codec[Color] = bytes(3).xmap(buf => Color(buf(0), buf(1), buf(2)), t => ByteVector(t.r, t.g, t.b))

def zeropaddedstring(size: Int): Codec[String] = fixedSizeBytes(32, utf8).xmap(s => s.takeWhile(_ != '\u0000'), s => s)

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
package fr.acinq.eclair.wire

import fr.acinq.bitcoin.ByteVector32
import fr.acinq.eclair.wire.LightningMessageCodecs.{bytes32, channelUpdateCodec, uint64}
import fr.acinq.eclair.wire.CommonCodecs.{sha256, uint64overflow}
import fr.acinq.eclair.wire.LightningMessageCodecs.channelUpdateCodec
import scodec.codecs._
import scodec.{Attempt, Codec}
import scodec.Attempt

/**
* see https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md
Expand Down Expand Up @@ -63,8 +64,6 @@ object FailureMessageCodecs {
val NODE = 0x2000
val UPDATE = 0x1000

val sha256Codec: Codec[ByteVector32] = ("sha256Codec" | bytes32)

val channelUpdateCodecWithType = LightningMessageCodecs.lightningMessageCodec.narrow[ChannelUpdate](f => Attempt.successful(f.asInstanceOf[ChannelUpdate]), g => g)

// NB: for historical reasons some implementations were including/ommitting the message type (258 for ChannelUpdate)
Expand All @@ -76,22 +75,22 @@ object FailureMessageCodecs {
.typecase(NODE | 2, provide(TemporaryNodeFailure))
.typecase(PERM | 2, provide(PermanentNodeFailure))
.typecase(PERM | NODE | 3, provide(RequiredNodeFeatureMissing))
.typecase(BADONION | PERM | 4, sha256Codec.as[InvalidOnionVersion])
.typecase(BADONION | PERM | 5, sha256Codec.as[InvalidOnionHmac])
.typecase(BADONION | PERM | 6, sha256Codec.as[InvalidOnionKey])
.typecase(UPDATE | 7, (("channelUpdate" | channelUpdateWithLengthCodec)).as[TemporaryChannelFailure])
.typecase(BADONION | PERM | 4, sha256.as[InvalidOnionVersion])
.typecase(BADONION | PERM | 5, sha256.as[InvalidOnionHmac])
.typecase(BADONION | PERM | 6, sha256.as[InvalidOnionKey])
.typecase(UPDATE | 7, ("channelUpdate" | channelUpdateWithLengthCodec).as[TemporaryChannelFailure])
.typecase(PERM | 8, provide(PermanentChannelFailure))
.typecase(PERM | 9, provide(RequiredChannelFeatureMissing))
.typecase(PERM | 10, provide(UnknownNextPeer))
.typecase(UPDATE | 11, (("amountMsat" | uint64) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[AmountBelowMinimum])
.typecase(UPDATE | 12, (("amountMsat" | uint64) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[FeeInsufficient])
.typecase(UPDATE | 11, (("amountMsat" | uint64overflow) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[AmountBelowMinimum])
.typecase(UPDATE | 12, (("amountMsat" | uint64overflow) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[FeeInsufficient])
.typecase(UPDATE | 13, (("expiry" | uint32) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[IncorrectCltvExpiry])
.typecase(UPDATE | 14, (("channelUpdate" | channelUpdateWithLengthCodec)).as[ExpiryTooSoon])
.typecase(UPDATE | 14, ("channelUpdate" | channelUpdateWithLengthCodec).as[ExpiryTooSoon])
.typecase(UPDATE | 20, (("messageFlags" | byte) :: ("channelFlags" | byte) :: ("channelUpdate" | channelUpdateWithLengthCodec)).as[ChannelDisabled])
.typecase(PERM | 15, (("amountMsat" | withDefaultValue(optional(bitsRemaining, uint64), 0L))).as[IncorrectOrUnknownPaymentDetails])
.typecase(PERM | 15, ("amountMsat" | withDefaultValue(optional(bitsRemaining, uint64overflow), 0L)).as[IncorrectOrUnknownPaymentDetails])
.typecase(PERM | 16, provide(IncorrectPaymentAmount))
.typecase(17, provide(FinalExpiryTooSoon))
.typecase(18, (("expiry" | uint32)).as[FinalIncorrectCltvExpiry])
.typecase(19, (("amountMsat" | uint64)).as[FinalIncorrectHtlcAmount])
.typecase(18, ("expiry" | uint32).as[FinalIncorrectCltvExpiry])
.typecase(19, ("amountMsat" | uint64overflow).as[FinalIncorrectHtlcAmount])
.typecase(21, provide(ExpiryTooFar))
}
Loading