Skip to content
Draft
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
8 changes: 5 additions & 3 deletions eclair-core/src/main/scala/fr/acinq/eclair/UInt64.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@
package fr.acinq.eclair

import com.google.common.primitives.UnsignedLongs
import scodec.bits.ByteVector
import scodec.bits.HexStringSyntax
import scodec.bits.{ByteVector, HexStringSyntax}

case class UInt64(private val underlying: Long) extends Ordered[UInt64] {

override def compare(o: UInt64): Int = UnsignedLongs.compare(underlying, o.underlying)
private def compare(other: MilliSatoshi): Int = other.toLong match {
case l if l < 0 => 1 // if @param 'other' is negative then is always smaller than 'this'
case l if l < 0 => 1 // if @param 'other' is negative then is always smaller than 'this'
case _ => compare(UInt64(other.toLong)) // we must do an unsigned comparison here because the uint64 can exceed the capacity of MilliSatoshi class
}

Expand All @@ -33,6 +32,9 @@ case class UInt64(private val underlying: Long) extends Ordered[UInt64] {
def <=(other: MilliSatoshi): Boolean = compare(other) <= 0
def >=(other: MilliSatoshi): Boolean = compare(other) >= 0

def +(other: Int) = UInt64(underlying + other)
def +(other: Long) = UInt64(underlying + other)

def toByteVector: ByteVector = ByteVector.fromLong(underlying)

def toBigInt: BigInt = (BigInt(underlying >>> 1) << 1) + (underlying & 1)
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,54 @@ object OfferCodecs {
.typecase(UInt64(240), signature)
).complete)

private val proofSig: Codec[ProofSignature] = tlvField(bytes64)
private val preimage: Codec[InvoicePreimage] = tlvField(bytes32)
private val omitted: Codec[OmittedTlvs] = tlvField(list(varint))
private val missingHashes: Codec[MissingHashes] = tlvField(list(bytes32))
private val leafHashes: Codec[LeafHashes] = tlvField(list(bytes32))
private val proofNote: Codec[ProofNote] = tlvField(utf8)

val payerProofTlvCodec: DiscriminatorCodec[PayerProofTlv, UInt64] = discriminated[PayerProofTlv].by(varint)
// Invoice part that must be copy-pasted from above
.typecase(UInt64(0), invoiceRequestMetadata)
.typecase(UInt64(2), offerChains)
.typecase(UInt64(4), offerMetadata)
.typecase(UInt64(6), offerCurrency)
.typecase(UInt64(8), offerAmount)
.typecase(UInt64(10), offerDescription)
.typecase(UInt64(12), offerFeatures)
.typecase(UInt64(14), offerAbsoluteExpiry)
.typecase(UInt64(16), offerPaths)
.typecase(UInt64(18), offerIssuer)
.typecase(UInt64(20), offerQuantityMax)
.typecase(UInt64(22), offerNodeId)
.typecase(UInt64(80), invoiceRequestChain)
.typecase(UInt64(82), invoiceRequestAmount)
.typecase(UInt64(84), invoiceRequestFeatures)
.typecase(UInt64(86), invoiceRequestQuantity)
.typecase(UInt64(88), invoiceRequestPayerId)
.typecase(UInt64(89), invoiceRequestPayerNote)
.typecase(UInt64(160), invoicePaths)
.typecase(UInt64(161), invoiceAccountable)
.typecase(UInt64(162), invoiceBlindedPay)
.typecase(UInt64(164), invoiceCreatedAt)
.typecase(UInt64(166), invoiceRelativeExpiry)
.typecase(UInt64(168), invoicePaymentHash)
.typecase(UInt64(170), invoiceAmount)
.typecase(UInt64(172), invoiceFallbacks)
.typecase(UInt64(174), invoiceFeatures)
.typecase(UInt64(176), invoiceNodeId)
.typecase(UInt64(240), signature)
// Payer proof part
.typecase(UInt64(241), proofSig)
.typecase(UInt64(1001), preimage)
.typecase(UInt64(1002), omitted)
.typecase(UInt64(1003), missingHashes)
.typecase(UInt64(1004), leafHashes)
.typecase(UInt64(1005), proofNote)

val payerProofCodec: Codec[TlvStream[PayerProofTlv]] = catchAllCodec(TlvCodecs.tlvStream[PayerProofTlv](payerProofTlvCodec).complete)

private val invoiceErrorTlvCodec: Codec[TlvStream[InvoiceErrorTlv]] = catchAllCodec(TlvCodecs.tlvStream[InvoiceErrorTlv](discriminated[InvoiceErrorTlv].by(varint)
.typecase(UInt64(1), tlvField(tu64overflow.as[ErroneousField]))
.typecase(UInt64(3), tlvField(bytes.as[SuggestedValue]))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ object OfferTypes {

sealed trait Bolt12Tlv extends Tlv

sealed trait InvoiceTlv extends Bolt12Tlv
sealed trait PayerProofTlv extends Bolt12Tlv

sealed trait InvoiceTlv extends PayerProofTlv

sealed trait InvoiceRequestTlv extends InvoiceTlv

Expand Down Expand Up @@ -220,7 +222,25 @@ object OfferTypes {
* Signature from the sender when used in an invoice request.
* Signature from the recipient when used in an invoice.
*/
case class Signature(signature: ByteVector64) extends InvoiceRequestTlv with InvoiceTlv
case class Signature(signature: ByteVector64) extends InvoiceRequestTlv with InvoiceTlv with PayerProofTlv

/** The payer signs the payer proof. */
case class ProofSignature(signature: ByteVector64) extends PayerProofTlv

/** Preimage matching the invoice's [[InvoicePaymentHash]]. */
case class InvoicePreimage(preimage: ByteVector32) extends PayerProofTlv

/** The payer may omit some invoice TLVs for privacy reasons. */
case class OmittedTlvs(missing: List[UInt64]) extends PayerProofTlv

/** The payer must include the missing parts of the invoice TLV merkle tree. */
case class MissingHashes(missing: List[ByteVector32]) extends PayerProofTlv

/** The payer must include a nonce hash for each invoice TLV included in the payer proof. */
case class LeafHashes(hashes: List[ByteVector32]) extends PayerProofTlv

/** An optional challenge may be included in the payer proof. */
case class ProofNote(note: String) extends PayerProofTlv

private def isOfferTlv(tlv: GenericTlv): Boolean =
// Offer TLVs are in the range [1, 79] or [1000000000, 1999999999].
Expand Down Expand Up @@ -467,9 +487,12 @@ object OfferTypes {
// Encoding tlvs is always safe, unless we have a bug in our codecs, so we can call `.require` here.
val encoded = codec.encode(tlvs).require
// Decoding tlvs that we just encoded is safe as well.
// This encoding/decoding step ensures that the resulting tlvs are ordered.
// This encoding/decoding step ensures that the resulting tlvs are ordered and that we combine known and unknown TLVs.
val genericTlvs = vector(genericTlv).decode(encoded).require.value
val firstTlv = genericTlvs.minBy(_.tag)
// The first TLV in the stream is used as a hashing nonce for all leaves of the tree, to ensure that
// their values cannot be brute-forced when omitted in payer proofs. For Bolt12 invoices and invoice
// requests this is invreq_metadata (tag 0); for payer proofs it's the lowest-tag disclosed TLV.
val firstTlv = genericTlvs.headOption.getOrElse(GenericTlv(UInt64(0), ByteVector.empty))
val nonceKey = ByteVector("LnNonce".getBytes) ++ genericTlv.encode(firstTlv).require.bytes

def previousPowerOfTwo(n: Int): Int = {
Expand Down Expand Up @@ -499,7 +522,7 @@ object OfferTypes {
merkleTree(0, genericTlvs.length)
}

private def hash(tag: ByteVector, msg: ByteVector): ByteVector32 = {
def hash(tag: ByteVector, msg: ByteVector): ByteVector32 = {
val tagHash = Crypto.sha256(tag)
Crypto.sha256(tagHash ++ tagHash ++ msg)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ object OnionRoutingCodecs {
def failureMessage: FailureMessage = InvalidOnionPayload(tag, 0)
}
case class MissingRequiredTlv(tag: UInt64) extends InvalidTlvPayload
case class InvalidTlvValue(tag: UInt64) extends InvalidTlvPayload
case class ForbiddenTlv(tag: UInt64) extends InvalidTlvPayload
// @formatter:on

Expand Down
Loading
Loading