Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variable-length onion payloads #976

Merged
merged 28 commits into from
Jul 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2c06d41
Sphinx: typo in computeBlindingFactor
t-bast Apr 26, 2019
314e3fc
Sphinx: implement multi-frame onion proposal
t-bast Apr 29, 2019
ac6ec1a
Sphinx: add tests for multi-frame onions
t-bast Apr 30, 2019
f75ae41
Sphinx: use 5 bits to encode number of frames.
t-bast May 15, 2019
4014183
SphinxSpec: small clean-up
t-bast May 15, 2019
f2fff4f
Features: add multi-frame onion feature flag.
t-bast May 15, 2019
02de66a
SphinxSpec: add official test vector.
t-bast May 22, 2019
7bd8942
Sphinx: use variable-length payloads.
t-bast May 28, 2019
b4cbed5
SphinxSpec: add official variable-length test vector
t-bast Jun 7, 2019
9c42e55
features: rename variable-length onion flag
t-bast Jun 12, 2019
9a434d5
FeaturesSpec: update test name
t-bast Jun 25, 2019
367d6eb
Sphinx: refactor onion objects.
t-bast Jun 28, 2019
97622d4
Update varint codec name
t-bast Jul 1, 2019
22b5faa
Use scodec for onion packet.
t-bast Jul 3, 2019
0f28e8a
Use scodec for onion failure packet.
t-bast Jul 4, 2019
5022847
Address PR comments:
t-bast Jul 5, 2019
b39769e
Create a decode-only codec to extract onion payload length
t-bast Jul 8, 2019
52d7023
Clean-up a few nits:
t-bast Jul 8, 2019
5946b6a
Add function to extract failure code from failure message
t-bast Jul 8, 2019
4c6a05a
Clean-up failureOnionCodec
t-bast Jul 8, 2019
0c13aea
Rename to OnionRouting everywhere.
t-bast Jul 8, 2019
9506dd7
Add error log if onion payload decoder has remaining bytes
t-bast Jul 8, 2019
9fa1dc3
Update onion feature bit.
t-bast Jul 9, 2019
5a1c3b2
Clean-up: sort imports
t-bast Jul 11, 2019
b3232e4
Update test vectors to use big-endian varints
t-bast Jul 15, 2019
358b20a
Sphinx: remove the tlv termination signal.
t-bast Jul 23, 2019
873bb41
Formatting and small fixes
t-bast Jul 23, 2019
40d9b1c
Merge master
t-bast Jul 23, 2019
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
7 changes: 4 additions & 3 deletions eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import java.util.BitSet

import scodec.bits.ByteVector


