#!/usr/bin/swift import Foundation let recipient = CommandLine.arguments[1] let targetHRP = CommandLine.arguments[2] let id = try Bech32().decode(recipient) print(Bech32().encode(hrp: targetHRP, data: id.data)) // Copy paste of Sources/Bech32.swift because I don't know how to mix source files // from different dirs (not sure if this is possible). Probably should just // export and import // Spec: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki // Modified version of https://github.com/0xDEADP00L/Bech32 // 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. /// Bech32 checksum implementation public class Bech32 { private let gen: [UInt32] = [0x3b6a_57b2, 0x2650_8e6d, 0x1ea1_19fa, 0x3d42_33dd, 0x2a14_62b3] /// Bech32 checksum delimiter private let checksumMarker: String = "1" /// Bech32 character set for encoding private let encCharset: Data = Data("qpzry9x8gf2tvdw0s3jn54khce6mua7l".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, ] 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 DecodingError.bitsConversionFailed } return odata } /// 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 { let hrpBytes = Data(hrp.utf8) 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 private func encodeBech32(_ hrp: String, values: Data) -> String { let checksum = createChecksum(hrp: hrp, values: values) var combined = values combined.append(checksum) let hrpBytes = Data(hrp.utf8) var ret = hrpBytes ret.append(Data("1".utf8)) for i in combined { ret.append(encCharset[Int(i)]) } return String(decoding: ret, as: UTF8.self) } /// Decode Bech32 string public func decodeBech32(_ str: String) throws -> (hrp: String, checksum: Data) { let strBytes = Data(str.utf8) 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.. String { let isUpper = hrp[hrp.startIndex].isUppercase let result = encodeBech32( isUpper ? hrp.lowercased() : hrp, values: try! self.convertBits(from: 8, to: 5, pad: true, idata: data)) return isUpper ? result.uppercased() : result } public func decode(_ str: String) throws -> (hrp: String, data: Data) { let isUpper = str[str.startIndex].isUppercase let result = try decodeBech32(isUpper ? str.lowercased() : str) return ( isUpper ? result.hrp.uppercased() : result.hrp, try convertBits(from: 5, to: 8, pad: false, idata: result.checksum) ) } } extension Bech32 { public enum DecodingError: LocalizedError { case nonUTF8String case nonPrintableCharacter case invalidCase case noChecksumMarker case incorrectHrpSize case incorrectChecksumSize case invalidCharacter case checksumMismatch case bitsConversionFailed public var errorDescription: String? { switch self { case .bitsConversionFailed: return "Failed to perform bits conversion" 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" } } } }