mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-23 06:45:21 +01:00
Bech32 address improvements (#2571)
* Bech32 address improvements * Respond to review
This commit is contained in:
parent
0549fa355a
commit
33245b500e
4 changed files with 39 additions and 13 deletions
|
@ -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(
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Reference in a new issue