fix: reuse TronAddress.kt's existing Base58Check instead of duplicating it

CI caught a redeclaration - Base58Check is already a chain-agnostic object
in this same package (TronAddress.kt), used for Tron's own Base58Check
addresses. Bitcoin's legacy addresses are the same shape (1 version byte +
20-byte hash), just a different version byte - reuse it directly.
This commit is contained in:
2026-07-13 06:00:31 -07:00
parent d4faa64c19
commit 9629e25e69
2 changed files with 10 additions and 57 deletions
@@ -1,53 +0,0 @@
package io.novafoundation.nova.common.utils
import java.math.BigInteger
private const val BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
/**
* Base58Check - the legacy encoding used by Bitcoin's P2PKH (`1...`) and P2SH (`3...`) addresses, as opposed to
* native SegWit's bech32. Needed only to recognize/decode EXTERNAL destination addresses for sending (many
* exchanges, including at least one confirmed live, only offer a P2SH withdrawal address with no way to pick a
* native SegWit one) - this app's OWN address (derivation, receiving, `bitcoinAddressToAccountId()`) remains
* native SegWit only, unaffected by this.
*/
object Base58Check {
class Decoded(val version: Int, val payload: ByteArray)
fun decode(address: String): Decoded {
val full = base58Decode(address)
require(full.size == 25) { "Base58Check payload must be 25 bytes, got ${full.size}" }
val versionAndPayload = full.copyOfRange(0, 21)
val checksum = full.copyOfRange(21, 25)
val expectedChecksum = versionAndPayload.sha256d().copyOfRange(0, 4)
require(checksum.contentEquals(expectedChecksum)) { "Base58Check checksum mismatch" }
return Decoded(version = versionAndPayload[0].toInt() and 0xff, payload = versionAndPayload.copyOfRange(1, 21))
}
private fun base58Decode(input: String): ByteArray {
require(input.isNotEmpty()) { "Base58 input must not be empty" }
var value = BigInteger.ZERO
val base = BigInteger.valueOf(58)
for (c in input) {
val digit = BASE58_ALPHABET.indexOf(c)
require(digit >= 0) { "Invalid base58 character: $c" }
value = value.multiply(base).add(BigInteger.valueOf(digit.toLong()))
}
val bytes = value.toByteArray().let {
// BigInteger.toByteArray() may prepend a 0x00 sign-disambiguation byte - strip it, it is not part of the data.
if (it.size > 1 && it[0] == 0.toByte()) it.copyOfRange(1, it.size) else it
}
// Each leading '1' character encodes a leading zero byte that BigInteger's conversion would otherwise drop.
val leadingZeros = input.takeWhile { it == '1' }.length
return ByteArray(leadingZeros) + bytes
}
}
@@ -33,12 +33,18 @@ fun String.decodeBitcoinDestination(): BitcoinDestination {
}
}
// Base58Check itself is chain-agnostic (see TronAddress.kt) - a Bitcoin legacy address is 1 version byte +
// 20-byte hash, same shape as Tron's own address, just a different version byte and no fixed prefix meaning.
val decoded = Base58Check.decode(this)
require(decoded.size == 21) { "Not a valid Bitcoin legacy address: $this" }
return when (decoded.version) {
P2PKH_VERSION -> BitcoinDestination.P2pkh(decoded.payload)
P2SH_VERSION -> BitcoinDestination.P2sh(decoded.payload)
else -> error("Unsupported Bitcoin address version byte: ${decoded.version}")
val version = decoded[0].toInt() and 0xff
val hash = decoded.copyOfRange(1, decoded.size)
return when (version) {
P2PKH_VERSION -> BitcoinDestination.P2pkh(hash)
P2SH_VERSION -> BitcoinDestination.P2sh(hash)
else -> error("Unsupported Bitcoin address version byte: $version")
}
}