mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-20 10:13:26 +01:00
Adding properties to make sure address from string fails if we have mixed case or we replace a random char in the string
This commit is contained in:
parent
661b25acbb
commit
e3311b6694
@ -128,7 +128,7 @@ sealed abstract class Bech32Address extends BitcoinAddress {
|
||||
val checksum = Bech32Address.createChecksum(hrp,data)
|
||||
val all = data ++ checksum
|
||||
val encoding = Bech32Address.encodeToString(all)
|
||||
hrp.toString + "1" + encoding
|
||||
hrp.toString + Bech32Address.separator + encoding
|
||||
}
|
||||
|
||||
override def scriptPubKey: WitnessScriptPubKey = {
|
||||
@ -142,9 +142,10 @@ sealed abstract class Bech32Address extends BitcoinAddress {
|
||||
object Bech32Address {
|
||||
private case class Bech32AddressImpl(hrp: HumanReadablePart, data: Seq[UInt8]) extends Bech32Address
|
||||
|
||||
private val logger = BitcoinSLogger.logger
|
||||
/** Separator used to separate the hrp & data parts of a bech32 addr */
|
||||
val separator = '1'
|
||||
|
||||
def isValid(bytes: Seq[Byte]): Boolean = ???
|
||||
private val logger = BitcoinSLogger.logger
|
||||
|
||||
def apply(witSPK: WitnessScriptPubKey,
|
||||
networkParameters: NetworkParameters): Try[Bech32Address] = {
|
||||
@ -155,8 +156,8 @@ object Bech32Address {
|
||||
case _: MainNet => bc
|
||||
case _: TestNet3 | _: RegTest => tb
|
||||
}
|
||||
//add witversion
|
||||
encoded.map(e => Bech32Address(hrp,Seq(UInt8.zero) ++ e))
|
||||
val witVersion = witSPK.witnessVersion.version.underlying.toShort
|
||||
encoded.map(e => Bech32Address(hrp,Seq(UInt8(witVersion)) ++ e))
|
||||
}
|
||||
|
||||
|
||||
@ -220,7 +221,7 @@ object Bech32Address {
|
||||
private val u32Five = UInt32(5)
|
||||
private val u32Eight = UInt32(8)
|
||||
/** The separator between the hrp and payload of bech 32 */
|
||||
private val separator = '1'
|
||||
|
||||
/** Converts a byte array from base 8 to base 5 */
|
||||
def encode(bytes: Seq[UInt8]): Try[Seq[UInt8]] = {
|
||||
NumberUtil.convertUInt8s(bytes,u32Eight,u32Five,true)
|
||||
|
@ -491,9 +491,11 @@ sealed trait WitnessScriptPubKey extends ScriptPubKey {
|
||||
object WitnessScriptPubKey {
|
||||
|
||||
/** Witness scripts must begin with one of these operations, see BIP141 */
|
||||
private val validFirstOps: Seq[ScriptNumberOperation] = Seq(OP_0,OP_1,OP_2,OP_3,OP_4,OP_5,OP_6, OP_7, OP_8,
|
||||
val validWitVersions: Seq[ScriptNumberOperation] = Seq(OP_0,OP_1,OP_2,OP_3,OP_4,OP_5,OP_6, OP_7, OP_8,
|
||||
OP_9, OP_10, OP_11, OP_12, OP_13, OP_14, OP_15, OP_16)
|
||||
|
||||
val unassignedWitVersions = validWitVersions.tail
|
||||
|
||||
def apply(asm: Seq[ScriptToken]): Option[WitnessScriptPubKey] = fromAsm(asm)
|
||||
|
||||
def fromAsm(asm: Seq[ScriptToken]): Option[WitnessScriptPubKey] = asm match {
|
||||
@ -510,7 +512,7 @@ object WitnessScriptPubKey {
|
||||
val bytes = asm.flatMap(_.bytes)
|
||||
val firstOp = asm.headOption
|
||||
if (bytes.size < 4 || bytes.size > 42) false
|
||||
else if (!validFirstOps.contains(firstOp.getOrElse(OP_1NEGATE))) false
|
||||
else if (!validWitVersions.contains(firstOp.getOrElse(OP_1NEGATE))) false
|
||||
else if (asm(1).toLong + 2 == bytes.size) true
|
||||
else false
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package org.bitcoins.core.protocol
|
||||
|
||||
import org.bitcoins.core.gen.{AddressGenerator, ChainParamsGenerator, ScriptGenerators}
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.scalacheck.{Gen, Prop, Properties}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Random, Success}
|
||||
|
||||
class Bech32Spec extends Properties("Bech32Spec") {
|
||||
private val logger = BitcoinSLogger.logger
|
||||
|
||||
property("serialization symmetry") = {
|
||||
Prop.forAll(ScriptGenerators.witnessScriptPubKeyV0,ChainParamsGenerator.networkParams) { case ((witSPK,_),network) =>
|
||||
@ -16,15 +19,47 @@ class Bech32Spec extends Properties("Bech32Spec") {
|
||||
}
|
||||
|
||||
property("checksum must not work if we modify a char") = {
|
||||
Prop.forAll(AddressGenerator.bech32Address) { case addr : Bech32Address =>
|
||||
val old = addr. value
|
||||
Prop.forAll(AddressGenerator.bech32Address) { case addr: Bech32Address =>
|
||||
val old = addr.value
|
||||
val rand = Math.abs(Random.nextInt)
|
||||
val idx = rand % old.size
|
||||
val replacementChar = Bech32Address.charset(rand % Bech32Address.charset.size)
|
||||
val (f,l) = old.splitAt(idx)
|
||||
val replacementChar = pickReplacementChar(l.head)
|
||||
val replaced = f ++ Seq(replacementChar) ++ l.tail
|
||||
//should fail because we replaced a char in the addr, so checksum invalid
|
||||
Bech32Address.fromString(replaced).isFailure
|
||||
}
|
||||
}
|
||||
|
||||
property("must fail if we have a mixed case") = {
|
||||
Prop.forAllNoShrink(AddressGenerator.bech32Address) { case addr: Bech32Address =>
|
||||
val old = addr.value
|
||||
val replaced = switchCaseRandChar(old)
|
||||
//should fail because we we switched the case of a random char
|
||||
Bech32Address.fromString(replaced).isFailure
|
||||
}
|
||||
}
|
||||
|
||||
@tailrec
|
||||
private def pickReplacementChar(oldChar: Char): Char = {
|
||||
val rand = Math.abs(Random.nextInt)
|
||||
val newChar = Bech32Address.charset(rand % Bech32Address.charset.size)
|
||||
//make sure we don't pick the same char we are replacing in the bech32 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.size
|
||||
val (f,l) = addr.splitAt(idx)
|
||||
if (l.head.isDigit) {
|
||||
switchCaseRandChar(addr)
|
||||
} else if (l.head.isUpper) {
|
||||
f ++ Seq(l.head.toLower) ++ l.tail
|
||||
} else {
|
||||
f ++ Seq(l.head.toUpper) ++ l.tail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user