mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 22:36:34 +01:00
parent
12bff309c2
commit
b0f7d6f26b
11 changed files with 537 additions and 35 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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] {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue