Skip to content

Commit

Permalink
Fix uri parsing in Bip353 resolver
Browse files Browse the repository at this point in the history
We are now using our bip21 parser when reading the
data field returned by the bip353 resolver, which
lets us handle uris with parameters correctly.

Also added new bip353 error types when relevant.
  • Loading branch information
dpad85 committed Jul 19, 2024
1 parent 22de914 commit 7c5218b
Show file tree
Hide file tree
Showing 18 changed files with 128 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ private fun ScanErrorView(
is Scan.BadRequestReason.InvalidLnurl -> stringResource(R.string.scan_error_lnurl_invalid)
is Scan.BadRequestReason.UnsupportedLnurl -> stringResource(R.string.scan_error_lnurl_unsupported)
is Scan.BadRequestReason.UnknownFormat -> stringResource(R.string.scan_error_invalid_generic)
is Scan.BadRequestReason.Bip353NameNotFound -> stringResource(id = R.string.scan_error_bip353_name_not_found, reason.username, reason.domain)
is Scan.BadRequestReason.Bip353InvalidUri -> stringResource(id = R.string.scan_error_bip353_invalid_uri)
is Scan.BadRequestReason.Bip353InvalidOffer -> stringResource(id = R.string.scan_error_bip353_invalid_offer)
is Scan.BadRequestReason.Bip353NoDNSSEC -> stringResource(id = R.string.scan_error_bip353_dnssec)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ fun MutualCloseView(
modifier = Modifier.fillMaxWidth(),
enabled = address.isNotBlank() && model.channels.isNotEmpty(),
onClick = {
when (val validation = Parser.readBitcoinAddress(chain, address)) {
when (val validation = Parser.parseBip21Uri(chain, address)) {
is Either.Left -> {
val error = validation.value
addressErrorMessage = when (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class SwapInRefundViewModel(
log.error("error when estimating swap-in refund fees: ", e)
state = SwapInRefundState.Done.Failed.Error(e)
}) {
when (val parseAddress = Parser.readBitcoinAddress(NodeParamsManager.chain, address)) {
when (val parseAddress = Parser.parseBip21Uri(NodeParamsManager.chain, address)) {
is Either.Left -> {
log.debug("invalid refund address=$address (${parseAddress.value}")
state = SwapInRefundState.Done.Failed.InvalidAddress(address, parseAddress.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@
<string name="scan_error_lnurl_invalid">Error al procesar este enlace LNURL. Comprueba que sea válido.</string>
<string name="scan_error_lnurl_unsupported">Este tipo de LNURL aún no es compatible.</string>
<string name="scan_error_invalid_generic">Estos datos usan un formato desconocido por lo que no se pueden procesar.</string>
<string name="scan_error_bip353_name_not_found">El nombre \"%1$s\" no se encuentra en \"%2$s\".</string>
<string name="scan_error_bip353_invalid_uri">Esta dirección utiliza un recurso Bip21 no válido.</string>
<string name="scan_error_bip353_invalid_offer">Esta dirección utiliza una oferta Bolt12 no válida.</string>
<string name="scan_error_bip353_dnssec">Esta dirección está alojada en un DNS no seguro. DNSSEC debe estar habilitado.</string>

Expand Down
2 changes: 2 additions & 0 deletions phoenix-android/src/main/res/values-cs/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@
<string name="scan_error_lnurl_invalid">Tento LNURL odkaz se nepodařilo zpracovat. Ujistěte se, že je platný.</string>
<string name="scan_error_lnurl_unsupported">Tento typ LNURL zatím není podporován.</string>
<string name="scan_error_invalid_generic">Tato data používají neznámý formát a nelze je zpracovat.</string>
<string name="scan_error_bip353_name_not_found">Název \"%1$s\" nebyl nalezen na \"%2$s\".</string>
<string name="scan_error_bip353_invalid_uri">Tato adresa používá neplatný zdroj Bip21.</string>
<string name="scan_error_bip353_invalid_offer">Tato adresa používá neplatnou nabídku Bolt12.</string>
<string name="scan_error_bip353_dnssec">Tato adresa je hostována na nezabezpečeném DNS. DNSSEC musí být povolen.</string>

Expand Down
2 changes: 2 additions & 0 deletions phoenix-android/src/main/res/values-de/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@
<string name="scan_error_lnurl_invalid">Dieser LNURL-Link konnte nicht verarbeitet werden. Stellen Sie sicher, dass er gültig ist.</string>
<string name="scan_error_lnurl_unsupported">Dieser LNURL-Typ wird noch nicht unterstützt.</string>
<string name="scan_error_invalid_generic">Diese Daten haben ein unbekanntes Format und können nicht verarbeitet werden.</string>
<string name="scan_error_bip353_name_not_found">Name \"%1$s\" wird auf \"%2$s\" nicht gefunden.</string>
<string name="scan_error_bip353_invalid_uri">Diese Adresse verwendet eine ungültige Bip21 Ressource.</string>
<string name="scan_error_bip353_invalid_offer">Diese Adresse verwendet ein ungültiges Bolt12-Angebot.</string>
<string name="scan_error_bip353_dnssec">Diese Adresse wird über einen unsicheren DNS gehostet. DNSSEC muss aktiviert sein.</string>

Expand Down
2 changes: 2 additions & 0 deletions phoenix-android/src/main/res/values-es/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@
<string name="scan_error_lnurl_invalid">No se ha podido procesar este enlace LNURL. Asegúrese de que es válido.</string>
<string name="scan_error_lnurl_unsupported">Este tipo de LNURL aún no se admite.</string>
<string name="scan_error_invalid_generic">Estos datos utilizan un formato desconocido y no pueden ser procesados.</string>
<string name="scan_error_bip353_name_not_found">El nombre \"%1$s\" no se encuentra en \"%2$s\".</string>
<string name="scan_error_bip353_invalid_uri">Esta dirección utiliza un recurso Bip21 no válido.</string>
<string name="scan_error_bip353_invalid_offer">Esta dirección utiliza una oferta Bolt12 no válida.</string>
<string name="scan_error_bip353_dnssec">Esta dirección está alojada en un DNS no seguro. DNSSEC debe estar habilitado.</string>

Expand Down
2 changes: 2 additions & 0 deletions phoenix-android/src/main/res/values-fr/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@
<string name="scan_error_lnurl_invalid">Ce lien LNURL n\'a pas pu être traité. Assurez-vous qu\'il soit valide.</string>
<string name="scan_error_lnurl_unsupported">Ce type de lien LNURL n\'est pas supporté.</string>
<string name="scan_error_invalid_generic">Ce contenu est mal formatté ou bien n\'est pas géré par Phoenix.</string>
<string name="scan_error_bip353_name_not_found">Le nom \"%1$s\" est introuvable sur \"%2$s\"</string>
<string name="scan_error_bip353_invalid_uri">Cette adresse utilise une ressource Bip21 non valide.</string>
<string name="scan_error_bip353_invalid_offer">Cette adresse utilise une offre Bolt12 invalide.</string>
<string name="scan_error_bip353_dnssec">Cette adresse est hébergée sur un DNS non sécurisé. DNSSEC doit être activé.</string>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@
<string name="scan_error_lnurl_invalid">Falha ao processar esse link LNURL. Verifique se ele é válido.</string>
<string name="scan_error_lnurl_unsupported">Este tipo de LNURL ainda não é suportado.</string>
<string name="scan_error_invalid_generic">Esses dados usam um formato desconhecido e não podem ser processados.</string>
<string name="scan_error_bip353_name_not_found">O nome \"%1$s\" não foi encontrado em \"%2$s\".</string>
<string name="scan_error_bip353_invalid_uri">Este endereço usa um recurso Bip21 inválido.</string>
<string name="scan_error_bip353_invalid_offer">Este endereço usa uma oferta Bolt12 inválida.</string>
<string name="scan_error_bip353_dnssec">Este endereço está hospedado em um DNS não seguro. O DNSSEC deve estar ativado.</string>

Expand Down
2 changes: 2 additions & 0 deletions phoenix-android/src/main/res/values-sk/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@
<string name="scan_error_lnurl_invalid">Tento LNURL odkaz sa nepodarilo spracovať. Uistite sa, že je platný.</string>
<string name="scan_error_lnurl_unsupported">Tento typ LNURL zatiaľ nie je podporovaný.</string>
<string name="scan_error_invalid_generic">Tieto údaje používajú neznámy formát a nemožno ich spracovať.</string>
<string name="scan_error_bip353_name_not_found">Názov \"%1$s\" sa nenašiel na \"%2$s\".</string>
<string name="scan_error_bip353_invalid_uri">Táto adresa používa neplatný zdroj Bip21.</string>
<string name="scan_error_bip353_invalid_offer">Táto adresa používa neplatnú ponuku Bolt12.</string>
<string name="scan_error_bip353_dnssec">Táto adresa je umiestnená na nezabezpečenom DNS. DNSSEC musí byť povolený.</string>

Expand Down
2 changes: 2 additions & 0 deletions phoenix-android/src/main/res/values-vi/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@
<string name="scan_error_lnurl_invalid">Không thể xử lý liên kết LNURL này. Hãy đảm bảo liên kết này có hiệu lực.</string>
<string name="scan_error_lnurl_unsupported">Loại LNURL này chưa được hỗ trợ.</string>
<string name="scan_error_invalid_generic">Dữ liệu này sử dụng định dạng không xác định và không thể xử lý được.</string>
<string name="scan_error_bip353_name_not_found">Không tìm thấy tên \"%1$s\" trên \"%2$s\".</string>
<string name="scan_error_bip353_invalid_uri">Địa chỉ này sử dụng tài nguyên Bip21 không hợp lệ.</string>
<string name="scan_error_bip353_invalid_offer">Địa chỉ này sử dụng ưu đãi Bolt12 không hợp lệ.</string>
<string name="scan_error_bip353_dnssec">Địa chỉ này được lưu trữ trên một DNS không an toàn. DNSSEC phải được bật.</string>

Expand Down
2 changes: 2 additions & 0 deletions phoenix-android/src/main/res/values/important_strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@
<string name="scan_error_lnurl_invalid">Failed to process this LNURL link. Make sure it is valid.</string>
<string name="scan_error_lnurl_unsupported">This type of LNURL is not supported yet.</string>
<string name="scan_error_invalid_generic">This data uses an unknown format and cannot be processed.</string>
<string name="scan_error_bip353_name_not_found">Name \"%1$s\" is not found on \"%2$s\".</string>
<string name="scan_error_bip353_invalid_uri">This address uses an invalid Bip21 resource.</string>
<string name="scan_error_bip353_invalid_offer">This address uses an invalid Bolt12 offer.</string>
<string name="scan_error_bip353_dnssec">This address is hosted on an unsecure DNS. DNSSEC must be enabled.</string>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class AppCloseChannelsConfigurationController(
override fun process(intent: CloseChannelsConfiguration.Intent) {
val scriptPubKey = if (intent is CloseChannelsConfiguration.Intent.MutualCloseAllChannels) {
try {
Parser.readBitcoinAddress(chain, intent.address).right!!.script
Parser.parseBip21Uri(chain, intent.address).right!!.script
} catch (e: Exception) {
throw IllegalArgumentException("Address is invalid. Caller MUST validate user input via readBitcoinAddress")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ object Scan {
data class ChainMismatch(val expected: Chain) : BadRequestReason()
data class ServiceError(val url: Url, val error: LnurlError.RemoteFailure) : BadRequestReason()
data class InvalidLnurl(val url: Url) : BadRequestReason()
data class Bip353NameNotFound(val username: String, val domain: String) : BadRequestReason()
data class Bip353InvalidUri(val path: String) : BadRequestReason()
data class Bip353InvalidOffer(val path: String) : BadRequestReason()
data class Bip353NoDNSSEC(val path: String) : BadRequestReason()
data class UnsupportedLnurl(val url: Url) : BadRequestReason()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package fr.acinq.phoenix.controllers.payments
import fr.acinq.bitcoin.BitcoinError
import fr.acinq.bitcoin.Chain
import fr.acinq.bitcoin.utils.Either
import fr.acinq.bitcoin.utils.Try
import fr.acinq.lightning.Lightning
import fr.acinq.lightning.MilliSatoshi
import fr.acinq.lightning.TrampolineFees
Expand Down Expand Up @@ -576,35 +575,32 @@ class AppScanController(
logger.debug { "dns resolved to ${json.toString().take(100)}" }

val status = json["Status"]?.jsonPrimitive?.intOrNull
// could be a [BadRequestReason.Bip353NameNotFound] it status == 3
if (status == null || status > 0) return null

// check dnssec
val ad = json["AD"]?.jsonPrimitive?.booleanOrNull
if (ad != true) {
logger.debug { "AD false, abort dns lookup" }
throw Scan.BadRequestReason.Bip353NoDNSSEC(dnsPath)
}

// check name matches records
val records = json["Answer"]?.jsonArray
if (records.isNullOrEmpty()) {
logger.debug { "no records for $dnsPath" }
return null
logger.debug { "no answer for $dnsPath" }
throw Scan.BadRequestReason.Bip353NameNotFound(username, domain)
}

val matchingRecord = records.filterIsInstance<JsonObject>().firstOrNull {
logger.debug { "inspecting record=$it" }
it["name"]?.jsonPrimitive?.content == dnsPath
} ?: return null

val ad = json["AD"]?.jsonPrimitive?.booleanOrNull
if (ad != true) {
logger.debug { "AD false, abort dns lookup" }
throw Scan.BadRequestReason.Bip353NoDNSSEC(dnsPath)
}

val data = matchingRecord["data"]?.jsonPrimitive?.content ?: return null
if (!data.startsWith("bitcoin:")) throw Scan.BadRequestReason.Bip353InvalidOffer(dnsPath)
val offerString = data.substringAfter("lno=").substringBefore("?")
if (offerString.isBlank()) throw Scan.BadRequestReason.Bip353InvalidOffer(dnsPath)
} ?: throw Scan.BadRequestReason.Bip353NameNotFound(username, domain)

return when (val offer = OfferTypes.Offer.decode(offerString)) {
is Try.Success -> { offer.result }
is Try.Failure -> {
throw Scan.BadRequestReason.Bip353InvalidOffer(dnsPath)
}
val data = matchingRecord["data"]?.jsonPrimitive?.content ?: throw Scan.BadRequestReason.Bip353InvalidUri(dnsPath)
return when (val res = Parser.parseBip21Uri(chain, data)) {
is Either.Left -> throw Scan.BadRequestReason.Bip353InvalidUri(dnsPath)
is Either.Right -> res.value.offer ?: throw Scan.BadRequestReason.Bip353InvalidOffer(dnsPath)
}
}

Expand All @@ -617,7 +613,7 @@ class AppScanController(

/** Invokes `Parser.readBitcoinAddress`, but maps [BitcoinUriError.InvalidUri] to a null result instead of a fatal error. */
private fun readBitcoinAddress(input: String): Either<BitcoinUriError, BitcoinUri>? {
return when (val result = Parser.readBitcoinAddress(chain, input)) {
return when (val result = Parser.parseBip21Uri(chain, input)) {
is Either.Left -> when (result.left) {
is BitcoinUriError.InvalidUri -> null
else -> result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ object Parser {
}

/**
* Parses an input and returns a [BitcoinUri] if it is valid, or a typed error otherwise.
* Parses an input and returns a bip-21 [BitcoinUri] if it is valid, or a typed error otherwise.
*
* @param chain the chain this parser expects the address to be valid on.
* @param input can range from a basic bitcoin address to a sophisticated Bitcoin URI with a prefix and parameters.
*/
fun readBitcoinAddress(
fun parseBip21Uri(
chain: Chain,
input: String
): Either<BitcoinUriError, BitcoinUri> {
Expand Down Expand Up @@ -128,13 +128,13 @@ object Parser {
val message = url.parameters["message"]
val lightning = url.parameters["lightning"]?.let {
when (val res = Bolt11Invoice.read(it)) {
is Try.Success -> res.get()
is Try.Success -> res.result
is Try.Failure -> null
}
}
val offer = url.parameters["lno"]?.let {
when (val res = OfferTypes.Offer.decode(it)) {
is Try.Success -> res.get()
is Try.Success -> res.result
is Try.Failure -> null
}
}
Expand All @@ -160,6 +160,6 @@ object Parser {

/** Transforms a bitcoin address into a public key script if valid, otherwise returns null. */
fun addressToPublicKeyScriptOrNull(chain: Chain, address: String): ByteVector? {
return readBitcoinAddress(chain, address).right?.script
return parseBip21Uri(chain, address).right?.script
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ class LnurlPayTest {
)
}
val client = fakeClient(engine)
val lnurl = Lnurl.extractLnurl("[email protected]", logger)
assertIs<Lnurl.Request>(lnurl)
// TODO move to an email-like reader test with bip353
val lnurl = Lnurl.Request(Url("https://zbd.gg/.well-known/lnurlp/acinq"), tag = Lnurl.Tag.Pay)
val response: HttpResponse = client.get(lnurl.initialUrl)
val json = Lnurl.processLnurlResponse(response, logger)
assertEquals("payRequest", json.get("tag")!!.jsonPrimitive.content)
Expand Down
Loading

0 comments on commit 7c5218b

Please sign in to comment.