Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ object JsonSerializers {
val description: String?,
val metadata: ByteVector?,
val expirySeconds: Long?,
val nodeId: PublicKey?,
val issuerId: PublicKey?,
val path: List<RouteBlinding.BlindedRoute>?,
val features: Features?,
val unknownTlvs: List<GenericTlv>?
Expand All @@ -690,7 +690,7 @@ object JsonSerializers {
description = o.description,
metadata = o.metadata,
expirySeconds = o.expirySeconds,
nodeId = o.nodeId,
issuerId = o.issuerId,
path = o.paths?.map { it.route }?.run { ifEmpty { null } },
features = o.features.let { if (it == Features.empty) null else it },
unknownTlvs = o.records.unknown.toList().run { ifEmpty { null } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ data class Bolt12Invoice(val records: TlvStream<InvoiceTlv>) : PaymentRequest()

// It is assumed that the request is valid for this offer.
fun validateFor(request: InvoiceRequest): Either<String, Unit> {
val offerNodeIds = invoiceRequest.offer.nodeId?.let { listOf(it) } ?: invoiceRequest.offer.paths!!.map { it.route.blindedNodeIds.last() }
val offerNodeIds = invoiceRequest.offer.issuerId?.let { listOf(it) } ?: invoiceRequest.offer.paths!!.map { it.route.blindedNodeIds.last() }
return if (invoiceRequest.unsigned() != request.unsigned()) {
Either.Left("Invoice does not match request")
} else if (!offerNodeIds.contains(nodeId)) {
Expand Down
30 changes: 15 additions & 15 deletions src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -262,21 +262,21 @@ object OfferTypes {
}

/**
* Public key of the offer creator.
* Public key of the offer issuer.
* If `OfferPaths` is present, they must be used to retrieve an invoice even if this public key corresponds to a node id in the public network.
* If `OfferPaths` is not present, this public key must correspond to a node id in the public network that needs to be contacted to retrieve an invoice.
*/
data class OfferNodeId(val publicKey: PublicKey) : OfferTlv() {
override val tag: Long get() = OfferNodeId.tag
data class OfferIssuerId(val publicKey: PublicKey) : OfferTlv() {
override val tag: Long get() = OfferIssuerId.tag

override fun write(out: Output) {
LightningCodecs.writeBytes(publicKey.value, out)
}

companion object : TlvValueReader<OfferNodeId> {
companion object : TlvValueReader<OfferIssuerId> {
const val tag: Long = 22
override fun read(input: Input): OfferNodeId {
return OfferNodeId(PublicKey(LightningCodecs.bytes(input, input.availableBytes)))
override fun read(input: Input): OfferIssuerId {
return OfferIssuerId(PublicKey(LightningCodecs.bytes(input, input.availableBytes)))
}
}
}
Expand Down Expand Up @@ -732,10 +732,10 @@ object OfferTypes {
val paths: List<ContactInfo.BlindedPath>? = records.get<OfferPaths>()?.paths
val issuer: String? = records.get<OfferIssuer>()?.issuer
val quantityMax: Long? = records.get<OfferQuantityMax>()?.max?.let { if (it == 0L) Long.MAX_VALUE else it }
val nodeId: PublicKey? = records.get<OfferNodeId>()?.publicKey
// A valid offer must contain a blinded path or a nodeId.
val contactInfos: List<ContactInfo> = paths ?: listOf(ContactInfo.RecipientNodeId(nodeId!!))
val contactNodeIds: List<PublicKey> = contactInfos.map { it.nodeId }
val issuerId: PublicKey? = records.get<OfferIssuerId>()?.publicKey
// A valid offer must contain a blinded path or an issuerId (and may contain both).
// When both are provided, the blinded paths should be tried first.
val contactInfos: List<ContactInfo> = paths ?: listOf(ContactInfo.RecipientNodeId(issuerId!!))

fun encode(): String {
val data = tlvSerializer.write(records)
Expand Down Expand Up @@ -773,7 +773,7 @@ object OfferTypes {
amount?.let { OfferAmount(it) },
description?.let { OfferDescription(it) },
features.bolt12Features().let { if (it != Features.empty) OfferFeatures(it) else null },
OfferNodeId(nodeId)
OfferIssuerId(nodeId)
)
return Offer(TlvStream(tlvs + additionalTlvs, customTlvs))
}
Expand Down Expand Up @@ -813,7 +813,7 @@ object OfferTypes {

fun validate(records: TlvStream<OfferTlv>): Either<InvalidTlvPayload, Offer> {
if (records.get<OfferDescription>() == null && records.get<OfferAmount>() != null) return Left(MissingRequiredTlv(10))
if (records.get<OfferNodeId>() == null && records.get<OfferPaths>() == null) return Left(MissingRequiredTlv(22))
if (records.get<OfferIssuerId>() == null && records.get<OfferPaths>() == null) return Left(MissingRequiredTlv(22))
if (records.unknown.any { !isOfferTlv(it) }) return Left(ForbiddenTlv(records.unknown.find { !isOfferTlv(it) }!!.tag))
return Right(Offer(records))
}
Expand All @@ -830,7 +830,7 @@ object OfferTypes {
OfferPaths.tag to OfferPaths as TlvValueReader<OfferTlv>,
OfferIssuer.tag to OfferIssuer as TlvValueReader<OfferTlv>,
OfferQuantityMax.tag to OfferQuantityMax as TlvValueReader<OfferTlv>,
OfferNodeId.tag to OfferNodeId as TlvValueReader<OfferTlv>,
OfferIssuerId.tag to OfferIssuerId as TlvValueReader<OfferTlv>,
)
)

Expand Down Expand Up @@ -958,7 +958,7 @@ object OfferTypes {
OfferPaths.tag to OfferPaths as TlvValueReader<InvoiceRequestTlv>,
OfferIssuer.tag to OfferIssuer as TlvValueReader<InvoiceRequestTlv>,
OfferQuantityMax.tag to OfferQuantityMax as TlvValueReader<InvoiceRequestTlv>,
OfferNodeId.tag to OfferNodeId as TlvValueReader<InvoiceRequestTlv>,
OfferIssuerId.tag to OfferIssuerId as TlvValueReader<InvoiceRequestTlv>,
// Invoice request part
InvoiceRequestChain.tag to InvoiceRequestChain as TlvValueReader<InvoiceRequestTlv>,
InvoiceRequestAmount.tag to InvoiceRequestAmount as TlvValueReader<InvoiceRequestTlv>,
Expand Down Expand Up @@ -998,7 +998,7 @@ object OfferTypes {
OfferPaths.tag to OfferPaths as TlvValueReader<InvoiceTlv>,
OfferIssuer.tag to OfferIssuer as TlvValueReader<InvoiceTlv>,
OfferQuantityMax.tag to OfferQuantityMax as TlvValueReader<InvoiceTlv>,
OfferNodeId.tag to OfferNodeId as TlvValueReader<InvoiceTlv>,
OfferIssuerId.tag to OfferIssuerId as TlvValueReader<InvoiceTlv>,
InvoiceRequestChain.tag to InvoiceRequestChain as TlvValueReader<InvoiceTlv>,
InvoiceRequestAmount.tag to InvoiceRequestAmount as TlvValueReader<InvoiceTlv>,
InvoiceRequestFeatures.tag to InvoiceRequestFeatures as TlvValueReader<InvoiceTlv>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import fr.acinq.lightning.wire.OfferTypes.OfferChains
import fr.acinq.lightning.wire.OfferTypes.OfferDescription
import fr.acinq.lightning.wire.OfferTypes.OfferFeatures
import fr.acinq.lightning.wire.OfferTypes.OfferIssuer
import fr.acinq.lightning.wire.OfferTypes.OfferNodeId
import fr.acinq.lightning.wire.OfferTypes.OfferIssuerId
import fr.acinq.lightning.wire.OfferTypes.OfferQuantityMax
import fr.acinq.lightning.wire.OfferTypes.PaymentInfo
import fr.acinq.lightning.wire.OfferTypes.Signature
Expand Down Expand Up @@ -173,7 +173,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
val otherNodeKey = randomKey()
val withOtherNodeId = signInvoice(Bolt12Invoice(TlvStream(invoice.records.records.map {
when (it) {
is OfferNodeId -> OfferNodeId(otherNodeKey.publicKey())
is OfferIssuerId -> OfferIssuerId(otherNodeKey.publicKey())
else -> it
}
}.toSet())), nodeKey)
Expand Down Expand Up @@ -236,7 +236,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
val tlvs = setOf(
InvoiceRequestMetadata(ByteVector.fromHex("010203040506")),
OfferDescription("offer description"),
OfferNodeId(nodeKey.publicKey()),
OfferIssuerId(nodeKey.publicKey()),
InvoiceRequestAmount(15000.msat),
InvoiceRequestPayerId(payerKey.publicKey()),
InvoiceRequestPayerNote("I am Batman"),
Expand Down Expand Up @@ -306,7 +306,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
val nodeKey = randomKey()
val tlvs = setOf(
InvoiceRequestMetadata(ByteVector.fromHex("012345")),
OfferNodeId(nodeKey.publicKey()),
OfferIssuerId(nodeKey.publicKey()),
InvoiceRequestPayerId(randomKey().publicKey()),
InvoicePaths(listOf(createPaymentBlindedRoute(randomKey().publicKey()).route)),
InvoiceBlindedPay(listOf(PaymentInfo(0.msat, 0, CltvExpiryDelta(0), 0.msat, 765432.msat, Features.empty))),
Expand Down Expand Up @@ -360,7 +360,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
OfferDescription(description),
OfferFeatures(Features.empty),
OfferIssuer(issuer),
OfferNodeId(nodeKey.publicKey()),
OfferIssuerId(nodeKey.publicKey()),
InvoiceRequestChain(chain),
InvoiceRequestAmount(amount),
InvoiceRequestQuantity(quantity),
Expand Down Expand Up @@ -489,7 +489,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
OfferDescription("offer with quantity"),
OfferIssuer("[email protected]"),
OfferQuantityMax(1000),
OfferNodeId(nodeKey.publicKey())
OfferIssuerId(nodeKey.publicKey())
)
)
val encodedOffer = "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqqgqvqcdgq2zdhkven9wgs8w6t5dqs8zatpde6xjarezggkzmrfvdj5qcnfvaeksmms9e3k7mg5qgp7s93pqvn6l4vemgezdarq3wt2kpp0u4vt74vzz8futen7ej97n93jypp57"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import fr.acinq.lightning.wire.OfferTypes.OfferAmount
import fr.acinq.lightning.wire.OfferTypes.OfferChains
import fr.acinq.lightning.wire.OfferTypes.OfferDescription
import fr.acinq.lightning.wire.OfferTypes.OfferIssuer
import fr.acinq.lightning.wire.OfferTypes.OfferNodeId
import fr.acinq.lightning.wire.OfferTypes.OfferIssuerId
import fr.acinq.lightning.wire.OfferTypes.OfferQuantityMax
import fr.acinq.lightning.wire.OfferTypes.Signature
import fr.acinq.lightning.wire.OfferTypes.readPath
Expand All @@ -55,13 +55,13 @@ class OfferTypesTestsCommon : LightningTestSuite() {

@Test
fun `minimal offer`() {
val tlvs = setOf(OfferNodeId(nodeId))
val tlvs = setOf(OfferIssuerId(nodeId))
val offer = Offer(TlvStream(tlvs))
val encoded = "lno1zcssxr0juddeytv7nwawhk9nq9us0arnk8j8wnsq8r2e86vzgtfneupe"
assertEquals(offer, Offer.decode(encoded).get())
assertNull(offer.amount)
assertNull(offer.description)
assertEquals(nodeId, offer.nodeId)
assertEquals(nodeId, offer.issuerId)
// We can't create an empty offer.
assertTrue(Offer.validate(TlvStream.empty()).isLeft)
}
Expand All @@ -75,14 +75,14 @@ class OfferTypesTestsCommon : LightningTestSuite() {
OfferDescription("offer with quantity"),
OfferIssuer("[email protected]"),
OfferQuantityMax(0),
OfferNodeId(nodeId)
OfferIssuerId(nodeId)
)
)
val encoded = "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqqgqyeq5ym0venx2u3qwa5hg6pqw96kzmn5d968jys3v9kxjcm9gp3xjemndphhqtnrdak3gqqkyypsmuhrtwfzm85mht4a3vcp0yrlgua3u3m5uqpc6kf7nqjz6v70qwg"
assertEquals(offer, Offer.decode(encoded).get())
assertEquals(50.msat, offer.amount)
assertEquals("offer with quantity", offer.description)
assertEquals(nodeId, offer.nodeId)
assertEquals(nodeId, offer.issuerId)
assertEquals("[email protected]", offer.issuer)
assertEquals(Long.MAX_VALUE, offer.quantityMax)
}
Expand Down Expand Up @@ -149,7 +149,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {

@Test
fun `check that invoice request matches offer - without chain`() {
val offer = Offer(TlvStream(OfferAmount(100.msat), OfferDescription("offer without chains"), OfferNodeId(randomKey().publicKey())))
val offer = Offer(TlvStream(OfferAmount(100.msat), OfferDescription("offer without chains"), OfferIssuerId(randomKey().publicKey())))
val payerKey = randomKey()
val tlvs: Set<InvoiceRequestTlv> = offer.records.records + setOf(
InvoiceRequestMetadata(ByteVector.fromHex("012345")),
Expand All @@ -171,7 +171,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
fun `check that invoice request matches offer - with chains`() {
val chain1 = BlockHash(randomBytes32())
val chain2 = BlockHash(randomBytes32())
val offer = Offer(TlvStream(OfferChains(listOf(chain1, chain2)), OfferAmount(100.msat), OfferDescription("offer with chains"), OfferNodeId(randomKey().publicKey())))
val offer = Offer(TlvStream(OfferChains(listOf(chain1, chain2)), OfferAmount(100.msat), OfferDescription("offer with chains"), OfferIssuerId(randomKey().publicKey())))
val payerKey = randomKey()
val request1 = InvoiceRequest(offer, 100.msat, 1, Features.empty, payerKey, null, chain1)
assertTrue(request1.isValid())
Expand All @@ -196,7 +196,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
TlvStream(
OfferAmount(500.msat),
OfferDescription("offer for multiple items"),
OfferNodeId(randomKey().publicKey()),
OfferIssuerId(randomKey().publicKey()),
OfferQuantityMax(10),
)
)
Expand All @@ -216,7 +216,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
val payerKey = PrivateKey.fromHex("527d410ec920b626ece685e8af9abc976a48dbf2fe698c1b35d90a1c5fa2fbca")
val tlvsWithoutSignature = setOf(
InvoiceRequestMetadata(ByteVector.fromHex("abcdef")),
OfferNodeId(nodeId),
OfferIssuerId(nodeId),
InvoiceRequestPayerId(payerKey.publicKey()),
)
val signature = signSchnorr(InvoiceRequest.signatureTag, rootHash(TlvStream(tlvsWithoutSignature)), payerKey)
Expand All @@ -226,7 +226,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
assertEquals(invoiceRequest, InvoiceRequest.decode(encoded).get())
assertNull(invoiceRequest.offer.amount)
assertNull(invoiceRequest.offer.description)
assertEquals(nodeId, invoiceRequest.offer.nodeId)
assertEquals(nodeId, invoiceRequest.offer.issuerId)
assertEquals(ByteVector.fromHex("abcdef"), invoiceRequest.metadata)
assertEquals(payerKey.publicKey(), invoiceRequest.payerId)
// Removing any TLV from the minimal invoice request makes it invalid.
Expand Down Expand Up @@ -514,7 +514,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
assertNull(offer.description)
assertEquals(Features.empty, offer.features) // the offer shouldn't have any feature to guarantee stability
assertNull(offer.expirySeconds)
assertNull(offer.nodeId) // the offer should not leak our node_id
assertNull(offer.issuerId) // the offer should not leak our node_id
assertEquals(1, offer.contactInfos.size)
val path = offer.contactInfos.first()
assertIs<BlindedPath>(path)
Expand Down