All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.fr.acinq.lightning.wire.MessageOnion.kt Maven / Gradle / Ivy

There is a newer version: 1.8.4
Show newest version
package fr.acinq.lightning.wire

import fr.acinq.bitcoin.ByteVector
import fr.acinq.bitcoin.PublicKey
import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.bitcoin.io.ByteArrayOutput
import fr.acinq.bitcoin.io.Input
import fr.acinq.bitcoin.io.Output
import fr.acinq.lightning.crypto.RouteBlinding

sealed class OnionMessagePayloadTlv : Tlv {
    /**
     * Onion messages may provide a reply path, allowing the recipient to send a message back to the original sender.
     * The reply path uses route blinding, which ensures that the sender doesn't leak its identity to the recipient.
     */
    data class ReplyPath(val blindedRoute: RouteBlinding.BlindedRoute) : OnionMessagePayloadTlv() {
        override val tag: Long get() = ReplyPath.tag
        override fun write(out: Output) {
            LightningCodecs.writeEncodedNodeId(blindedRoute.introductionNodeId, out)
            LightningCodecs.writeBytes(blindedRoute.blindingKey.value, out)
            LightningCodecs.writeByte(blindedRoute.blindedNodes.size, out)
            for (hop in blindedRoute.blindedNodes) {
                LightningCodecs.writeBytes(hop.blindedPublicKey.value, out)
                LightningCodecs.writeU16(hop.encryptedPayload.size(), out)
                LightningCodecs.writeBytes(hop.encryptedPayload, out)
            }
        }

        companion object : TlvValueReader {
            const val tag: Long = 2
            override fun read(input: Input): ReplyPath {
                val firstNodeId = LightningCodecs.encodedNodeId(input)
                val blinding = PublicKey(LightningCodecs.bytes(input, 33))
                val numHops = LightningCodecs.byte(input)
                val path = (0 until numHops).map {
                    val blindedPublicKey = PublicKey(LightningCodecs.bytes(input, 33))
                    val encryptedPayload = ByteVector(LightningCodecs.bytes(input, LightningCodecs.u16(input)))
                    RouteBlinding.BlindedNode(blindedPublicKey, encryptedPayload)
                }
                return ReplyPath(RouteBlinding.BlindedRoute(firstNodeId, blinding, path))
            }
        }
    }

    /**
     * Onion messages always use route blinding, even in the forward direction.
     * This ensures that intermediate nodes can't know whether they're forwarding a message or its reply.
     * The sender must provide some encrypted data for each intermediate node which lets them locate the next node.
     */
    data class EncryptedData(val data: ByteVector) : OnionMessagePayloadTlv() {
        override val tag: Long get() = EncryptedData.tag
        override fun write(out: Output) = LightningCodecs.writeBytes(data, out)

        companion object : TlvValueReader {
            const val tag: Long = 4
            override fun read(input: Input): EncryptedData =
                EncryptedData(ByteVector(LightningCodecs.bytes(input, input.availableBytes)))
        }
    }

    /**
     * In order to pay a Bolt 12 offer, we must send an onion message to request an invoice corresponding to that offer.
     * The creator of the offer will send us an invoice back through our blinded reply path.
     */
    data class InvoiceRequest(val tlvs: TlvStream) : OnionMessagePayloadTlv() {
        override val tag: Long get() = InvoiceRequest.tag
        override fun write(out: Output) = OfferTypes.InvoiceRequest.tlvSerializer.write(tlvs, out)

        companion object : TlvValueReader {
            const val tag: Long = 64

            override fun read(input: Input): InvoiceRequest =
                InvoiceRequest(OfferTypes.InvoiceRequest.tlvSerializer.read(input))
        }
    }

    /**
     * When receiving an invoice request, we must send an onion message back containing an invoice corresponding to the
     * requested offer (if it was an offer we published).
     */
    data class Invoice(val tlvs: TlvStream) : OnionMessagePayloadTlv() {
        override val tag: Long get() = Invoice.tag
        override fun write(out: Output) = OfferTypes.Invoice.tlvSerializer.write(tlvs, out)

        companion object : TlvValueReader {
            const val tag: Long = 66

            override fun read(input: Input): Invoice =
                Invoice(OfferTypes.Invoice.tlvSerializer.read(input))
        }
    }

    /**
     * This message may be used when we receive an invalid invoice or invoice request.
     * It contains information helping senders figure out why their message was invalid.
     */
    data class InvoiceError(val tlvs: TlvStream) : OnionMessagePayloadTlv() {
        override val tag: Long get() = InvoiceError.tag
        override fun write(out: Output) = tlvSerializer.write(tlvs, out)

        companion object : TlvValueReader {
            const val tag: Long = 68

            val tlvSerializer = TlvStreamSerializer(
                true, @Suppress("UNCHECKED_CAST") mapOf(
                    OfferTypes.ErroneousField.tag to OfferTypes.ErroneousField.Companion as TlvValueReader,
                    OfferTypes.SuggestedValue.tag to OfferTypes.SuggestedValue.Companion as TlvValueReader,
                    OfferTypes.Error.tag to OfferTypes.Error.Companion as TlvValueReader,
                )
            )

            override fun read(input: Input): InvoiceError =
                InvoiceError(tlvSerializer.read(input))
        }
    }
}

data class MessageOnion(val records: TlvStream) {
    val replyPath = records.get()?.blindedRoute
    val encryptedData = records.get()!!.data

    fun write(out: Output) = tlvSerializer.write(records, out)

    fun write(): ByteArray {
        val out = ByteArrayOutput()
        write(out)
        return out.toByteArray()
    }

    companion object {
        val tlvSerializer = TlvStreamSerializer(
            true, @Suppress("UNCHECKED_CAST") mapOf(
                OnionMessagePayloadTlv.ReplyPath.tag to OnionMessagePayloadTlv.ReplyPath.Companion as TlvValueReader,
                OnionMessagePayloadTlv.EncryptedData.tag to OnionMessagePayloadTlv.EncryptedData.Companion as TlvValueReader,
                OnionMessagePayloadTlv.InvoiceRequest.tag to OnionMessagePayloadTlv.InvoiceRequest.Companion as TlvValueReader,
                OnionMessagePayloadTlv.Invoice.tag to OnionMessagePayloadTlv.Invoice.Companion as TlvValueReader,
                OnionMessagePayloadTlv.InvoiceError.tag to OnionMessagePayloadTlv.InvoiceError.Companion as TlvValueReader
            )
        )

        fun read(input: Input): MessageOnion = MessageOnion(tlvSerializer.read(input))

        fun read(bytes: ByteArray): MessageOnion = read(ByteArrayInput(bytes))
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy