Implement bech32m (#2572)

* Implement bech32m

* Respond to review
This commit is contained in:
benthecarman 2021-03-18 18:04:38 -05:00 committed by GitHub
parent 12bff309c2
commit b0f7d6f26b
11 changed files with 537 additions and 35 deletions

View file

@ -1,12 +1,8 @@
package org.bitcoins.core.protocol
import org.bitcoins.core.util.Bech32
import org.bitcoins.core.util.{Bech32, Bech32Encoding}
import org.bitcoins.testkitcore.gen._
import org.bitcoins.testkitcore.gen.ln.LnInvoiceGen
import org.bitcoins.testkitcore.gen.{
AddressGenerator,
ChainParamsGenerator,
ScriptGenerators
}
import org.scalacheck.{Prop, Properties}
import scala.annotation.tailrec
@ -15,14 +11,16 @@ import scala.util.{Random, Success}
class Bech32Spec extends Properties("Bech32Spec") {
property("split all LN invoices into HRP and data") = {
Prop.forAll(LnInvoiceGen.lnInvoice) { invoice =>
val splitT = Bech32.splitToHrpAndData(invoice.toString)
val splitT =
Bech32.splitToHrpAndData(invoice.toString, Bech32Encoding.Bech32)
splitT.isSuccess
}
}
property("split all Bech32 addresses into HRP and data") = {
Prop.forAll(AddressGenerator.bech32Address) { address =>
val splitT = Bech32.splitToHrpAndData(address.value)
val splitT =
Bech32.splitToHrpAndData(address.value, Bech32Encoding.Bech32)
splitT.isSuccess
}
}

View file

@ -5,9 +5,9 @@ import org.bitcoins.core.number.{UInt5, UInt8}
import org.bitcoins.core.protocol.ln.LnHumanReadablePart
import org.bitcoins.core.protocol.ln.currency.PicoBitcoins
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.util.Bech32
import org.bitcoins.core.util.{Bech32, Bech32Encoding}
import org.bitcoins.crypto.ECPublicKey
import org.bitcoins.testkitcore.gen.NumberGenerator
import org.bitcoins.testkitcore.gen._
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import scodec.bits.ByteVector
@ -233,7 +233,7 @@ class Bech32Test extends BitcoinSUnitTest {
)
it must "fail to find the bech32 weakness" in {
val failsAll = invalidBech32.forall(invalid =>
Bech32.splitToHrpAndData(invalid).isSuccess)
Bech32.splitToHrpAndData(invalid, Bech32Encoding.Bech32).isSuccess)
assert(failsAll)
}
@ -263,4 +263,10 @@ class Bech32Test extends BitcoinSUnitTest {
"bc1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7")
.isFailure)
}
it must "fail to read a bech32m address" in {
forAll(AddressGenerator.bech32mAddress) { bech32m =>
assert(Bech32Address.fromStringT(bech32m.toString).isFailure)
}
}
}

View file

@ -0,0 +1,82 @@
package org.bitcoins.core.protocol
import org.bitcoins.core.protocol.script.WitnessVersion0
import org.bitcoins.core.util.{Bech32, Bech32Encoding}
import org.bitcoins.testkitcore.gen._
import org.scalacheck.{Prop, Properties}
import scala.annotation.tailrec
import scala.util.{Random, Success}
class Bech32mSpec extends Properties("Bech32mSpec") {
property("split all Bech32m addresses into HRP and data") = {
Prop.forAll(AddressGenerator.bech32mAddress) { address =>
val splitT =
Bech32.splitToHrpAndData(address.value, Bech32Encoding.Bech32m)
splitT.isSuccess
}
}
property("serialization symmetry") = {
Prop.forAll(ScriptGenerators.witnessScriptPubKey.suchThat(
_._1.witnessVersion != WitnessVersion0),
ChainParamsGenerator.networkParams) {
case ((witSPK, _), network) =>
val addr = Bech32mAddress(witSPK, network)
val spk = Bech32mAddress.fromStringToWitSPK(addr.value)
spk == Success(witSPK)
}
}
property("checksum must not work if we modify a char") = {
Prop.forAll(AddressGenerator.bech32mAddress) { addr: Bech32mAddress =>
val old = addr.value
val rand = Math.abs(Random.nextInt())
val idx = rand % old.length
val (f, l) = old.splitAt(idx)
val replacementChar = pickReplacementChar(l.head)
val replaced = s"$f$replacementChar${l.tail}"
//should fail because we replaced a char in the addr, so checksum invalid
Bech32mAddress.fromStringT(replaced).isFailure
}
}
property("must fail if we have a mixed case") = {
Prop.forAllNoShrink(AddressGenerator.bech32mAddress) {
addr: Bech32mAddress =>
val old = addr.value
val replaced = switchCaseRandChar(old)
//should fail because we we switched the case of a random char
val actual = Bech32mAddress.fromStringT(replaced)
actual.isFailure
}
}
@tailrec
private def pickReplacementChar(oldChar: Char): Char = {
val rand = Math.abs(Random.nextInt())
val newChar = Bech32.charset(rand % Bech32.charset.size)
//make sure we don't pick the same char we are replacing in the bech32m address
if (oldChar == newChar) pickReplacementChar(oldChar)
else newChar
}
@tailrec
private def switchCaseRandChar(addr: String): String = {
val rand = Math.abs(Random.nextInt())
val idx = rand % addr.length
val (f, l) = addr.splitAt(idx)
if (l.head.isDigit) {
switchCaseRandChar(addr)
} else {
val middle =
if (l.head.isUpper) {
l.head.toLower
} else {
l.head.toUpper
}
s"$f$middle${l.tail}"
}
}
}

View file

