Initial commit
This commit is contained in:
commit
e5e1353b78
|
@ -0,0 +1,4 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright 2018 Evolution Group Limited
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,22 @@
|
|||
// swift-tools-version:4.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Bech32",
|
||||
products: [
|
||||
.library(
|
||||
name: "Bech32",
|
||||
targets: ["Bech32"]),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Bech32",
|
||||
dependencies: [],
|
||||
path: "./Sources"),
|
||||
.testTarget(
|
||||
name: "Bech32Tests",
|
||||
dependencies: ["Bech32"]),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
# Bech32
|
||||
|
||||
Base32 address format for native v0-16 witness outputs implementation
|
||||
|
||||
[See BIP173 proposal on bitcoin repo](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)
|
||||
|
||||
Inspired by Pieter Wuille C++ reference implementation
|
||||
|
||||
### Install
|
||||
|
||||
- Drop files into project
|
||||
- Correct Bech32Tests `@testable import` statement
|
||||
|
||||
### Check
|
||||
|
||||
Swift Package Manager is needed to run tests and build .xcodeproj.
|
||||
If SPM (v4) is installed then one can type:
|
||||
|
||||
git clone https://github.com/0xDEADP00L/Bech32.git
|
||||
cd Bech32
|
||||
swift test
|
|
@ -0,0 +1,183 @@
|
|||
//
|
||||
// Bech32.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 Foundation
|
||||
|
||||
/// Bech32 checksum implementation
|
||||
public class Bech32 {
|
||||
private let gen: [UInt32] = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
|
||||
/// Bech32 checksum delimiter
|
||||
private let checksumMarker: String = "1"
|
||||
/// Bech32 character set for encoding
|
||||
private let encCharset: Data = "qpzry9x8gf2tvdw0s3jn54khce6mua7l".data(using: .utf8)!
|
||||
/// Bech32 character set for decoding
|
||||
private let decCharset: [Int8] = [
|
||||
-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
|
||||
]
|
||||
|
||||
/// Find the polynomial with value coefficients mod the generator as 30-bit.
|
||||
private func polymod(_ values: Data) -> UInt32 {
|
||||
var chk: UInt32 = 1
|
||||
for v in values {
|
||||
let top = (chk >> 25)
|
||||
chk = (chk & 0x1ffffff) << 5 ^ UInt32(v)
|
||||
for i: UInt8 in 0..<5 {
|
||||
chk ^= ((top >> i) & 1) == 0 ? 0 : gen[Int(i)]
|
||||
}
|
||||
}
|
||||
return chk
|
||||
}
|
||||
|
||||
/// Expand a HRP for use in checksum computation.
|
||||
private func expandHrp(_ hrp: String) -> Data {
|
||||
guard let hrpBytes = hrp.data(using: .utf8) else { return Data() }
|
||||
var result = Data(repeating: 0x00, count: hrpBytes.count*2+1)
|
||||
for (i, c) in hrpBytes.enumerated() {
|
||||
result[i] = c >> 5
|
||||
result[i + hrpBytes.count + 1] = c & 0x1f
|
||||
}
|
||||
result[hrp.count] = 0
|
||||
return result
|
||||
}
|
||||
|
||||
/// Verify checksum
|
||||
private func verifyChecksum(hrp: String, checksum: Data) -> Bool {
|
||||
var data = expandHrp(hrp)
|
||||
data.append(checksum)
|
||||
return polymod(data) == 1
|
||||
}
|
||||
|
||||
/// Create checksum
|
||||
private func createChecksum(hrp: String, values: Data) -> Data {
|
||||
var enc = expandHrp(hrp)
|
||||
enc.append(values)
|
||||
enc.append(Data(repeating: 0x00, count: 6))
|
||||
let mod: UInt32 = polymod(enc) ^ 1
|
||||
var ret: Data = Data(repeating: 0x00, count: 6)
|
||||
for i in 0..<6 {
|
||||
ret[i] = UInt8((mod >> (5 * (5 - i))) & 31)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
/// Encode Bech32 string
|
||||
public func encode(_ hrp: String, values: Data) -> String {
|
||||
let checksum = createChecksum(hrp: hrp, values: values)
|
||||
var combined = values
|
||||
combined.append(checksum)
|
||||
guard let hrpBytes = hrp.data(using: .utf8) else { return "" }
|
||||
var ret = hrpBytes
|
||||
ret.append("1".data(using: .utf8)!)
|
||||
for i in combined {
|
||||
ret.append(encCharset[Int(i)])
|
||||
}
|
||||
return String(data: ret, encoding: .utf8) ?? ""
|
||||
}
|
||||
|
||||
/// Decode Bech32 string
|
||||
public func decode(_ str: String) throws -> (hrp: String, checksum: Data) {
|
||||
guard let strBytes = str.data(using: .utf8) else {
|
||||
throw DecodingError.nonUTF8String
|
||||
}
|
||||
guard strBytes.count <= 90 else {
|
||||
throw DecodingError.stringLengthExceeded
|
||||
}
|
||||
var lower: Bool = false
|
||||
var upper: Bool = false
|
||||
for c in strBytes {
|
||||
// printable range
|
||||
if c < 33 || c > 126 {
|
||||
throw DecodingError.nonPrintableCharacter
|
||||
}
|
||||
// 'a' to 'z'
|
||||
if c >= 97 && c <= 122 {
|
||||
lower = true
|
||||
}
|
||||
// 'A' to 'Z'
|
||||
if c >= 65 && c <= 90 {
|
||||
upper = true
|
||||
}
|
||||
}
|
||||
if lower && upper {
|
||||
throw DecodingError.invalidCase
|
||||
}
|
||||
guard let pos = str.range(of: checksumMarker, options: .backwards)?.lowerBound else {
|
||||
throw DecodingError.noChecksumMarker
|
||||
}
|
||||
let intPos: Int = str.distance(from: str.startIndex, to: pos)
|
||||
guard intPos >= 1 else {
|
||||
throw DecodingError.incorrectHrpSize
|
||||
}
|
||||
guard intPos + 7 <= str.count else {
|
||||
throw DecodingError.incorrectChecksumSize
|
||||
}
|
||||
let vSize: Int = str.count - 1 - intPos
|
||||
var values: Data = Data(repeating: 0x00, count: vSize)
|
||||
for i in 0..<vSize {
|
||||
let c = strBytes[i + intPos + 1]
|
||||
let decInt = decCharset[Int(c)]
|
||||
if decInt == -1 {
|
||||
throw DecodingError.invalidCharacter
|
||||
}
|
||||
values[i] = UInt8(decInt)
|
||||
}
|
||||
let hrp = String(str[..<pos]).lowercased()
|
||||
guard verifyChecksum(hrp: hrp, checksum: values) else {
|
||||
throw DecodingError.checksumMismatch
|
||||
}
|
||||
return (hrp, Data(values[..<(vSize-6)]))
|
||||
}
|
||||
}
|
||||
|
||||
extension Bech32 {
|
||||
public enum DecodingError: LocalizedError {
|
||||
case nonUTF8String
|
||||
case nonPrintableCharacter
|
||||
case invalidCase
|
||||
case noChecksumMarker
|
||||
case incorrectHrpSize
|
||||
case incorrectChecksumSize
|
||||
case stringLengthExceeded
|
||||
|
||||
case invalidCharacter
|
||||
case checksumMismatch
|
||||
|
||||
var localizedDescription: String {
|
||||
switch self {
|
||||
case .checksumMismatch:
|
||||
return "Checksum doesn't match"
|
||||
case .incorrectChecksumSize:
|
||||
return "Checksum size too low"
|
||||
case .incorrectHrpSize:
|
||||
return "Human-readable-part is too small or empty"
|
||||
case .invalidCase:
|
||||
return "String contains mixed case characters"
|
||||
case .invalidCharacter:
|
||||
return "Invalid character met on decoding"
|
||||
case .noChecksumMarker:
|
||||
return "Checksum delimiter not found"
|
||||
case .nonPrintableCharacter:
|
||||
return "Non printable character in input string"
|
||||
case .nonUTF8String:
|
||||
return "String cannot be decoded by utf8 decoder"
|
||||
case .stringLengthExceeded:
|
||||
return "Input string is too long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
//
|
||||
// SegwitAddrCoder.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 Foundation
|
||||
|
||||
/// Segregated Witness Address encoder/decoder
|
||||
public class SegwitAddrCoder {
|
||||
private let bech32 = Bech32()
|
||||
|
||||
/// Convert from one power-of-2 number base to another
|
||||
private func convertBits(from: Int, to: Int, pad: Bool, idata: Data) throws -> Data {
|
||||
var acc: Int = 0
|
||||
var bits: Int = 0
|
||||
let maxv: Int = (1 << to) - 1
|
||||
let maxAcc: Int = (1 << (from + to - 1)) - 1
|
||||
var odata = Data()
|
||||
for ibyte in idata {
|
||||
acc = ((acc << from) | Int(ibyte)) & maxAcc
|
||||
bits += from
|
||||
while bits >= to {
|
||||
bits -= to
|
||||
odata.append(UInt8((acc >> bits) & maxv))
|
||||
}
|
||||
}
|
||||
if pad {
|
||||
if bits != 0 {
|
||||
odata.append(UInt8((acc << (to - bits)) & maxv))
|
||||
}
|
||||
} else if (bits >= from || ((acc << (to - bits)) & maxv) != 0) {
|
||||
throw CoderError.bitsConversionFailed
|
||||
}
|
||||
return odata
|
||||
}
|
||||
|
||||
/// Decode segwit address
|
||||
public func decode(hrp: String, addr: String) throws -> (version: Int, program: Data) {
|
||||
let dec = try bech32.decode(addr)
|
||||
guard dec.hrp == hrp else {
|
||||
throw CoderError.hrpMismatch(dec.hrp, hrp)
|
||||
}
|
||||
guard dec.checksum.count >= 1 else {
|
||||
throw CoderError.checksumSizeTooLow
|
||||
}
|
||||
let conv = try convertBits(from: 5, to: 8, pad: false, idata: dec.checksum.advanced(by: 1))
|
||||
guard conv.count >= 2 && conv.count <= 40 else {
|
||||
throw CoderError.dataSizeMismatch(conv.count)
|
||||
}
|
||||
guard dec.checksum[0] <= 16 else {
|
||||
throw CoderError.segwitVersionNotSupported(dec.checksum[0])
|
||||
}
|
||||
if dec.checksum[0] == 0 && conv.count != 20 && conv.count != 32 {
|
||||
throw CoderError.segwitV0ProgramSizeMismatch(conv.count)
|
||||
}
|
||||
return (Int(dec.checksum[0]), conv)
|
||||
}
|
||||
|
||||
/// Encode segwit address
|
||||
public func encode(hrp: String, version: Int, program: Data) throws -> String {
|
||||
var enc = Data([UInt8(version)])
|
||||
enc.append(try convertBits(from: 8, to: 5, pad: true, idata: program))
|
||||
let result = bech32.encode(hrp, values: enc)
|
||||
guard let _ = try? decode(hrp: hrp, addr: result) else {
|
||||
throw CoderError.encodingCheckFailed
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension SegwitAddrCoder {
|
||||
public enum CoderError: LocalizedError {
|
||||
case bitsConversionFailed
|
||||
case hrpMismatch(String, String)
|
||||
case checksumSizeTooLow
|
||||
|
||||
case dataSizeMismatch(Int)
|
||||
case segwitVersionNotSupported(UInt8)
|
||||
case segwitV0ProgramSizeMismatch(Int)
|
||||
|
||||
case encodingCheckFailed
|
||||
|
||||
var localizedDescription: String {
|
||||
switch self {
|
||||
case .bitsConversionFailed:
|
||||
return "Failed to perform bits conversion"
|
||||
case .checksumSizeTooLow:
|
||||
return "Checksum size is too low"
|
||||
case .dataSizeMismatch(let size):
|
||||
return "Program size \(size) does not meet required range 2...40"
|
||||
case .encodingCheckFailed:
|
||||
return "Failed to check result after encoding"
|
||||
case .hrpMismatch(let got, let expected):
|
||||
return "Human-readable-part \"\(got)\" does not match requested \"\(expected)\""
|
||||
case .segwitV0ProgramSizeMismatch(let size):
|
||||
return "Segwit program size \(size) does not meet version 0 requirments"
|
||||
case .segwitVersionNotSupported(let version):
|
||||
return "Segwit version \(version) is not supported by this decoder"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
//
|
||||
// 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)
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import XCTest
|
||||
@testable import Bech32Tests
|
||||
|
||||
XCTMain([
|
||||
testCase(Bech32Tests.allTests),
|
||||
])
|
Loading…
Reference in New Issue