From 310db1008ecc6691404882d67e47a3219fbc1375 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 14 Sep 2023 13:46:29 -0400 Subject: [PATCH] Fixes bug when verifying the checksum of LNURLs (bech32) in uppercase. --- .../com/vitorpamplona/amethyst/model/User.kt | 22 +++--------- .../service/lnurl/LightningAddressResolver.kt | 8 ++--- .../quartz/encoders/Bech32Util.kt | 6 ++-- .../vitorpamplona/quartz/encoders/Lud06.kt | 35 +++++++++++++++++++ .../quartz/encoders/Lud06Test.kt | 13 +++++++ 5 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/encoders/Lud06.kt create mode 100644 quartz/src/test/java/com/vitorpamplona/quartz/encoders/Lud06Test.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 732a1f1c6..0d15a10cc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -5,16 +5,15 @@ import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.distinctUntilChanged -import androidx.lifecycle.map import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.note.toShortenHex -import com.vitorpamplona.quartz.encoders.Bech32 import com.vitorpamplona.quartz.encoders.Hex import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.encoders.Lud06 import com.vitorpamplona.quartz.encoders.toNpub import com.vitorpamplona.quartz.events.BookmarkListEvent import com.vitorpamplona.quartz.events.ChatroomKey @@ -27,9 +26,6 @@ import com.vitorpamplona.quartz.events.toImmutableListOfLists import kotlinx.collections.immutable.persistentSetOf import kotlinx.coroutines.Dispatchers import java.math.BigDecimal -import java.util.regex.Pattern - -val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)") @Stable class User(val pubkeyHex: String) { @@ -268,19 +264,11 @@ class User(val pubkeyHex: String) { info?.updatedMetadataAt = latestMetadata.createdAt info?.tags = latestMetadata.tags.toImmutableListOfLists() - if (newUserInfo.lud16.isNullOrBlank() && newUserInfo.lud06?.lowercase()?.startsWith("lnurl") == true) { - try { - val url = String(Bech32.decodeBytes(newUserInfo.lud06!!, false).second) - - val matcher = lnurlpPattern.matcher(url) - while (matcher.find()) { - val domain = matcher.group(2) - val username = matcher.group(3) - - info?.lud16 = "$username@$domain" + if (newUserInfo.lud16.isNullOrBlank()) { + info?.lud06?.let { + if (it.lowercase().startsWith("lnurl")) { + info?.lud16 = Lud06().toLud16(it) } - } catch (t: Throwable) { - // Doesn't create errors. } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt index eded63d1d..00032ac22 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/LightningAddressResolver.kt @@ -4,8 +4,8 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.checkNotInMainThread -import com.vitorpamplona.quartz.encoders.Bech32 import com.vitorpamplona.quartz.encoders.LnInvoiceUtil +import com.vitorpamplona.quartz.encoders.Lud06 import com.vitorpamplona.quartz.encoders.toLnUrl import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -27,11 +27,7 @@ class LightningAddressResolver() { } if (lnaddress.lowercase().startsWith("lnurl")) { - return try { - String(Bech32.decodeBytes(lnaddress, false).second) - } catch (e: Exception) { - null - } + return Lud06().toLnUrlp(lnaddress) } return null diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Bech32Util.kt b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Bech32Util.kt index 6398225dc..f8fc487ba 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Bech32Util.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Bech32Util.kt @@ -69,14 +69,14 @@ object Bech32 { values.forEach { v -> val b = chk shr 25 chk = ((chk and 0x1ffffff) shl 5) xor v.toInt() - for (i in 0..5) { + for (i in 0..4) { if (((b shr i) and 1) != 0) chk = chk xor GEN[i] } } values1.forEach { v -> val b = chk shr 25 chk = ((chk and 0x1ffffff) shl 5) xor v.toInt() - for (i in 0..5) { + for (i in 0..4) { if (((b shr i) and 1) != 0) chk = chk xor GEN[i] } } @@ -129,7 +129,7 @@ object Bech32 { } } - val hrp = bech32.take(pos) + val hrp = bech32.take(pos).lowercase() // strings must be lower case require(hrp.length in 1..83) { "hrp must contain 1 to 83 characters" } val data = Array(bech32.length - pos - 1) { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Lud06.kt b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Lud06.kt new file mode 100644 index 000000000..0cbb8d876 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Lud06.kt @@ -0,0 +1,35 @@ +package com.vitorpamplona.quartz.encoders + +import android.util.Log +import java.util.regex.Pattern + +val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)") + +class Lud06 { + fun toLud16(str: String): String? { + return try { + val url = toLnUrlp(str) + + val matcher = lnurlpPattern.matcher(url) + matcher.find() + val domain = matcher.group(2) + val username = matcher.group(3) + + "$username@$domain" + } catch (t: Throwable) { + t.printStackTrace() + Log.w("Lud06ToLud16","Fail to convert LUD06 to LUD16",t) + null + } + } + + fun toLnUrlp(str: String): String? { + return try { + String(Bech32.decodeBytes(str, false).second) + } catch (t: Throwable) { + t.printStackTrace() + Log.w("Lud06ToLud16","Fail to convert LUD06 to LUD16",t) + null + } + } +} \ No newline at end of file diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/encoders/Lud06Test.kt b/quartz/src/test/java/com/vitorpamplona/quartz/encoders/Lud06Test.kt new file mode 100644 index 000000000..99db42c1d --- /dev/null +++ b/quartz/src/test/java/com/vitorpamplona/quartz/encoders/Lud06Test.kt @@ -0,0 +1,13 @@ +package com.vitorpamplona.quartz.encoders + +import org.junit.Assert.assertEquals +import org.junit.Test + +class Lud06Test { + val lnTips = "LNURL1DP68GURN8GHJ7MRW9E6XJURN9UH8WETVDSKKKMN0WAHZ7MRWW4EXCUP0XPURXEFEX9SKGCT9V5ER2V33X4NRGP2NE42" + @Test() + fun parseLnUrlp() { + assertEquals("https://ln.tips/.well-known/lnurlp/0x3e91adaee25215f4", Lud06().toLnUrlp(lnTips)) + assertEquals("0x3e91adaee25215f4@ln.tips", Lud06().toLud16(lnTips)) + } +} \ No newline at end of file