@ -0,0 +1,239 @@
package org.bitcoins.core.protocol
import org.bitcoins.core.number.UInt5
import org.bitcoins.core.protocol.script.{WitnessScriptPubKey, WitnessVersion0}
import org.bitcoins.core.util.{Bech32, Bech32Encoding}
import org.bitcoins.core.util.Bech32Encoding.Bech32m
import org.bitcoins.testkitcore.gen._
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import scala.annotation.tailrec
import scala.util.{Random, Success}
class Bech32mTest extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
behavior of "Bech32m"
it must "split all Bech32m addresses into HRP and data" in {
forAll(AddressGenerator.bech32mAddress) { address =>
val splitT =
Bech32.splitToHrpAndData(address.value, Bech32Encoding.Bech32m)
splitT.isSuccess
}
}
it must "serialization symmetry" in {
forAll(ScriptGenerators.witnessScriptPubKey.suchThat(
_._1.witnessVersion != WitnessVersion0),
ChainParamsGenerator.networkParams) { case ((witSPK, _), network) =>
val addr = Bech32mAddress(witSPK, network)
val spk = Bech32mAddress.fromStringToWitSPK(addr.value)
spk == Success(witSPK)
}
}
it must "checksum must not work if we modify a char" in {
forAll(AddressGenerator.bech32mAddress) { addr: Bech32mAddress =>
val old = addr.value
val rand = Math.abs(Random.nextInt())
val idx = rand % old.length
val (f, l) = old.splitAt(idx)
val replacementChar = pickReplacementChar(l.head)
val replaced = s"$f$replacementChar${l.tail}"
//should fail because we replaced a char in the addr, so checksum invalid
Bech32mAddress.fromStringT(replaced).isFailure
}
}
it must "must fail if we have a mixed case" in {
forAll(AddressGenerator.bech32mAddress) { addr: Bech32mAddress =>
val old = addr.value
val replaced = switchCaseRandChar(old)
//should fail because we we switched the case of a random char
val actual = Bech32mAddress.fromStringT(replaced)
actual.isFailure
}
}
it must "pass valid bech32m test vectors" in {
assert(Bech32.splitToHrpAndData("A1LQFN3A", Bech32m).isSuccess)
assert(Bech32.splitToHrpAndData("a1lqfn3a", Bech32m).isSuccess)
assert(
Bech32
.splitToHrpAndData(
"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6",
Bech32m)
.isSuccess)
assert(
Bech32
.splitToHrpAndData(
"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8",
Bech32m)
.isSuccess)
assert(Bech32.splitToHrpAndData("?1v759aa", Bech32m).isSuccess)
}
it must "pass invalid bech32m test vectors" in {
assert(
Bech32
.splitToHrpAndData(
"an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4",
Bech32m)
.isFailure)
assert(Bech32.splitToHrpAndData("qyrz8wqd2c9m", Bech32m).isFailure)
assert(Bech32.splitToHrpAndData("1qyrz8wqd2c9m", Bech32m).isFailure)
assert(Bech32.splitToHrpAndData("y1b0jsk6g", Bech32m).isFailure)
assert(Bech32.splitToHrpAndData("lt1igcx5c0", Bech32m).isFailure)
assert(Bech32.splitToHrpAndData("in1muywd", Bech32m).isFailure)
assert(Bech32.splitToHrpAndData("mm1crxm3i", Bech32m).isFailure)
assert(Bech32.splitToHrpAndData("M1VUXWEZ", Bech32m).isFailure)
assert(Bech32.splitToHrpAndData("16plkw9", Bech32m).isFailure)
assert(Bech32.splitToHrpAndData("1p2gdwpf", Bech32m).isFailure)
}
it must "get spk from bech32m addresses" in {
assert(
Bech32mAddress
.fromString("BC1PW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KJ9WKRU")
.scriptPubKey == WitnessScriptPubKey.fromAsmHex(
"5114751e76e8199196d454941c45d1b3a323f1433bd6"))
assert(
Bech32mAddress
.fromString(
"tb1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q98lawz")
.scriptPubKey == WitnessScriptPubKey.fromAsmHex(
"51201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"))
assert(
Bech32mAddress
.fromString(
"bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y")
.scriptPubKey == WitnessScriptPubKey.fromAsmHex(
"5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"))
assert(
Bech32mAddress
.fromString("BC1SW50QGDZ25J")
.scriptPubKey == WitnessScriptPubKey.fromAsmHex("6002751e"))
assert(
Bech32mAddress
.fromString("bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs")
.scriptPubKey == WitnessScriptPubKey.fromAsmHex(
"5210751e76e8199196d454941c45d1b3a323"))
assert(
Bech32mAddress
.fromString(
"tb1gqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsescs2hvq")
.scriptPubKey == WitnessScriptPubKey.fromAsmHex(
"5820000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"))
assert(
Bech32mAddress
.fromString(
"tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c")
.scriptPubKey == WitnessScriptPubKey.fromAsmHex(
"5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"))
assert(
Bech32mAddress
.fromString(
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0")
.scriptPubKey == WitnessScriptPubKey.fromAsmHex(
"512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))
}
it must "fail to read invalid bech32m addresses" in {
assert(
Bech32mAddress
.fromStringT(
"tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut")
.isFailure)
assert(
Bech32mAddress
.fromStringT(
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd")
.isFailure)
assert(
Bech32mAddress
.fromStringT(
"tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf")
.isFailure)
assert(
Bech32mAddress
.fromStringT(
"BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL")
.isFailure)
assert(
Bech32mAddress
.fromStringT(
"bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4")
.isFailure)
assert(
Bech32mAddress
.fromStringT(
"BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R")
.isFailure)
assert(Bech32mAddress.fromStringT("bc1pw5dgrnzv").isFailure)
assert(
Bech32mAddress
.fromStringT(
"bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav")
.isFailure)
assert(
Bech32mAddress
.fromStringT("BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P")
.isFailure)
assert(
Bech32mAddress
.fromStringT(
"tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq")
.isFailure)
assert(Bech32mAddress.fromStringT("bc1gmk9yu").isFailure)
}
it must "create the correct checksum for a 0 byte address" in {
val checksum =
Bech32mAddress.createChecksum(BtcHumanReadablePart.bc, Vector(UInt5.zero))
checksum must be(Seq(16, 30, 17, 4, 6, 30).map(i => UInt5(i.toByte)))
checksum.map(ch => Bech32.charset(ch.toInt)).mkString must be("s73yx7")
}
it must "fail to read a segwitV0 spk as a bech32m address" in {
forAll(ScriptGenerators.witnessScriptPubKeyV0,
ChainParamsGenerator.networkParams) { case (witSpkV0, np) =>
assert(Bech32mAddress.fromScriptPubKeyT(witSpkV0._1, np).isFailure)
}
}
it must "fail to read a bech32 address" in {
forAll(AddressGenerator.bech32Address) { bech32 =>
assert(Bech32mAddress.fromStringT(bech32.toString).isFailure)
}
}
@tailrec
private def pickReplacementChar(oldChar: Char): Char = {
val rand = Math.abs(Random.nextInt())
val newChar = Bech32.charset(rand % Bech32.charset.size)
//make sure we don't pick the same char we are replacing in the bech32m address
if (oldChar == newChar) pickReplacementChar(oldChar)
else newChar
}
@tailrec
private def switchCaseRandChar(addr: String): String = {
val rand = Math.abs(Random.nextInt())
val idx = rand % addr.length
val (f, l) = addr.splitAt(idx)
if (l.head.isDigit) {
switchCaseRandChar(addr)
} else {
val middle =
if (l.head.isUpper) {
l.head.toLower
} else {
l.head.toUpper
}
s"$f$middle${l.tail}"
}
}
}

View file

@ -422,10 +422,8 @@ class LnInvoiceUnitTest extends BitcoinSUnitTest {
}
it must "have serialization symmetry for the invoices" in {
forAll(LnInvoiceGen.lnInvoice) { invoice =>
LnInvoice.fromStringT(invoice.toString).get == invoice
}
}

View file

@ -4,6 +4,7 @@ import org.bitcoins.core.config._
import org.bitcoins.core.number.{UInt5, UInt8}
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.script.constant.ScriptConstant
import org.bitcoins.core.util.Bech32Encoding.Bech32m
import org.bitcoins.core.util._
import org.bitcoins.crypto._
import scodec.bits.ByteVector
@ -106,7 +107,7 @@ sealed abstract class Bech32Address extends BitcoinAddress {
}
def verifyChecksum: Boolean = {
Bech32.verifyChecksum(hrp.expand, data ++ checksum)
Bech32.verifyChecksum(hrp.expand, data ++ checksum, Bech32Encoding.Bech32)
}
}
@ -143,7 +144,7 @@ object Bech32Address extends AddressFactory[Bech32Address] {
hrp: BtcHumanReadablePart,
bytes: Vector[UInt5]): Vector[UInt5] = {
val values = hrp.expand ++ bytes
Bech32.createChecksum(values)
Bech32.createChecksum(values, Bech32Encoding.Bech32)
}
/** Tries to convert the given string a to a
@ -181,7 +182,7 @@ object Bech32Address extends AddressFactory[Bech32Address] {
/** Decodes bech32 string to the [[org.bitcoins.core.protocol.BtcHumanReadablePart HumanReadablePart]] & data part */
override def fromString(bech32: String): Bech32Address = {
val bech32T = for {
(hrp, data) <- Bech32.splitToHrpAndData(bech32)
(hrp, data) <- Bech32.splitToHrpAndData(bech32, Bech32Encoding.Bech32)
network = BtcHumanReadablePart.fromString(hrp).network
} yield Bech32Address(network, data)
@ -210,6 +211,142 @@ object Bech32Address extends AddressFactory[Bech32Address] {
}
/** https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
*/
sealed abstract class Bech32mAddress extends BitcoinAddress {
lazy val hrp: BtcHumanReadablePart = BtcHumanReadablePart(networkParameters)
def data: Vector[UInt5]
override def value: String = {
val all: Vector[UInt5] = data ++ checksum
val encoding = Bech32.encode5bitToString(all)
hrp.toString + Bech32.separator + encoding
}
def checksum: Vector[UInt5] = Bech32mAddress.createChecksum(hrp, data)
override def scriptPubKey: WitnessScriptPubKey = {
val spk = Bech32mAddress.fromStringToWitSPK(value).get
require(spk.witnessVersion != WitnessVersion0,
"Use bech32 addresses for segwit v0")
spk
}
override def hash: HashDigest = {
DoubleSha256Digest.empty
}
def expandHrp: Vector[UInt5] = {
hrp.expand
}
def verifyChecksum: Boolean = {
Bech32.verifyChecksum(hrp.expand, data ++ checksum, Bech32Encoding.Bech32m)
}
}
object Bech32mAddress extends AddressFactory[Bech32mAddress] {
private case class Bech32mAddressImpl(
networkParameters: NetworkParameters,
data: Vector[UInt5])
extends Bech32mAddress {
require(verifyChecksum, "checksum did not pass")
require(Try(scriptPubKey).isSuccess, "invalid witness script pub key")
}
def empty(network: NetworkParameters = MainNet): Bech32mAddress =
fromScriptPubKey(P2WSHWitnessSPKV0(EmptyScriptPubKey), network)
def apply(
witSPK: WitnessScriptPubKey,
networkParameters: NetworkParameters): Bech32mAddress = {
//we don't encode the wit version or pushop for program into base5
val prog = UInt8.toUInt8s(witSPK.asmBytes.tail.tail)
val encoded = Bech32.from8bitTo5bit(prog)
val witVersion = witSPK.witnessVersion.version.toInt.toByte
Bech32mAddress(networkParameters, Vector(UInt5(witVersion)) ++ encoded)
}
def apply(
networkParameters: NetworkParameters,
data: Vector[UInt5]): Bech32mAddress = {
Bech32mAddressImpl(networkParameters, data)
}
/** Returns a base 5 checksum as specified by BIP173 */
def createChecksum(
hrp: BtcHumanReadablePart,
bytes: Vector[UInt5]): Vector[UInt5] = {
val values = hrp.expand ++ bytes
Bech32.createChecksum(values, Bech32Encoding.Bech32m)
}
/** Tries to convert the given string a to a
* [[org.bitcoins.core.protocol.script.WitnessScriptPubKey WitnessScriptPubKey]]
*/
def fromStringToWitSPK(string: String): Try[WitnessScriptPubKey] = {
val decoded = Bech32.splitToHrpAndData(string, Bech32m)
decoded.flatMap { case (_, bytes) =>
val (v, _) = (bytes.head, bytes.tail)
val convertedProg = NumberUtil.convertUInt5sToUInt8(bytes.tail)
val progBytes = UInt8.toBytes(convertedProg)
val witVersion = WitnessVersion(v.toInt)
val pushOp = BitcoinScriptUtil.calculatePushOp(progBytes)
witVersion match {
case Some(v) =>
val witSPK = Try(
WitnessScriptPubKey(
List(v.version) ++ pushOp ++ List(ScriptConstant(progBytes))))
witSPK match {
case Success(spk) => Success(spk)
case Failure(err) =>
Failure(
new IllegalArgumentException(
"Failed to decode bech32 into a witSPK: " + err.getMessage))
}
case None =>
Failure(
new IllegalArgumentException(
"Witness version was not valid, got: " + v))
}
}
}
/** Decodes bech32 string to the [[org.bitcoins.core.protocol.BtcHumanReadablePart HumanReadablePart]] & data part */
override def fromString(bech32m: String): Bech32mAddress = {
val bech32T = for {
(hrp, data) <- Bech32.splitToHrpAndData(bech32m, Bech32Encoding.Bech32m)
network = BtcHumanReadablePart.fromString(hrp).network
} yield Bech32mAddress(network, data)
bech32T match {
case Success(bech32m) => bech32m
case Failure(exn) => throw exn
}
}
override def fromScriptPubKeyT(
spk: ScriptPubKey,
np: NetworkParameters): Try[Bech32mAddress] =
spk match {
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
_: P2SHScriptPubKey | _: LockTimeScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | _: WitnessScriptPubKeyV0 |
EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
case witSPK: WitnessScriptPubKey =>
Success(Bech32mAddress(witSPK, np))
}
}
object P2PKHAddress extends AddressFactory[P2PKHAddress] {
private case class P2PKHAddressImpl(
@ -373,6 +510,7 @@ object BitcoinAddress extends AddressFactory[BitcoinAddress] {
.fromStringT(value)
.orElse(P2SHAddress.fromStringT(value))
.orElse(Bech32Address.fromStringT(value))
.orElse(Bech32mAddress.fromStringT(value))
addressT match {
case Success(addr) => addr
@ -389,16 +527,16 @@ object BitcoinAddress extends AddressFactory[BitcoinAddress] {
case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np))
case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np))
case witSPK: WitnessScriptPubKeyV0 => Success(Bech32Address(witSPK, np))
case unassigned: UnassignedWitnessScriptPubKey =>
Success(Bech32mAddress(unassigned, np))
case x @ (_: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey |
_: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey |
_: WitnessCommitment | _: UnassignedWitnessScriptPubKey |
EmptyScriptPubKey) =>
_: WitnessCommitment | EmptyScriptPubKey) =>
Failure(
new IllegalArgumentException(
"Cannot create a address for the scriptPubKey: " + x))
}
}
object Address extends AddressFactory[Address] {

View file

@ -137,7 +137,7 @@ object LnInvoice extends StringFactory[LnInvoice] with BitcoinSLogger {
hrp: LnHumanReadablePart,
data: Vector[UInt5]): Vector[UInt5] = {
val hrpBytes = hrpExpand(hrp)
val u5s = Bech32.createChecksum(hrpBytes ++ data)
val u5s = Bech32.createChecksum(hrpBytes ++ data, Bech32Encoding.Bech32)
u5s
}

View file

@ -1,7 +1,5 @@
package org.bitcoins.core.protocol.ln
import java.nio.charset.Charset
import org.bitcoins.core.config.{MainNet, NetworkParameters}
import org.bitcoins.core.number.{UInt32, UInt5, UInt8}
import org.bitcoins.core.protocol._
@ -13,6 +11,7 @@ import org.bitcoins.core.util.{Bech32, SeqWrapper}
import org.bitcoins.crypto.{CryptoUtil, Sha256Digest, Sha256Hash160Digest}
import scodec.bits.ByteVector
import java.nio.charset.Charset
import scala.annotation.tailrec
/** One of the tagged fields on a Lightning Network invoice
@ -202,6 +201,8 @@ object LnTag {
case _: P2SHAddress => FallbackAddressV.P2SH.u8
case bech32: Bech32Address =>
UInt8(bech32.scriptPubKey.witnessVersion.version.toInt)
case bech32m: Bech32mAddress =>
UInt8(bech32m.scriptPubKey.witnessVersion.version.toInt)
}
}

View file

@ -7,8 +7,31 @@ import scodec.bits.ByteVector
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
/** There exists 2 different kinds of bech32 encodings: bech32 & bech32m
* @see https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
* @see https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki
*/
sealed abstract class Bech32Encoding {
/** The constant that is XORed into the checksum
* @see https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki#bech32m
*/
def constant: Int
}
object Bech32Encoding {
case object Bech32 extends Bech32Encoding {
override val constant = 1
}
case object Bech32m extends Bech32Encoding {
override val constant = 0x2bc830a3
}
}
/** A abstract class representing basic utility functions of Bech32
* For more information on Bech32 please seee BIP173
* For more information on Bech32 please see BIP173
* [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki]]
*/
sealed abstract class Bech32 {
@ -19,9 +42,12 @@ sealed abstract class Bech32 {
/** Creates a checksum for the given byte vector according to
* [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 BIP173]]
*/
def createChecksum(u5s: Vector[UInt5]): Vector[UInt5] = {
def createChecksum(
u5s: Vector[UInt5],
encoding: Bech32Encoding): Vector[UInt5] = {
val z = UInt5.zero
val polymod: Long = polyMod(u5s ++ Array(z, z, z, z, z, z)) ^ 1
val polymod: Long =
polyMod(u5s ++ Array(z, z, z, z, z, z)) ^ encoding.constant
//[(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
val result: Vector[UInt5] = 0
@ -204,7 +230,9 @@ 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[(String, Vector[UInt5])] = {
def splitToHrpAndData(
bech32: String,
encoding: Bech32Encoding): Try[(String, Vector[UInt5])] = {
val sepIndexes = bech32.zipWithIndex.filter { case (sep, _) =>
sep == Bech32.separator
}
@ -242,7 +270,7 @@ sealed abstract class Bech32 {
dataWithCheck <- Bech32.checkDataValidity(dataStr)
hrpU5s = hrpExpand(hrpStr)
dataNoCheck <- {
if (verifyChecksum(hrpU5s, dataWithCheck)) {
if (verifyChecksum(hrpU5s, dataWithCheck, encoding)) {
Success(dataWithCheck.take(dataWithCheck.size - 6))
} else
Failure(
@ -256,19 +284,23 @@ sealed abstract class Bech32 {
def splitToHrpAndData[T <: Bech32HumanReadablePart](
bech32: String,
encoding: Bech32Encoding,
factory: StringFactory[T]): Try[(T, Vector[UInt5])] = {
splitToHrpAndData(bech32).flatMap { case (hrpString, data) =>
splitToHrpAndData(bech32, encoding).flatMap { case (hrpString, data) =>
factory
.fromStringT(hrpString)
.map(hrp => (hrp, data))
}
}
def verifyChecksum(hrp: Seq[UInt5], u5s: Seq[UInt5]): Boolean = {
def verifyChecksum(
hrp: Seq[UInt5],
u5s: Seq[UInt5],
encoding: Bech32Encoding): Boolean = {
val data = hrp ++ u5s
val checksum = Bech32.polyMod(data.toVector)
checksum == 1
checksum == encoding.constant
}
/** Assumes we are given a valid bech32 string */

View file

@ -1,6 +1,7 @@
package org.bitcoins.testkitcore.gen
import org.bitcoins.core.protocol._
import org.bitcoins.core.protocol.script.WitnessVersion0
import org.scalacheck.Gen
/** Created by chris on 6/12/17.
@ -28,11 +29,17 @@ sealed trait AddressGenerator {
addr = Bech32Address(witSPKV0, network)
} yield addr
def bitcoinAddress: Gen[BitcoinAddress] =
Gen.oneOf(p2pkhAddress, p2shAddress, bech32Address)
def bech32mAddress: Gen[Bech32mAddress] =
for {
(witSPK, _) <- ScriptGenerators.witnessScriptPubKey.suchThat(
_._1.witnessVersion != WitnessVersion0)
network <- ChainParamsGenerator.networkParams
} yield Bech32mAddress(witSPK, network)
def address: Gen[Address] =
Gen.oneOf(p2pkhAddress, p2shAddress, bech32Address)
def bitcoinAddress: Gen[BitcoinAddress] =
Gen.oneOf(p2pkhAddress, p2shAddress, bech32Address, bech32mAddress)
def address: Gen[Address] = bitcoinAddress
}
object AddressGenerator extends AddressGenerator

View file

@ -5,6 +5,7 @@ import org.bitcoins.core.protocol.ln.LnTag.NodeIdTag
import org.bitcoins.core.protocol.ln._
import org.bitcoins.core.protocol.ln.node.NodeId
import org.bitcoins.crypto.ECPrivateKey
import org.bitcoins.testkitcore.gen.AddressGenerator._
import org.bitcoins.testkitcore.gen._
import org.scalacheck.Gen
@ -87,7 +88,7 @@ sealed abstract class LnInvoiceGen {
}
def fallbackAddress: Gen[LnTag.FallbackAddressTag] = {
AddressGenerator.address.map { addr =>
Gen.oneOf(p2pkhAddress, p2shAddress, bech32Address).map { addr =>
LnTag.FallbackAddressTag(addr)
}
}