Skip to content

Commit

Permalink
Variable-length onion payloads (#976)
Browse files Browse the repository at this point in the history
Add support for variable-length onion payloads at the Sphinx (cryptographic) layer.
This is currently unused as we keep using the legacy format by default (this will be changed in a later commit).
This commit also refactors quite heavily the Sphinx file.
  • Loading branch information
t-bast authored Jul 23, 2019
1 parent 189b11e commit 93d9369
Show file tree
Hide file tree
Showing 44 changed files with 1,195 additions and 694 deletions.
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

0 comments on commit 93d9369

Please sign in to comment.