Skip to content

Commit f86fffe

Browse files
authored
Rename OfferIssuerId (#720)
The `OfferIssuerId` was previously named `OfferNodeId`, but its usage changed when it was renamed: it doesn't necessarily match a lightning `node_id` and can instead be a completely unrelated public key. It can now be included in combination with blinded paths: when that happens, the blinded paths must be used in priority to reach the node as the `issuer_id` may not match a network `node_id` at all (or may even match an unrelated node).
1 parent ff19651 commit f86fffe

File tree

5 files changed

+35
-35
lines changed

5 files changed

+35
-35
lines changed

src/commonMain/kotlin/fr/acinq/lightning/json/JsonSerializers.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ object JsonSerializers {
666666
val description: String?,
667667
val metadata: ByteVector?,
668668
val expirySeconds: Long?,
669-
val nodeId: PublicKey?,
669+
val issuerId: PublicKey?,
670670
val path: List<RouteBlinding.BlindedRoute>?,
671671
val features: Features?,
672672
val unknownTlvs: List<GenericTlv>?
@@ -690,7 +690,7 @@ object JsonSerializers {
690690
description = o.description,
691691
metadata = o.metadata,
692692
expirySeconds = o.expirySeconds,
693-
nodeId = o.nodeId,
693+
issuerId = o.issuerId,
694694
path = o.paths?.map { it.route }?.run { ifEmpty { null } },
695695
features = o.features.let { if (it == Features.empty) null else it },
696696
unknownTlvs = o.records.unknown.toList().run { ifEmpty { null } }

src/commonMain/kotlin/fr/acinq/lightning/payment/Bolt12Invoice.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ data class Bolt12Invoice(val records: TlvStream<InvoiceTlv>) : PaymentRequest()
5252

5353
// It is assumed that the request is valid for this offer.
5454
fun validateFor(request: InvoiceRequest): Either<String, Unit> {
55-
val offerNodeIds = invoiceRequest.offer.nodeId?.let { listOf(it) } ?: invoiceRequest.offer.paths!!.map { it.route.blindedNodeIds.last() }
55+
val offerNodeIds = invoiceRequest.offer.issuerId?.let { listOf(it) } ?: invoiceRequest.offer.paths!!.map { it.route.blindedNodeIds.last() }
5656
return if (invoiceRequest.unsigned() != request.unsigned()) {
5757
Either.Left("Invoice does not match request")
5858
} else if (!offerNodeIds.contains(nodeId)) {

src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -262,21 +262,21 @@ object OfferTypes {
262262
}
263263

264264
/**
265-
* Public key of the offer creator.
265+
* Public key of the offer issuer.
266266
* 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.
267267
* 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.
268268
*/
269-
data class OfferNodeId(val publicKey: PublicKey) : OfferTlv() {
270-
override val tag: Long get() = OfferNodeId.tag
269+
data class OfferIssuerId(val publicKey: PublicKey) : OfferTlv() {
270+
override val tag: Long get() = OfferIssuerId.tag
271271

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

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

740740
fun encode(): String {
741741
val data = tlvSerializer.write(records)
@@ -773,7 +773,7 @@ object OfferTypes {
773773
amount?.let { OfferAmount(it) },
774774
description?.let { OfferDescription(it) },
775775
features.bolt12Features().let { if (it != Features.empty) OfferFeatures(it) else null },
776-
OfferNodeId(nodeId)
776+
OfferIssuerId(nodeId)
777777
)
778778
return Offer(TlvStream(tlvs + additionalTlvs, customTlvs))
779779
}
@@ -813,7 +813,7 @@ object OfferTypes {
813813

814814
fun validate(records: TlvStream<OfferTlv>): Either<InvalidTlvPayload, Offer> {
815815
if (records.get<OfferDescription>() == null && records.get<OfferAmount>() != null) return Left(MissingRequiredTlv(10))
816-
if (records.get<OfferNodeId>() == null && records.get<OfferPaths>() == null) return Left(MissingRequiredTlv(22))
816+
if (records.get<OfferIssuerId>() == null && records.get<OfferPaths>() == null) return Left(MissingRequiredTlv(22))
817817
if (records.unknown.any { !isOfferTlv(it) }) return Left(ForbiddenTlv(records.unknown.find { !isOfferTlv(it) }!!.tag))
818818
return Right(Offer(records))
819819
}
@@ -830,7 +830,7 @@ object OfferTypes {
830830
OfferPaths.tag to OfferPaths as TlvValueReader<OfferTlv>,
831831
OfferIssuer.tag to OfferIssuer as TlvValueReader<OfferTlv>,
832832
OfferQuantityMax.tag to OfferQuantityMax as TlvValueReader<OfferTlv>,
833-
OfferNodeId.tag to OfferNodeId as TlvValueReader<OfferTlv>,
833+
OfferIssuerId.tag to OfferIssuerId as TlvValueReader<OfferTlv>,
834834
)
835835
)
836836

@@ -958,7 +958,7 @@ object OfferTypes {
958958
OfferPaths.tag to OfferPaths as TlvValueReader<InvoiceRequestTlv>,
959959
OfferIssuer.tag to OfferIssuer as TlvValueReader<InvoiceRequestTlv>,
960960
OfferQuantityMax.tag to OfferQuantityMax as TlvValueReader<InvoiceRequestTlv>,
961-
OfferNodeId.tag to OfferNodeId as TlvValueReader<InvoiceRequestTlv>,
961+
OfferIssuerId.tag to OfferIssuerId as TlvValueReader<InvoiceRequestTlv>,
962962
// Invoice request part
963963
InvoiceRequestChain.tag to InvoiceRequestChain as TlvValueReader<InvoiceRequestTlv>,
964964
InvoiceRequestAmount.tag to InvoiceRequestAmount as TlvValueReader<InvoiceRequestTlv>,
@@ -998,7 +998,7 @@ object OfferTypes {
998998
OfferPaths.tag to OfferPaths as TlvValueReader<InvoiceTlv>,
999999
OfferIssuer.tag to OfferIssuer as TlvValueReader<InvoiceTlv>,
10001000
OfferQuantityMax.tag to OfferQuantityMax as TlvValueReader<InvoiceTlv>,
1001-
OfferNodeId.tag to OfferNodeId as TlvValueReader<InvoiceTlv>,
1001+
OfferIssuerId.tag to OfferIssuerId as TlvValueReader<InvoiceTlv>,
10021002
InvoiceRequestChain.tag to InvoiceRequestChain as TlvValueReader<InvoiceTlv>,
10031003
InvoiceRequestAmount.tag to InvoiceRequestAmount as TlvValueReader<InvoiceTlv>,
10041004
InvoiceRequestFeatures.tag to InvoiceRequestFeatures as TlvValueReader<InvoiceTlv>,

src/commonTest/kotlin/fr/acinq/lightning/payment/Bolt12InvoiceTestsCommon.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import fr.acinq.lightning.wire.OfferTypes.OfferChains
3737
import fr.acinq.lightning.wire.OfferTypes.OfferDescription
3838
import fr.acinq.lightning.wire.OfferTypes.OfferFeatures
3939
import fr.acinq.lightning.wire.OfferTypes.OfferIssuer
40-
import fr.acinq.lightning.wire.OfferTypes.OfferNodeId
40+
import fr.acinq.lightning.wire.OfferTypes.OfferIssuerId
4141
import fr.acinq.lightning.wire.OfferTypes.OfferQuantityMax
4242
import fr.acinq.lightning.wire.OfferTypes.PaymentInfo
4343
import fr.acinq.lightning.wire.OfferTypes.Signature
@@ -173,7 +173,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
173173
val otherNodeKey = randomKey()
174174
val withOtherNodeId = signInvoice(Bolt12Invoice(TlvStream(invoice.records.records.map {
175175
when (it) {
176-
is OfferNodeId -> OfferNodeId(otherNodeKey.publicKey())
176+
is OfferIssuerId -> OfferIssuerId(otherNodeKey.publicKey())
177177
else -> it
178178
}
179179
}.toSet())), nodeKey)
@@ -236,7 +236,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
236236
val tlvs = setOf(
237237
InvoiceRequestMetadata(ByteVector.fromHex("010203040506")),
238238
OfferDescription("offer description"),
239-
OfferNodeId(nodeKey.publicKey()),
239+
OfferIssuerId(nodeKey.publicKey()),
240240
InvoiceRequestAmount(15000.msat),
241241
InvoiceRequestPayerId(payerKey.publicKey()),
242242
InvoiceRequestPayerNote("I am Batman"),
@@ -306,7 +306,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
306306
val nodeKey = randomKey()
307307
val tlvs = setOf(
308308
InvoiceRequestMetadata(ByteVector.fromHex("012345")),
309-
OfferNodeId(nodeKey.publicKey()),
309+
OfferIssuerId(nodeKey.publicKey()),
310310
InvoiceRequestPayerId(randomKey().publicKey()),
311311
InvoicePaths(listOf(createPaymentBlindedRoute(randomKey().publicKey()).route)),
312312
InvoiceBlindedPay(listOf(PaymentInfo(0.msat, 0, CltvExpiryDelta(0), 0.msat, 765432.msat, Features.empty))),
@@ -360,7 +360,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
360360
OfferDescription(description),
361361
OfferFeatures(Features.empty),
362362
OfferIssuer(issuer),
363-
OfferNodeId(nodeKey.publicKey()),
363+
OfferIssuerId(nodeKey.publicKey()),
364364
InvoiceRequestChain(chain),
365365
InvoiceRequestAmount(amount),
366366
InvoiceRequestQuantity(quantity),
@@ -489,7 +489,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
489489
OfferDescription("offer with quantity"),
490490
OfferIssuer("[email protected]"),
491491
OfferQuantityMax(1000),
492-
OfferNodeId(nodeKey.publicKey())
492+
OfferIssuerId(nodeKey.publicKey())
493493
)
494494
)
495495
val encodedOffer = "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqqgqvqcdgq2zdhkven9wgs8w6t5dqs8zatpde6xjarezggkzmrfvdj5qcnfvaeksmms9e3k7mg5qgp7s93pqvn6l4vemgezdarq3wt2kpp0u4vt74vzz8futen7ej97n93jypp57"

src/commonTest/kotlin/fr/acinq/lightning/wire/OfferTypesTestsCommon.kt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import fr.acinq.lightning.wire.OfferTypes.OfferAmount
2929
import fr.acinq.lightning.wire.OfferTypes.OfferChains
3030
import fr.acinq.lightning.wire.OfferTypes.OfferDescription
3131
import fr.acinq.lightning.wire.OfferTypes.OfferIssuer
32-
import fr.acinq.lightning.wire.OfferTypes.OfferNodeId
32+
import fr.acinq.lightning.wire.OfferTypes.OfferIssuerId
3333
import fr.acinq.lightning.wire.OfferTypes.OfferQuantityMax
3434
import fr.acinq.lightning.wire.OfferTypes.Signature
3535
import fr.acinq.lightning.wire.OfferTypes.readPath
@@ -55,13 +55,13 @@ class OfferTypesTestsCommon : LightningTestSuite() {
5555

5656
@Test
5757
fun `minimal offer`() {
58-
val tlvs = setOf(OfferNodeId(nodeId))
58+
val tlvs = setOf(OfferIssuerId(nodeId))
5959
val offer = Offer(TlvStream(tlvs))
6060
val encoded = "lno1zcssxr0juddeytv7nwawhk9nq9us0arnk8j8wnsq8r2e86vzgtfneupe"
6161
assertEquals(offer, Offer.decode(encoded).get())
6262
assertNull(offer.amount)
6363
assertNull(offer.description)
64-
assertEquals(nodeId, offer.nodeId)
64+
assertEquals(nodeId, offer.issuerId)
6565
// We can't create an empty offer.
6666
assertTrue(Offer.validate(TlvStream.empty()).isLeft)
6767
}
@@ -75,14 +75,14 @@ class OfferTypesTestsCommon : LightningTestSuite() {
7575
OfferDescription("offer with quantity"),
7676
OfferIssuer("[email protected]"),
7777
OfferQuantityMax(0),
78-
OfferNodeId(nodeId)
78+
OfferIssuerId(nodeId)
7979
)
8080
)
8181
val encoded = "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqqgqyeq5ym0venx2u3qwa5hg6pqw96kzmn5d968jys3v9kxjcm9gp3xjemndphhqtnrdak3gqqkyypsmuhrtwfzm85mht4a3vcp0yrlgua3u3m5uqpc6kf7nqjz6v70qwg"
8282
assertEquals(offer, Offer.decode(encoded).get())
8383
assertEquals(50.msat, offer.amount)
8484
assertEquals("offer with quantity", offer.description)
85-
assertEquals(nodeId, offer.nodeId)
85+
assertEquals(nodeId, offer.issuerId)
8686
assertEquals("[email protected]", offer.issuer)
8787
assertEquals(Long.MAX_VALUE, offer.quantityMax)
8888
}
@@ -149,7 +149,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
149149

150150
@Test
151151
fun `check that invoice request matches offer - without chain`() {
152-
val offer = Offer(TlvStream(OfferAmount(100.msat), OfferDescription("offer without chains"), OfferNodeId(randomKey().publicKey())))
152+
val offer = Offer(TlvStream(OfferAmount(100.msat), OfferDescription("offer without chains"), OfferIssuerId(randomKey().publicKey())))
153153
val payerKey = randomKey()
154154
val tlvs: Set<InvoiceRequestTlv> = offer.records.records + setOf(
155155
InvoiceRequestMetadata(ByteVector.fromHex("012345")),
@@ -171,7 +171,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
171171
fun `check that invoice request matches offer - with chains`() {
172172
val chain1 = BlockHash(randomBytes32())
173173
val chain2 = BlockHash(randomBytes32())
174-
val offer = Offer(TlvStream(OfferChains(listOf(chain1, chain2)), OfferAmount(100.msat), OfferDescription("offer with chains"), OfferNodeId(randomKey().publicKey())))
174+
val offer = Offer(TlvStream(OfferChains(listOf(chain1, chain2)), OfferAmount(100.msat), OfferDescription("offer with chains"), OfferIssuerId(randomKey().publicKey())))
175175
val payerKey = randomKey()
176176
val request1 = InvoiceRequest(offer, 100.msat, 1, Features.empty, payerKey, null, chain1)
177177
assertTrue(request1.isValid())
@@ -196,7 +196,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
196196
TlvStream(
197197
OfferAmount(500.msat),
198198
OfferDescription("offer for multiple items"),
199-
OfferNodeId(randomKey().publicKey()),
199+
OfferIssuerId(randomKey().publicKey()),
200200
OfferQuantityMax(10),
201201
)
202202
)
@@ -216,7 +216,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
216216
val payerKey = PrivateKey.fromHex("527d410ec920b626ece685e8af9abc976a48dbf2fe698c1b35d90a1c5fa2fbca")
217217
val tlvsWithoutSignature = setOf(
218218
InvoiceRequestMetadata(ByteVector.fromHex("abcdef")),
219-
OfferNodeId(nodeId),
219+
OfferIssuerId(nodeId),
220220
InvoiceRequestPayerId(payerKey.publicKey()),
221221
)
222222
val signature = signSchnorr(InvoiceRequest.signatureTag, rootHash(TlvStream(tlvsWithoutSignature)), payerKey)
@@ -226,7 +226,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
226226
assertEquals(invoiceRequest, InvoiceRequest.decode(encoded).get())
227227
assertNull(invoiceRequest.offer.amount)
228228
assertNull(invoiceRequest.offer.description)
229-
assertEquals(nodeId, invoiceRequest.offer.nodeId)
229+
assertEquals(nodeId, invoiceRequest.offer.issuerId)
230230
assertEquals(ByteVector.fromHex("abcdef"), invoiceRequest.metadata)
231231
assertEquals(payerKey.publicKey(), invoiceRequest.payerId)
232232
// Removing any TLV from the minimal invoice request makes it invalid.
@@ -514,7 +514,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
514514
assertNull(offer.description)
515515
assertEquals(Features.empty, offer.features) // the offer shouldn't have any feature to guarantee stability
516516
assertNull(offer.expirySeconds)
517-
assertNull(offer.nodeId) // the offer should not leak our node_id
517+
assertNull(offer.issuerId) // the offer should not leak our node_id
518518
assertEquals(1, offer.contactInfos.size)
519519
val path = offer.contactInfos.first()
520520
assertIs<BlindedPath>(path)

0 commit comments

Comments
 (0)