mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 09:52:09 +01:00
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:
parent
82b324f1aa
commit
334b4d0965
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user