/**
* Created by PM on 13/02/2017.
*/
Expand All @@ -36,12 +35,13 @@ object Features {
val CHANNEL_RANGE_QUERIES_BIT_MANDATORY = 6
val CHANNEL_RANGE_QUERIES_BIT_OPTIONAL = 7

val VARIABLE_LENGTH_ONION_MANDATORY = 8
val VARIABLE_LENGTH_ONION_OPTIONAL = 9

def hasFeature(features: BitSet, bit: Int): Boolean = features.get(bit)

def hasFeature(features: ByteVector, bit: Int): Boolean = hasFeature(BitSet.valueOf(features.reverse.toArray), bit)


/**
* Check that the features that we understand are correctly specified, and that there are no mandatory features that
* we don't understand (even bits)
Expand All @@ -51,7 +51,8 @@ object Features {
for (i <- 0 until bitset.length() by 2) {
if (bitset.get(i) && !supportedMandatoryFeatures.contains(i)) return false
}
return true

true
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2176,8 +2176,10 @@ class Channel(val nodeParams: NodeParams, val wallet: EclairWallet, remoteNodeId

// let's now fail all pending htlc for which we are the final payee
val htlcsToFail = commitments1.remoteCommit.spec.htlcs.collect {
case DirectedHtlc(OUT, add) if Sphinx.parsePacket(nodeParams.privateKey, add.paymentHash, add.onionRoutingPacket)
.map(_.nextPacket.isLastPacket).getOrElse(true) => add // we also fail htlcs which onion we can't decode (message won't be precise)
case DirectedHtlc(OUT, add) if Sphinx.PaymentPacket.peel(nodeParams.privateKey, add.paymentHash, add.onionRoutingPacket).fold(
_ => true, // we also fail htlcs which onion we can't decode (message won't be precise)
p => p.isLastPacket
) => add
}

log.debug(s"failing htlcs=${htlcsToFail.map(Commitments.msg2String(_)).mkString(",")}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ import java.util.UUID
import akka.actor.ActorRef
import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.bitcoin.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, Transaction}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions.CommitTx
import fr.acinq.eclair.wire.{AcceptChannel, ChannelAnnouncement, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OpenChannel, Shutdown, UpdateAddHtlc}
import fr.acinq.eclair.wire.{AcceptChannel, ChannelAnnouncement, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingLocked, FundingSigned, Init, OnionRoutingPacket, OpenChannel, Shutdown, UpdateAddHtlc}
import fr.acinq.eclair.{ShortChannelId, UInt64}
import scodec.bits.{BitVector, ByteVector}

Expand Down Expand Up @@ -107,7 +106,7 @@ case class BITCOIN_PARENT_TX_CONFIRMED(childTx: Transaction) extends BitcoinEven
*/

sealed trait Command
final case class CMD_ADD_HTLC(amountMsat: Long, paymentHash: ByteVector32, cltvExpiry: Long, onion: ByteVector = Sphinx.LAST_PACKET.serialize, upstream: Either[UUID, UpdateAddHtlc], commit: Boolean = false, previousFailures: Seq[AddHtlcFailed] = Seq.empty) extends Command
final case class CMD_ADD_HTLC(amountMsat: Long, paymentHash: ByteVector32, cltvExpiry: Long, onion: OnionRoutingPacket, upstream: Either[UUID, UpdateAddHtlc], commit: Boolean = false, previousFailures: Seq[AddHtlcFailed] = Seq.empty) extends Command
final case class CMD_FULFILL_HTLC(id: Long, r: ByteVector32, commit: Boolean = false) extends Command
final case class CMD_FAIL_HTLC(id: Long, reason: Either[ByteVector, FailureMessage], commit: Boolean = false) extends Command
final case class CMD_FAIL_MALFORMED_HTLC(id: Long, onionHash: ByteVector32, failureCode: Int, commit: Boolean = false) extends Command
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import fr.acinq.eclair.transactions._
import fr.acinq.eclair.wire._
import fr.acinq.eclair.{Globals, UInt64}

import scala.util.{Failure, Success}

// @formatter:off
case class LocalChanges(proposed: List[UpdateMessage], signed: List[UpdateMessage], acked: List[UpdateMessage]) {
def all: List[UpdateMessage] = proposed ++ signed ++ acked
Expand Down Expand Up @@ -248,16 +246,16 @@ object Commitments {
throw UnknownHtlcId(commitments.channelId, cmd.id)
case Some(htlc) =>
// we need the shared secret to build the error packet
Sphinx.parsePacket(nodeSecret, htlc.paymentHash, htlc.onionRoutingPacket).map(_.sharedSecret) match {
case Success(sharedSecret) =>
Sphinx.PaymentPacket.peel(nodeSecret, htlc.paymentHash, htlc.onionRoutingPacket) match {
case Right(Sphinx.DecryptedPacket(_, _, sharedSecret)) =>
val reason = cmd.reason match {
case Left(forwarded) => Sphinx.forwardErrorPacket(forwarded, sharedSecret)
case Right(failure) => Sphinx.createErrorPacket(sharedSecret, failure)
case Left(forwarded) => Sphinx.FailurePacket.wrap(forwarded, sharedSecret)
case Right(failure) => Sphinx.FailurePacket.create(sharedSecret, failure)
}
val fail = UpdateFailHtlc(commitments.channelId, cmd.id, reason)
val commitments1 = addLocalProposal(commitments, fail)
(commitments1, fail)
case Failure(_) => throw CannotExtractSharedSecret(commitments.channelId, htlc)
case Left(_) => throw CannotExtractSharedSecret(commitments.channelId, htlc)
}
case None => throw UnknownHtlcId(commitments.channelId, cmd.id)
}
Expand Down
59 changes: 59 additions & 0 deletions eclair-core/src/main/scala/fr/acinq/eclair/crypto/Mac.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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.crypto

import fr.acinq.bitcoin.ByteVector32
import org.spongycastle.crypto.digests.SHA256Digest
import org.spongycastle.crypto.macs.HMac
import org.spongycastle.crypto.params.KeyParameter
import scodec.bits.ByteVector

/**
* Created by t-bast on 04/07/19.
*/

/**
* Create and verify message authentication codes.
*/
trait Mac32 {

def mac(message: ByteVector): ByteVector32

def verify(mac: ByteVector32, message: ByteVector): Boolean

}

case class Hmac256(key: ByteVector) extends Mac32 {

override def mac(message: ByteVector): ByteVector32 = Mac32.hmac256(key, message)

override def verify(mac: ByteVector32, message: ByteVector): Boolean = this.mac(message) === mac

}

object Mac32 {

def hmac256(key: ByteVector, message: ByteVector): ByteVector32 = {
val mac = new HMac(new SHA256Digest())
mac.init(new KeyParameter(key.toArray))
mac.update(message.toArray, 0, message.length.toInt)
val output = new Array[Byte](32)
mac.doFinal(output, 0)
ByteVector32(ByteVector.view(output))
}

}
Loading