Bech32 address improvements (#2571)

* Bech32 address improvements

* Respond to review
This commit is contained in:
benthecarman 2021-01-27 13:22:17 -06:00 committed by GitHub
parent 0549fa355a
commit 33245b500e
4 changed files with 39 additions and 13 deletions

View file

@ -29,6 +29,15 @@ class Bech32Test extends BitcoinSUnitTest {
} }
} }
it must "properly read an uppercase bech32 address" in {
val addrStr = "bcrt1qq6w6pu6zq90az9krn53zlkvgyzkyeglzukyepf"
val addrT = Address.fromStringT(addrStr.toUpperCase)
addrT match {
case Success(addr: Bech32Address) => assert(addr.value == addrStr)
case _ => fail()
}
}
it must "follow the example in BIP173" in { it must "follow the example in BIP173" in {
//https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#examples //https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#examples
val key = ECPublicKey( val key = ECPublicKey(

View file

@ -105,6 +105,10 @@ sealed abstract class Bech32Address extends BitcoinAddress {
def expandHrp: Vector[UInt5] = { def expandHrp: Vector[UInt5] = {
hrp.expand hrp.expand
} }
def verifyChecksum: Boolean = {
Bech32.verifyChecksum(hrp.expand, data ++ checksum)
}
} }
object Bech32Address extends AddressFactory[Bech32Address] { object Bech32Address extends AddressFactory[Bech32Address] {
@ -113,7 +117,7 @@ object Bech32Address extends AddressFactory[Bech32Address] {
networkParameters: NetworkParameters, networkParameters: NetworkParameters,
data: Vector[UInt5]) data: Vector[UInt5])
extends Bech32Address { extends Bech32Address {
//require(verifyChecksum(hrp, data), "checksum did not pass") require(verifyChecksum, s"checksum did not pass $checksum")
} }
def empty(network: NetworkParameters = MainNet): Bech32Address = def empty(network: NetworkParameters = MainNet): Bech32Address =
@ -192,7 +196,7 @@ object Bech32Address extends AddressFactory[Bech32Address] {
spk: ScriptPubKey, spk: ScriptPubKey,
np: NetworkParameters): Try[Bech32Address] = np: NetworkParameters): Try[Bech32Address] =
spk match { spk match {
case witSPK: WitnessScriptPubKey => case witSPK: WitnessScriptPubKeyV0 =>
Success(Bech32Address(witSPK, np)) Success(Bech32Address(witSPK, np))
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey | case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey | _: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
@ -386,9 +390,9 @@ object BitcoinAddress extends AddressFactory[BitcoinAddress] {
spk: ScriptPubKey, spk: ScriptPubKey,
np: NetworkParameters): Try[BitcoinAddress] = np: NetworkParameters): Try[BitcoinAddress] =
spk match { spk match {
case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np)) case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np))
case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np)) case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np))
case witSPK: WitnessScriptPubKey => Success(Bech32Address(witSPK, np)) case witSPK: WitnessScriptPubKeyV0 => Success(Bech32Address(witSPK, np))
case x @ (_: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey | case x @ (_: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey |
_: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey | _: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey |
_: ConditionalScriptPubKey | _: NonStandardScriptPubKey | _: ConditionalScriptPubKey | _: NonStandardScriptPubKey |

View file

@ -39,7 +39,7 @@ object BtcHumanReadablePart extends StringFactory[BtcHumanReadablePart] {
} }
override def fromString(str: String): BtcHumanReadablePart = override def fromString(str: String): BtcHumanReadablePart =
str match { str.toLowerCase match {
case "bc" => bc case "bc" => bc
case "tb" => tb case "tb" => tb
case "bcrt" => bcrt // Bitcoin Core specific case "bcrt" => bcrt // Bitcoin Core specific

View file

@ -135,20 +135,20 @@ sealed abstract class Bech32 {
remaining match { remaining match {
case Nil => Success(accum.reverse) case Nil => Success(accum.reverse)
case h :: t => case h :: t =>
if (!Bech32.charset.contains(h.toLower)) { if ((h.isUpper && hasLower) || (h.isLower && hasUpper)) {
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
"Invalid character in data of bech32 address, got: " + h)) "Cannot have mixed case for bech32 address"))
} else { } else {
if ((h.isUpper && hasLower) || (h.isLower && hasUpper)) { val value = Bech32.charsetReversed(h.toInt)
if (value == -1) {
Failure( Failure(
new IllegalArgumentException( new IllegalArgumentException(
"Cannot have mixed case for bech32 address")) "Invalid character in data of bech32 address, got: " + h))
} else { } else {
val byte = Bech32.charset.indexOf(h.toLower).toByte
loop(remaining = t, loop(remaining = t,
accum = UInt5.fromByte(byte) +: accum, accum = UInt5.fromByte(value.toByte) +: accum,
hasUpper = h.isUpper || hasUpper, hasUpper = h.isUpper || hasUpper,
hasLower = h.isLower || hasLower) hasLower = h.isLower || hasLower)
} }
@ -305,6 +305,19 @@ object Bech32 extends Bech32 {
val charset: Vector[Char] = Vector('q', 'p', 'z', 'r', 'y', '9', 'x', '8', val charset: Vector[Char] = Vector('q', 'p', 'z', 'r', 'y', '9', 'x', '8',
'g', 'f', '2', 't', 'v', 'd', 'w', '0', 's', '3', 'j', 'n', '5', '4', 'k', 'g', 'f', '2', 't', 'v', 'd', 'w', '0', 's', '3', 'j', 'n', '5', '4', 'k',
'h', 'c', 'e', '6', 'm', 'u', 'a', '7', 'l') 'h', 'c', 'e', '6', 'm', 'u', 'a', '7', 'l')
/** The Bech32 character set for decoding.
* @see https://github.com/sipa/bech32/blob/master/ref/c%2B%2B/bech32.cpp#L33
*/
val charsetReversed: Vector[Int] = Vector(
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, -1, 10, 17, 21, 20, 26, 30, 7,
5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31,
27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, -1,
29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11,
28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
)
} }
abstract class Bech32HumanReadablePart { abstract class Bech32HumanReadablePart {