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._
|
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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user