Bech32.swift (11675B)
1 // Copyright (c) 2017 Alex Bosworth 2 // Copyright (c) 2017 Pieter Wuille 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 // 22 23 import Foundation 24 25 extension String { 26 func lastIndex(of string: String) -> Int? { 27 guard let range = self.range(of: string, options: .backwards) else { return nil } 28 29 return self.distance(from: startIndex, to: range.lowerBound) 30 } 31 } 32 33 let CHARSET = byteConvert(string: "qpzry9x8gf2tvdw0s3jn54khce6mua7l") 34 let GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] 35 36 func polymod(_ values: [Int]) -> Int { 37 return values.reduce(1) { chk, value in 38 let top = chk >> 25 39 40 return (Int()..<5).reduce((chk & 0x1ffffff) << 5 ^ value) { chk, i in 41 guard (top >> i) & 1 > Int() else { return chk } 42 43 return chk ^ GENERATOR[i] 44 } 45 } 46 } 47 48 func hrpExpand(_ hrp: [UInt8]) -> [UInt8] { 49 return (Int()..<hrp.count).map { hrp[$0] >> 5 } + [UInt8()] + (Int()..<hrp.count).map { hrp[$0] & 31 } 50 } 51 52 func verifyChecksum(hrp: [UInt8], data: [UInt8]) -> Bool { 53 return polymod((hrpExpand(hrp) + data).map { Int($0) }) == 1 54 } 55 56 func createChecksum(hrp: [UInt8], data: [UInt8]) -> [UInt8] { 57 let values = (hrpExpand(hrp) + data + Array(repeating: UInt8(), count: 6)).map { Int($0) } 58 let mod: Int = polymod(values) ^ 1 59 60 return (Int()..<6).map { (mod >> (5 * (5 - $0))) & 31 }.map { UInt8($0) } 61 } 62 63 func byteConvert(string: String) -> [UInt8] { 64 return string.map { String($0).unicodeScalars.first?.value }.flatMap { $0 }.map { UInt8($0) } 65 } 66 67 func stringConvert(bytes: [UInt8]) -> String { 68 return bytes.reduce(String(), { $0 + String(format: "%c", $1)}) 69 } 70 71 func encode(hrp: [UInt8], data: [UInt8]) -> String { 72 let checksum = createChecksum(hrp: hrp, data: data) 73 74 return stringConvert(bytes: hrp) + "1" + stringConvert(bytes: (data + checksum).map { CHARSET[Int($0)] }) 75 } 76 77 enum DecodeBech32Error: Error { 78 case caseMixing 79 case inconsistentHrp 80 case invalidAddress 81 case invalidBits 82 case invalidCharacter(String) 83 case invalidChecksum 84 case invalidPayToHashLength 85 case invalidVersion 86 case missingSeparator 87 case missingVersion 88 89 var localizedDescription: String { 90 switch self { 91 case .caseMixing: 92 return "Mixed case characters are not allowed" 93 94 case .inconsistentHrp: 95 return "Internally inconsistent HRP" 96 97 case .invalidAddress: 98 return "Address is not a valid type" 99 100 case .invalidBits: 101 return "Bits are not valid" 102 103 case .invalidCharacter(let char): 104 return "Character \"\(char)\" is not valid" 105 106 case .invalidChecksum: 107 return "Checksum failed to verify data" 108 109 case .invalidPayToHashLength: 110 return "Unknown hash length for encoded output payload hash" 111 112 case .invalidVersion: 113 return "Invalid version number" 114 115 case .missingSeparator: 116 return "Missing address data separator" 117 118 case .missingVersion: 119 return "Missing address version" 120 } 121 } 122 } 123 124 public func decodeBech32(bechString: String) throws -> (hrp: [UInt8], data: [UInt8]) { 125 let bechBytes = byteConvert(string: bechString) 126 127 guard !(bechBytes.contains() { $0 < 33 && $0 > 126 }) else { throw DecodeBech32Error.invalidCharacter(bechString) } 128 129 let hasLower = bechBytes.contains() { $0 >= 97 && $0 <= 122 } 130 let hasUpper = bechBytes.contains() { $0 >= 65 && $0 <= 90 } 131 132 if hasLower && hasUpper { throw DecodeBech32Error.caseMixing } 133 134 let bechString = bechString.lowercased() 135 136 guard let pos = bechString.lastIndex(of: "1") else { throw DecodeBech32Error.missingSeparator } 137 138 if pos < 1 || pos + 7 > bechString.count { 139 throw DecodeBech32Error.missingSeparator 140 } 141 142 let bechStringBytes = byteConvert(string: bechString) 143 let hrp = byteConvert(string: bechString.substring(to: bechString.index(bechString.startIndex, offsetBy: pos))) 144 145 let data: [UInt8] = try ((pos + 1)..<bechStringBytes.count).map { i in 146 guard let d = CHARSET.firstIndex(of: bechStringBytes[i]) else { 147 throw DecodeBech32Error.invalidCharacter(stringConvert(bytes: [bechStringBytes[i]])) 148 } 149 150 return UInt8(d) 151 } 152 153 guard verifyChecksum(hrp: hrp, data: data) else { throw DecodeBech32Error.invalidChecksum } 154 155 return (hrp: hrp, data: Array(data[Int()..<data.count - 6])) 156 } 157 158 func convertbits(data: [UInt8], fromBits: Int, toBits: Int, pad: Bool) throws -> [UInt8] { 159 var acc = Int() 160 var bits = UInt8() 161 let maxv = (1 << toBits) - 1 162 163 let converted: [[Int]] = try data.map { value in 164 if (value < 0 || (UInt8(Int(value) >> fromBits)) != 0) { 165 throw DecodeBech32Error.invalidCharacter(stringConvert(bytes: [value])) 166 } 167 168 acc = (acc << fromBits) | Int(value) 169 bits += UInt8(fromBits) 170 171 var values = [Int]() 172 173 while bits >= UInt8(toBits) { 174 bits -= UInt8(toBits) 175 values += [(acc >> Int(bits)) & maxv] 176 } 177 178 return values 179 } 180 181 let padding = pad && bits > UInt8() ? [acc << (toBits - Int(bits)) & maxv] : [] 182 183 if !pad && (bits >= UInt8(fromBits) || acc << (toBits - Int(bits)) & maxv > Int()) { 184 throw DecodeBech32Error.invalidBits 185 } 186 187 return ((converted.flatMap { $0 }) + padding).map { UInt8($0) } 188 } 189 190 func encode(hrp: [UInt8], version: UInt8, program: [UInt8]) throws -> String { 191 let address = try encode(hrp: hrp, data: [version] + convertbits(data: program, fromBits: 8, toBits: 5, pad: true)) 192 193 // Confirm encoded address parses without error 194 let _ = try decodeAddress(hrp: hrp, address: address) 195 196 return address 197 } 198 199 func decodeAddress(hrp: [UInt8], address: String) throws -> (version: UInt8, program: [UInt8]) { 200 let decoded = try decodeBech32(bechString: address) 201 202 // Confirm decoded address matches expected type 203 guard stringConvert(bytes: decoded.hrp) == stringConvert(bytes: hrp) else { throw DecodeBech32Error.inconsistentHrp } 204 205 // Confirm version byte is present 206 guard let versionByte = decoded.data.first else { throw DecodeBech32Error.missingVersion } 207 208 // Confirm version byte is within the acceptable range 209 guard !decoded.data.isEmpty && versionByte <= 16 else { throw DecodeBech32Error.invalidVersion } 210 211 let program = try convertbits(data: Array(decoded.data[1..<decoded.data.count]), fromBits: 5, toBits: 8, pad: false) 212 213 // Confirm program is a valid length 214 guard program.count > 1 && program.count < 41 else { throw DecodeBech32Error.invalidAddress } 215 216 if versionByte == UInt8() { 217 // Confirm program is a known byte length (20 for pkhash, 32 for scripthash) 218 guard program.count == 20 || program.count == 32 else { throw DecodeBech32Error.invalidPayToHashLength } 219 } 220 221 return (version: versionByte, program: program) 222 } 223 224 func segwitScriptPubKey(version: UInt8, program: [UInt8]) -> [UInt8] { 225 return [version > UInt8() ? version + 0x50 : UInt8(), UInt8(program.count)] + program 226 } 227 228 229 /* 230 class TestBech32: XCTestCase { 231 func testInvalidAddresses() { 232 let INVALID_ADDRESS = [ 233 "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", 234 "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", 235 "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", 236 "bc1rw5uspcuh", 237 "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", 238 "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", 239 "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", 240 "tb1pw508d6qejxtdg4y5r3zarqfsj6c3", 241 "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", 242 ] 243 244 INVALID_ADDRESS.forEach { test in 245 ["bc", "tb"].forEach { type in 246 do { 247 let _ = try decodeAddress(hrp: byteConvert(string: type), address: test) 248 249 XCTFail("Expected invalid address: \(test)") 250 } catch { 251 return 252 } 253 } 254 } 255 } 256 257 func testChecksums() { 258 let VALID_CHECKSUM: [String] = [ 259 "A12UEL5L", 260 "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", 261 "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", 262 "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", 263 "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w" 264 ] 265 266 do { 267 try VALID_CHECKSUM.forEach { test in 268 let _ = try decodeBech32(bechString: test) 269 } 270 } catch { 271 XCTFail(error.localizedDescription) 272 } 273 } 274 275 func testValidAddresses() { 276 let VALID_BC_ADDRESSES: [String: (decoded: [UInt8], type: String)] = [ 277 "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4": ( 278 decoded: [ 279 0x00, 0x14, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 280 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 281 ], 282 type: "bc" 283 ), 284 285 "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7": ( 286 decoded: [ 287 0x00, 0x20, 0x18, 0x63, 0x14, 0x3c, 0x14, 0xc5, 0x16, 0x68, 0x04, 288 0xbd, 0x19, 0x20, 0x33, 0x56, 0xda, 0x13, 0x6c, 0x98, 0x56, 0x78, 289 0xcd, 0x4d, 0x27, 0xa1, 0xb8, 0xc6, 0x32, 0x96, 0x04, 0x90, 0x32, 290 0x62 291 ], 292 type: "tb" 293 ), 294 295 "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx": ( 296 decoded: [ 297 0x51, 0x28, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 298 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6, 299 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 0x94, 0x1c, 300 0x45, 0xd1, 0xb3, 0xa3, 0x23, 0xf1, 0x43, 0x3b, 0xd6 301 ], 302 type: "bc" 303 ), 304 305 "BC1SW50QA3JX3S": (decoded: [0x60, 0x02, 0x75, 0x1e], type: "bc"), 306 307 "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj": ( 308 decoded: [ 309 0x52, 0x10, 0x75, 0x1e, 0x76, 0xe8, 0x19, 0x91, 0x96, 0xd4, 0x54, 310 0x94, 0x1c, 0x45, 0xd1, 0xb3, 0xa3, 0x23 311 ], 312 type: "bc" 313 ), 314 315 "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy": ( 316 decoded: [ 317 0x00, 0x20, 0x00, 0x00, 0x00, 0xc4, 0xa5, 0xca, 0xd4, 0x62, 0x21, 318 0xb2, 0xa1, 0x87, 0x90, 0x5e, 0x52, 0x66, 0x36, 0x2b, 0x99, 0xd5, 319 0xe9, 0x1c, 0x6c, 0xe2, 0x4d, 0x16, 0x5d, 0xab, 0x93, 0xe8, 0x64, 320 0x33 321 ], 322 type: "tb" 323 ) 324 ] 325 326 do { 327 try VALID_BC_ADDRESSES.forEach { address, result in 328 let scriptPubKey = result.decoded 329 let hrp = byteConvert(string: result.type) 330 331 let ret = try decodeAddress(hrp: hrp, address: address) 332 333 let output = segwitScriptPubKey(version: ret.version, program: ret.program) 334 335 XCTAssertEqual(output, scriptPubKey) 336 337 let recreated = try encode(hrp: hrp, version: ret.version, program: ret.program).lowercased() 338 339 XCTAssertEqual(recreated, address.lowercased()) 340 } 341 } catch { 342 XCTFail(error.localizedDescription) 343 } 344 } 345 } 346 347 */