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._ import BtcHumanReadablePart._
"HumanReadablePart" must "match the correct hrp with the correct string" in { "HumanReadablePart" must "match the correct hrp with the correct string" in {
BtcHumanReadablePart("tb") must be(Success(tb)) BtcHumanReadablePart.fromStringT("tb") must be(Success(tb))
BtcHumanReadablePart("bc") must be(Success(bc)) BtcHumanReadablePart.fromStringT("bc") must be(Success(bc))
BtcHumanReadablePart("bcrt") must be(Success(bcrt)) BtcHumanReadablePart.fromStringT("bcrt") must be(Success(bcrt))
} }
it must "match the correct hrp with the correct network" in { 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] = { def expandHrp: Vector[UInt5] = {
Bech32.hrpExpand(hrp) hrp.expand
} }
} }
@ -151,7 +151,7 @@ object Bech32Address extends AddressFactory[Bech32Address] {
def createChecksum( def createChecksum(
hrp: BtcHumanReadablePart, hrp: BtcHumanReadablePart,
bytes: Vector[UInt5]): Vector[UInt5] = { bytes: Vector[UInt5]): Vector[UInt5] = {
val values = Bech32.hrpExpand(hrp) ++ bytes val values = hrp.expand ++ bytes
Bech32.createChecksum(values) Bech32.createChecksum(values)
} }
@ -191,7 +191,7 @@ object Bech32Address extends AddressFactory[Bech32Address] {
override def fromString(bech32: String): Bech32Address = { override def fromString(bech32: String): Bech32Address = {
val bech32T = for { val bech32T = for {
(hrp, data) <- Bech32.splitToHrpAndData(bech32) (hrp, data) <- Bech32.splitToHrpAndData(bech32)
btcHrp <- BtcHumanReadablePart(hrp) btcHrp = BtcHumanReadablePart.fromString(hrp)
} yield Bech32Address(btcHrp, data) } yield Bech32Address(btcHrp, data)
bech32T match { bech32T match {

View File

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

View File

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