damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

Bech32.swift (7772B)


      1 //
      2 //  Bech32.swift
      3 //
      4 //  Modified by William Casarin in 2022
      5 //  Created by Evolution Group Ltd on 12.02.2018.
      6 //  Copyright © 2018 Evolution Group Ltd. All rights reserved.
      7 //
      8 //  Base32 address format for native v0-16 witness outputs implementation
      9 //  https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
     10 //  Inspired by Pieter Wuille C++ implementation
     11 import Foundation
     12 
     13 /// Bech32 checksum implementation
     14 fileprivate let gen: [UInt32] = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
     15 /// Bech32 checksum delimiter
     16 fileprivate let checksumMarker: String = "1"
     17 /// Bech32 character set for encoding
     18 fileprivate let encCharset: Data = "qpzry9x8gf2tvdw0s3jn54khce6mua7l".data(using: .utf8)!
     19 /// Bech32 character set for decoding
     20 fileprivate let decCharset: [Int8] = [
     21     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     22     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     23     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
     24     15, -1, 10, 17, 21, 20, 26, 30,  7,  5, -1, -1, -1, -1, -1, -1,
     25     -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
     26     1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1,
     27     -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
     28     1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1
     29 ]
     30     
     31     /// Find the polynomial with value coefficients mod the generator as 30-bit.
     32 public func bech32_polymod(_ values: Data) -> UInt32 {
     33         var chk: UInt32 = 1
     34         for v in values {
     35             let top = (chk >> 25)
     36             chk = (chk & 0x1ffffff) << 5 ^ UInt32(v)
     37             for i: UInt8 in 0..<5 {
     38                 chk ^= ((top >> i) & 1) == 0 ? 0 : gen[Int(i)]
     39             }
     40         }
     41         return chk
     42     }
     43 
     44 
     45 /// Expand a HRP for use in checksum computation.
     46 func bech32_expand_hrp(_ s: String) -> Data {
     47     var left: [UInt8] = []
     48     var right: [UInt8] = []
     49     for x in Array(s) {
     50         let scalars = String(x).unicodeScalars
     51         left.append(UInt8(scalars[scalars.startIndex].value) >> 5)
     52         right.append(UInt8(scalars[scalars.startIndex].value) & 31)
     53     }
     54     return Data(left + [0] + right)
     55 }
     56     
     57 /// Verify checksum
     58 public func bech32_verify(hrp: String, checksum: Data) -> Bool {
     59     var data = bech32_expand_hrp(hrp)
     60     data.append(checksum)
     61     return bech32_polymod(data) == 1
     62 }
     63     
     64 /// Create checksum
     65 public func bech32_create_checksum(hrp: String, values: Data) -> Data {
     66     var enc = bech32_expand_hrp(hrp)
     67     enc.append(values)
     68     enc.append(Data(repeating: 0x00, count: 6))
     69     let mod: UInt32 = bech32_polymod(enc) ^ 1
     70     var ret: Data = Data(repeating: 0x00, count: 6)
     71     for i in 0..<6 {
     72         ret[i] = UInt8((mod >> (5 * (5 - i))) & 31)
     73     }
     74     return ret
     75 }
     76     
     77 public func bech32_encode(hrp: String, _ input: [UInt8]) -> String {
     78     let table: [Character] = Array("qpzry9x8gf2tvdw0s3jn54khce6mua7l")
     79     let bits = eightToFiveBits(input)
     80     let check_sum = bech32_checksum(hrp: hrp, data: bits)
     81     let separator = "1"
     82     return "\(hrp)" + separator + String((bits + check_sum).map { table[Int($0)] })
     83 }
     84 
     85 func bech32_checksum(hrp: String, data: [UInt8]) -> [UInt8] {
     86     let values = bech32_expand_hrp(hrp) + data
     87     let polymod = bech32_polymod(values + [0,0,0,0,0,0]) ^ 1
     88     var result: [UInt32] = []
     89     for i in (0..<6) {
     90         result.append((polymod >> (5 * (5 - UInt32(i)))) & 31)
     91     }
     92     return result.map { UInt8($0) }
     93 }
     94 
     95 func bech32_convert_bits(outbits: Int, input: Data, inbits: Int, pad: Int) -> Data? {
     96     let maxv: UInt32 = ((UInt32(1)) << outbits) - 1;
     97     var val: UInt32 = 0
     98     var bits: Int = 0
     99     var out = Data()
    100     
    101     for i in (0..<input.count) {
    102         val = (val << inbits) | UInt32(input[i])
    103         bits += inbits;
    104         while bits >= outbits {
    105             bits -= outbits;
    106             out.append(UInt8((val >> bits) & maxv))
    107         }
    108     }
    109     
    110     if pad != 0 {
    111         if bits != 0 {
    112             out.append(UInt8(val << (outbits - bits) & maxv))
    113         }
    114     } else if 0 != ((val << (outbits - bits)) & maxv) || bits >= inbits {
    115         return nil
    116     }
    117     
    118     return out
    119 }
    120 
    121 func eightToFiveBits(_ input: [UInt8]) -> [UInt8] {
    122     guard !input.isEmpty else { return [] }
    123     
    124     var outputSize = (input.count * 8) / 5
    125     if ((input.count * 8) % 5) != 0 {
    126         outputSize += 1
    127     }
    128     var outputArray: [UInt8] = []
    129     for i in (0..<outputSize) {
    130         let devision = (i * 5) / 8
    131         let reminder = (i * 5) % 8
    132         var element = input[devision] << reminder
    133         element >>= 3
    134         
    135         if (reminder > 3) && (i + 1 < outputSize) {
    136             element = element | (input[devision + 1] >> (8 - reminder + 3))
    137         }
    138         
    139         outputArray.append(element)
    140     }
    141     
    142     return outputArray
    143 }
    144     
    145     /// Decode Bech32 string
    146 @discardableResult
    147 public func bech32_decode(_ str: String) throws -> (hrp: String, data: Data)? {
    148     guard let strBytes = str.data(using: .utf8) else {
    149         throw Bech32Error.nonUTF8String
    150     }
    151     guard strBytes.count <= 2024 else {
    152         throw Bech32Error.stringLengthExceeded
    153     }
    154     var lower: Bool = false
    155     var upper: Bool = false
    156     for c in strBytes {
    157         // printable range
    158         if c < 33 || c > 126 {
    159             throw Bech32Error.nonPrintableCharacter
    160         }
    161         // 'a' to 'z'
    162         if c >= 97 && c <= 122 {
    163             lower = true
    164         }
    165         // 'A' to 'Z'
    166         if c >= 65 && c <= 90 {
    167             upper = true
    168         }
    169     }
    170     if lower && upper {
    171         throw Bech32Error.invalidCase
    172     }
    173     guard let pos = str.range(of: checksumMarker, options: .backwards)?.lowerBound else {
    174         throw Bech32Error.noChecksumMarker
    175     }
    176     let intPos: Int = str.distance(from: str.startIndex, to: pos)
    177     guard intPos >= 1 else {
    178         throw Bech32Error.incorrectHrpSize
    179     }
    180     guard intPos + 7 <= str.count else {
    181         throw Bech32Error.incorrectChecksumSize
    182     }
    183     let vSize: Int = str.count - 1 - intPos
    184     var values: Data = Data(repeating: 0x00, count: vSize)
    185     for i in 0..<vSize {
    186         let c = strBytes[i + intPos + 1]
    187         let decInt = decCharset[Int(c)]
    188         if decInt == -1 {
    189             throw Bech32Error.invalidCharacter
    190         }
    191         values[i] = UInt8(decInt)
    192     }
    193     let hrp = String(str[..<pos]).lowercased()
    194     guard bech32_verify(hrp: hrp, checksum: values) else {
    195         throw Bech32Error.checksumMismatch
    196     }
    197     let out = Data(values[..<(vSize-6)])
    198     guard let converted = bech32_convert_bits(outbits: 8, input: out, inbits: 5, pad: 0) else {
    199         return nil
    200     }
    201     return (hrp, converted)
    202 }
    203 
    204 public enum Bech32Error: LocalizedError {
    205     case nonUTF8String
    206     case nonPrintableCharacter
    207     case invalidCase
    208     case noChecksumMarker
    209     case incorrectHrpSize
    210     case incorrectChecksumSize
    211     case stringLengthExceeded
    212     
    213     case invalidCharacter
    214     case checksumMismatch
    215     
    216     public var errorDescription: String? {
    217         switch self {
    218         case .checksumMismatch:
    219             return "Checksum doesn't match"
    220         case .incorrectChecksumSize:
    221             return "Checksum size too low"
    222         case .incorrectHrpSize:
    223             return "Human-readable-part is too small or empty"
    224         case .invalidCase:
    225             return "String contains mixed case characters"
    226         case .invalidCharacter:
    227             return "Invalid character met on decoding"
    228         case .noChecksumMarker:
    229             return "Checksum delimiter not found"
    230         case .nonPrintableCharacter:
    231             return "Non printable character in input string"
    232         case .nonUTF8String:
    233             return "String cannot be decoded by utf8 decoder"
    234         case .stringLengthExceeded:
    235             return "Input string is too long"
    236         }
    237     }
    238 }