mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
fix(nostr): reject non-ascii characters before folding case
BIP-173 restricts a bech32 string to printable US-ASCII (33..126). The check has to run before the case test and before lowercase(): U+212A KELVIN SIGN reads as uppercase, so an otherwise all-uppercase string carrying one shows no mixed case, and lowercase() folds it to an ASCII 'k' -- an npub with a homoglyph in place of a K decoded to the same pubkey as the genuine one. The added test builds that exact string and expects a rejection; it fails on the previous commit.
This commit is contained in:
parent
29baab62e2
commit
d7e2559c9c
@ -28,6 +28,13 @@ object Bech32 {
|
||||
* alphanumeric mode. Mixed case is invalid and is rejected.
|
||||
*/
|
||||
fun decode(bech32String: String): Pair<String, ByteArray> {
|
||||
// BIP-173 restricts every character to printable US-ASCII, 33..126.
|
||||
// This has to run before the case test and before lowercase(): a
|
||||
// non-ASCII uppercase homoglyph such as U+212A KELVIN SIGN reads as
|
||||
// uppercase, so an otherwise all-uppercase string carrying one shows no
|
||||
// mixed case, and lowercase() then folds it into an ASCII 'k'.
|
||||
require(bech32String.all { it.code in 33..126 }) { "Invalid character" }
|
||||
|
||||
val hasLower = bech32String.any { it.isLowerCase() }
|
||||
val hasUpper = bech32String.any { it.isUpperCase() }
|
||||
require(!(hasLower && hasUpper)) { "Mixed case" }
|
||||
|
||||
@ -3,6 +3,7 @@ package com.bitchat.android.nostr
|
||||
import org.junit.Assert.assertArrayEquals
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
@ -43,6 +44,18 @@ class Bech32CaseTest {
|
||||
assertThrows(IllegalArgumentException::class.java) { Bech32.decode(flipped) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a non-ascii homoglyph is rejected rather than folded to ascii`() {
|
||||
val npub = Bech32.encode("npub", pubkey).uppercase()
|
||||
// U+212A KELVIN SIGN is uppercase and lowercases to an ASCII 'k', so an
|
||||
// all-uppercase string carrying one passes a naive mixed-case test and
|
||||
// then folds into a perfectly valid npub.
|
||||
val kelvin = npub.replace("K", "\u212A")
|
||||
assumeTrue(kelvin != npub)
|
||||
|
||||
assertThrows(IllegalArgumentException::class.java) { Bech32.decode(kelvin) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `round trip through encode and decode is stable`() {
|
||||
val npub = Bech32.encode("npub", pubkey)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user