bech32/Tests/Bech32Tests/Bech32Tests.swift

248 lines
9.8 KiB
Swift

//
// Bech32Tests.swift
//
// Created by Evolution Group Ltd on 12.02.2018.
// Copyright © 2018 Evolution Group Ltd. All rights reserved.
//
// Base32 address format for native v0-16 witness outputs implementation
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
// Inspired by Pieter Wuille C++ implementation
import XCTest
@testable import Bech32
fileprivate typealias InvalidChecksum = (bech32: String, error: Bech32.DecodingError)
fileprivate typealias ValidAddressData = (address: String, script: [UInt8])
fileprivate typealias InvalidAddressData = (hrp: String, version: Int, programLen: Int)
fileprivate extension Data {
var hex: String {
return self.map { String(format: "%02hhx", $0) }.joined()
}
}
class Bech32Tests: XCTestCase {
private let _validChecksum: [String] = [
"A12UEL5L",
"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
"split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
"?1ezyfcl"
]
private let _invalidChecksum: [InvalidChecksum] = [
(" 1nwldj5", Bech32.DecodingError.nonPrintableCharacter),
("\u{7f}1axkwrx", Bech32.DecodingError.nonPrintableCharacter),
("an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", Bech32.DecodingError.stringLengthExceeded),
("pzry9x0s0muk", Bech32.DecodingError.noChecksumMarker),
("1pzry9x0s0muk", Bech32.DecodingError.incorrectHrpSize),
("x1b4n0q5v", Bech32.DecodingError.invalidCharacter),
("li1dgmt3", Bech32.DecodingError.incorrectChecksumSize),
("de1lg7wt\u{ff}", Bech32.DecodingError.nonPrintableCharacter),
("10a06t8", Bech32.DecodingError.incorrectHrpSize),
("1qzzfhee", Bech32.DecodingError.incorrectHrpSize)
]
private let _invalidAddress: [String] = [
"tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty",
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5",
"BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2",
"bc1rw5uspcuh",
"bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90",
"BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P",
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7",
"bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du",
"tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv",
"bc1gmk9yu"
]
private let _validAddressData: [ValidAddressData] = [
("BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", [
0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6
]),
("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", [
0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04,
0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78,
0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32,
0x62
]),
("bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", [
0x81, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6,
0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c,
0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6
]),
("BC1SW50QA3JX3S", [
0x90, 0x02, 0x75, 0x1e
]),
("bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", [
0x82, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54,
0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23
]),
("tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", [
0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21,
0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5,
0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64,
0x33
])
]
private let _invalidAddressData: [InvalidAddressData] = [
("BC", 0, 20),
("bc", 0, 21),
("bc", 17, 32),
("bc", 1, 1),
("bc", 16, 41)
]
let bech32 = Bech32()
let addrCoder = SegwitAddrCoder()
func testValidChecksum() {
for valid in _validChecksum {
do {
let decoded = try bech32.decode(valid)
XCTAssertFalse(decoded.hrp.isEmpty, "Empty result for \"\(valid)\"")
let recoded = bech32.encode(decoded.hrp, values: decoded.checksum)
XCTAssert(valid.lowercased() == recoded.lowercased(), "Roundtrip encoding failed: \(valid) != \(recoded)")
} catch {
XCTFail("Error decoding \(valid): \(error.localizedDescription)")
}
}
}
func testInvalidChecksum() {
for invalid in _invalidChecksum {
let checksum = invalid.bech32
let reason = invalid.error
do {
let decoded = try bech32.decode(checksum)
XCTFail("Successfully decoded an invalid checksum \(checksum): \(decoded.checksum.hex)")
} catch let error as Bech32.DecodingError {
XCTAssert(errorsEqual(error, reason), "Decoding error mismatch, got \(error.localizedDescription), expected \(reason.localizedDescription)")
} catch {
XCTFail("Invalid error occured: \(error.localizedDescription)")
}
}
}
func testValidAddress() {
for valid in _validAddressData {
let address = valid.address
let script = Data(valid.script)
var hrp = "bc"
var decoded = try? addrCoder.decode(hrp: hrp, addr: address)
do {
if decoded == nil {
hrp = "tb"
decoded = try addrCoder.decode(hrp: hrp, addr: address)
}
} catch {
XCTFail("Failed to decode \(address)")
continue
}
let scriptPk = segwitPubKey(version: decoded!.version, program: decoded!.program)
XCTAssert(scriptPk == script, "Decoded script mismatch: \(scriptPk.hex) != \(script.hex)")
do {
let recoded = try addrCoder.encode(hrp: hrp, version: decoded!.version, program: decoded!.program)
XCTAssertFalse(recoded.isEmpty, "Recoded string is empty for \(address)")
} catch {
XCTFail("Roundtrip encoding failed for \"\(address)\" with error: \(error.localizedDescription)")
}
}
}
func testInvalidAddress() {
for invalid in _invalidAddress {
do {
let decoded = try addrCoder.decode(hrp: "bc", addr: invalid)
XCTFail("Successfully decoded an invalid address \(invalid) for hrp \"bc\": \(decoded.program.hex)")
} catch {
// OK here :)
}
do {
let decoded = try addrCoder.decode(hrp: "tb", addr: invalid)
XCTFail("Successfully decoded an invalid address \(invalid) for hrp \"tb\": \(decoded.program.hex)")
} catch {
// OK again
}
}
}
func testInvalidAddressEncoding() {
for invalid in _invalidAddressData {
do {
let zeroData = Data(repeating: 0x00, count: invalid.programLen)
let wtf = try addrCoder.encode(hrp: invalid.hrp, version: invalid.version, program: zeroData)
XCTFail("Successfully encoded zero bytes data \(wtf)")
} catch {
// the way it should go
}
}
}
func testAddressEncodingDecodingPerfomance() {
let addressToCode = _validAddressData[0].address
self.measure {
do {
for _ in 0..<10 {
let decoded = try addrCoder.decode(hrp: "bc", addr: addressToCode)
let _ = try addrCoder.encode(hrp: "bc", version: decoded.version, program: decoded.program)
}
} catch {
XCTFail(error.localizedDescription)
return
}
}
}
private func segwitPubKey(version: Int, program: Data) -> Data {
var result = Data()
result.append(version != 0 ? (0x80 | UInt8(version)) : 0x00)
result.append(UInt8(program.count))
result.append(program)
return result
}
private func errorsEqual(_ lhs: Bech32.DecodingError, _ rhs: Bech32.DecodingError) -> Bool {
switch lhs {
case .checksumMismatch:
return rhs == .checksumMismatch
case .incorrectChecksumSize:
return rhs == .incorrectChecksumSize
case .incorrectHrpSize:
return rhs == .incorrectHrpSize
case .invalidCase:
return rhs == .invalidCase
case .invalidCharacter:
return rhs == .invalidCharacter
case .noChecksumMarker:
return rhs == .noChecksumMarker
case .nonUTF8String:
return rhs == .nonUTF8String
case .stringLengthExceeded:
return rhs == .stringLengthExceeded
case .nonPrintableCharacter:
return rhs == .nonPrintableCharacter
}
}
static var allTests = [
("Valid Checksum", testValidChecksum),
("Invalid Checksum", testInvalidChecksum),
("Valid Address", testValidAddress),
("Invalid Address", testInvalidAddress),
("Zero Data", testInvalidAddressEncoding),
("Perfomance", testAddressEncodingDecodingPerfomance)
]
}