2020 09 18 btchrp stringfactory (#2031)

* Implement StringFactory on the companion object for BtcHumanReadablePart

* Make Bech32.checkHrpvalidity() take in a string rather than a Bech32HumanReadablePart as a parameter, now we check validity _just_ according to the bech32 standard, not specific applications light lightning / bitcoin

* Add missing override

* Overload Bech32.checkHrpValidity() to pass in a 'StringFactory' in cases where you know what HRP you expect

* Do nadav's suggestion on Bech32.splitToHrpAndData() -- allow a factory to be passed in parameterized with the type you expect
This commit is contained in:
Chris Stewart 2020-09-18 13:45:32 -05:00 committed by GitHub
parent 82b324f1aa
commit 334b4d0965
4 changed files with 51 additions and 41 deletions

View File

@ -9,9 +9,9 @@ class BtcHumanReadablePartTest extends BitcoinSUnitTest {
import BtcHumanReadablePart._
"HumanReadablePart" must "match the correct hrp with the correct string" in {
BtcHumanReadablePart("tb") must be(Success(tb))
BtcHumanReadablePart("bc") must be(Success(bc))
BtcHumanReadablePart("bcrt") must be(Success(bcrt))
BtcHumanReadablePart.fromStringT("tb") must be(Success(tb))
BtcHumanReadablePart.fromStringT("bc") must be(Success(bc))
BtcHumanReadablePart.fromStringT("bcrt") must be(Success(bcrt))
}
it must "match the correct hrp with the correct network" in {

View File

@ -112,7 +112,7 @@ sealed abstract class Bech32Address extends BitcoinAddress {
}
def expandHrp: Vector[UInt5] = {
Bech32.hrpExpand(hrp)
hrp.expand
}
}
@ -151,7 +151,7 @@ object Bech32Address extends AddressFactory[Bech32Address] {
def createChecksum(
hrp: BtcHumanReadablePart,
bytes: Vector[UInt5]): Vector[UInt5] = {
val values = Bech32.hrpExpand(hrp) ++ bytes
val values = hrp.expand ++ bytes
Bech32.createChecksum(values)
}
@ -191,7 +191,7 @@ object Bech32Address extends AddressFactory[Bech32Address] {
override def fromString(bech32: String): Bech32Address = {
val bech32T = for {
(hrp, data) <- Bech32.splitToHrpAndData(bech32)
btcHrp <- BtcHumanReadablePart(hrp)
btcHrp = BtcHumanReadablePart.fromString(hrp)
} yield Bech32Address(btcHrp, data)
bech32T match {

View File

@ -2,8 +2,7 @@ package org.bitcoins.core.protocol
import org.bitcoins.core.config._
import org.bitcoins.core.util.Bech32HumanReadablePart
import scala.util.{Failure, Success, Try}
import org.bitcoins.crypto.StringFactory
/**
* Represents the HumanReadablePart of a Bech32 address
@ -13,7 +12,7 @@ sealed abstract class BtcHumanReadablePart extends Bech32HumanReadablePart {
def network: BitcoinNetwork
}
object BtcHumanReadablePart {
object BtcHumanReadablePart extends StringFactory[BtcHumanReadablePart] {
/** Represents the HumanReadablePart for a bitcoin mainnet bech32 address */
case object bc extends BtcHumanReadablePart {
@ -39,15 +38,14 @@ object BtcHumanReadablePart {
override def chars: String = "bcrt"
}
def apply(str: String): Try[BtcHumanReadablePart] =
override def fromString(str: String): BtcHumanReadablePart =
str match {
case "bc" => Success(bc)
case "tb" => Success(tb)
case "bcrt" => Success(bcrt) // Bitcoin Core specific
case "bc" => bc
case "tb" => tb
case "bcrt" => bcrt // Bitcoin Core specific
case _ =>
Failure(
new IllegalArgumentException(
s"Could not construct BTC HRP from $str"))
throw new IllegalArgumentException(
s"Could not construct BTC HRP from $str")
}
def apply(network: NetworkParameters): BtcHumanReadablePart =
@ -57,6 +55,6 @@ object BtcHumanReadablePart {
case _: RegTest => bcrt
}
def apply(hrp: Bech32HumanReadablePart): Try[BtcHumanReadablePart] =
BtcHumanReadablePart(hrp.chars)
def apply(hrp: Bech32HumanReadablePart): BtcHumanReadablePart =
BtcHumanReadablePart.fromString(hrp.chars)
}

View File

@ -1,8 +1,7 @@
package org.bitcoins.core.util
import org.bitcoins.core.number.{UInt5, UInt8}
import org.bitcoins.core.protocol.BtcHumanReadablePart
import org.bitcoins.core.protocol.ln.LnHumanReadablePart
import org.bitcoins.crypto.StringFactory
import scodec.bits.ByteVector
import scala.annotation.tailrec
@ -41,8 +40,8 @@ sealed abstract class Bech32 {
* Expands the human readable part of a bech32 address as per
* [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 BIP173]]
*/
def hrpExpand(hrp: Bech32HumanReadablePart): Vector[UInt5] = {
val lowerchars = hrp.chars.toLowerCase
def hrpExpand(string: String): Vector[UInt5] = {
val lowerchars = string.toLowerCase
val x: Vector[UInt5] = lowerchars.map { c =>
UInt5(c >> 5)
@ -75,7 +74,7 @@ sealed abstract class Bech32 {
* [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 BIP173]]
* rules
*/
def checkHrpValidity(hrp: String): Try[Bech32HumanReadablePart] = {
def checkHrpValidity(hrp: String): Try[String] = {
@tailrec
def loop(
remaining: List[Char],
@ -106,18 +105,20 @@ sealed abstract class Bech32 {
val hrpT =
loop(hrp.toCharArray.toList, Nil, isLower = false, isUpper = false)
hrpT.flatMap { chars =>
hrpT.map { chars =>
val str = chars.mkString
val lnT = LnHumanReadablePart(str)
val btcT = BtcHumanReadablePart(str)
lnT
.orElse(btcT)
.orElse(Failure(new IllegalArgumentException(
s"Could not construct valid LN or BTC HRP from $str ")))
str
}
}
/** Checks the validity of the HRP against bech32 and the given [[StringFactory]] */
def checkHrpValidity[T <: Bech32HumanReadablePart](
hrp: String,
factory: StringFactory[T]): Try[T] = {
checkHrpValidity(hrp)
.flatMap(str => factory.fromStringT(str))
}
def isInHrpRange(char: Char): Boolean = char >= 33 && char <= 126
/**
@ -210,8 +211,7 @@ sealed abstract class Bech32 {
* [[https://github.com/sipa/bech32/blob/master/ref/python/segwit_addr.py#L62 this function]]
* by Sipa
*/
def splitToHrpAndData(
bech32: String): Try[(Bech32HumanReadablePart, Vector[UInt5])] = {
def splitToHrpAndData(bech32: String): Try[(String, Vector[UInt5])] = {
val sepIndexes = bech32.zipWithIndex.filter {
case (sep, _) => sep == Bech32.separator
}
@ -245,25 +245,37 @@ sealed abstract class Bech32 {
Failure(new IllegalArgumentException("Hrp/data too short"))
} else {
for {
hrp <- checkHrpValidity(hrpStr)
hrpString <- checkHrpValidity(hrpStr)
dataWithCheck <- Bech32.checkDataValidity(dataStr)
hrpU5s = hrpExpand(hrpStr)
dataNoCheck <- {
if (verifyChecksum(hrp, dataWithCheck)) {
if (verifyChecksum(hrpU5s, dataWithCheck)) {
Success(dataWithCheck.take(dataWithCheck.size - 6))
} else
Failure(
new IllegalArgumentException(
s"Checksum was invalid on bech32 string $bech32"))
}
} yield (hrp, dataNoCheck)
} yield (hrpString, dataNoCheck)
}
}
}
def verifyChecksum(hrp: Bech32HumanReadablePart, u5s: Seq[UInt5]): Boolean = {
val expandedHrp = hrpExpand(hrp)
val data = expandedHrp ++ u5s
val checksum = Bech32.polyMod(data)
def splitToHrpAndData[T <: Bech32HumanReadablePart](
bech32: String,
factory: StringFactory[T]): Try[(T, Vector[UInt5])] = {
splitToHrpAndData(bech32).flatMap {
case (hrpString, data) =>
factory
.fromStringT(hrpString)
.map(hrp => (hrp, data))
}
}
def verifyChecksum(hrp: Seq[UInt5], u5s: Seq[UInt5]): Boolean = {
val data = hrp ++ u5s
val checksum = Bech32.polyMod(data.toVector)
checksum == 1
}
@ -302,5 +314,5 @@ abstract class Bech32HumanReadablePart {
def chars: String
/** Expands this HRP into a vector of UInt5s, in accordance with the Bech32 spec */
def expand: Vector[UInt5] = Bech32.hrpExpand(this)
def expand: Vector[UInt5] = Bech32.hrpExpand(chars)
}