From aa0a6f96b8b9b9072e268ab98f955e0469c91289 Mon Sep 17 00:00:00 2001 From: benthecarman Date: Wed, 27 Jan 2021 13:22:17 -0600 Subject: [PATCH] Bech32 address improvements (#2571) * Bech32 address improvements * Respond to review --- .../bitcoins/core/protocol/Bech32Test.scala | 9 +++++++ .../org/bitcoins/core/protocol/Address.scala | 14 ++++++---- .../core/protocol/BtcHumanReadablePart.scala | 2 +- .../scala/org/bitcoins/core/util/Bech32.scala | 27 ++++++++++++++----- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/Bech32Test.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/Bech32Test.scala index 5b8468bf9b..5f2af29c76 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/Bech32Test.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/Bech32Test.scala @@ -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 { //https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#examples val key = ECPublicKey( diff --git a/core/src/main/scala/org/bitcoins/core/protocol/Address.scala b/core/src/main/scala/org/bitcoins/core/protocol/Address.scala index f77e01ae65..89632736ef 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/Address.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/Address.scala @@ -105,6 +105,10 @@ sealed abstract class Bech32Address extends BitcoinAddress { def expandHrp: Vector[UInt5] = { hrp.expand } + + def verifyChecksum: Boolean = { + Bech32.verifyChecksum(hrp.expand, data ++ checksum) + } } object Bech32Address extends AddressFactory[Bech32Address] { @@ -113,7 +117,7 @@ object Bech32Address extends AddressFactory[Bech32Address] { networkParameters: NetworkParameters, data: Vector[UInt5]) extends Bech32Address { - //require(verifyChecksum(hrp, data), "checksum did not pass") + require(verifyChecksum, s"checksum did not pass $checksum") } def empty(network: NetworkParameters = MainNet): Bech32Address = @@ -192,7 +196,7 @@ object Bech32Address extends AddressFactory[Bech32Address] { spk: ScriptPubKey, np: NetworkParameters): Try[Bech32Address] = spk match { - case witSPK: WitnessScriptPubKey => + case witSPK: WitnessScriptPubKeyV0 => Success(Bech32Address(witSPK, np)) case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey | _: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey | @@ -386,9 +390,9 @@ object BitcoinAddress extends AddressFactory[BitcoinAddress] { spk: ScriptPubKey, np: NetworkParameters): Try[BitcoinAddress] = spk match { - case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np)) - case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np)) - case witSPK: WitnessScriptPubKey => Success(Bech32Address(witSPK, np)) + case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np)) + case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np)) + case witSPK: WitnessScriptPubKeyV0 => Success(Bech32Address(witSPK, np)) case x @ (_: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey | _: ConditionalScriptPubKey | _: NonStandardScriptPubKey | diff --git a/core/src/main/scala/org/bitcoins/core/protocol/BtcHumanReadablePart.scala b/core/src/main/scala/org/bitcoins/core/protocol/BtcHumanReadablePart.scala index 909234073f..b8164973fd 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/BtcHumanReadablePart.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/BtcHumanReadablePart.scala @@ -39,7 +39,7 @@ object BtcHumanReadablePart extends StringFactory[BtcHumanReadablePart] { } override def fromString(str: String): BtcHumanReadablePart = - str match { + str.toLowerCase match { case "bc" => bc case "tb" => tb case "bcrt" => bcrt // Bitcoin Core specific diff --git a/core/src/main/scala/org/bitcoins/core/util/Bech32.scala b/core/src/main/scala/org/bitcoins/core/util/Bech32.scala index b143518c29..32077b2416 100644 --- a/core/src/main/scala/org/bitcoins/core/util/Bech32.scala +++ b/core/src/main/scala/org/bitcoins/core/util/Bech32.scala @@ -135,20 +135,20 @@ sealed abstract class Bech32 { remaining match { case Nil => Success(accum.reverse) case h :: t => - if (!Bech32.charset.contains(h.toLower)) { + if ((h.isUpper && hasLower) || (h.isLower && hasUpper)) { Failure( new IllegalArgumentException( - "Invalid character in data of bech32 address, got: " + h)) + "Cannot have mixed case for bech32 address")) } else { - if ((h.isUpper && hasLower) || (h.isLower && hasUpper)) { + val value = Bech32.charsetReversed(h.toInt) + + if (value == -1) { Failure( new IllegalArgumentException( - "Cannot have mixed case for bech32 address")) + "Invalid character in data of bech32 address, got: " + h)) } else { - val byte = Bech32.charset.indexOf(h.toLower).toByte - loop(remaining = t, - accum = UInt5.fromByte(byte) +: accum, + accum = UInt5.fromByte(value.toByte) +: accum, hasUpper = h.isUpper || hasUpper, 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', 'g', 'f', '2', 't', 'v', 'd', 'w', '0', 's', '3', 'j', 'n', '5', '4', 'k', '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 {