Bech32.kt (11048B)
1 /* 2 * This file is part of GNU Taler 3 * (C) 2020 Taler Systems S.A. 4 * 5 * GNU Taler is free software; you can redistribute it and/or modify it under the 6 * terms of the GNU General Public License as published by the Free Software 7 * Foundation; either version 3, or (at your option) any later version. 8 * 9 * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY 10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along with 14 * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 // Copyright (c) 2020 Figure Technologies Inc. 18 // The contents of this file were derived from an implementation 19 // by the btcsuite developers https://github.com/btcsuite/btcutil. 20 21 // Copyright (c) 2017 The btcsuite developers 22 // Use of this source code is governed by an ISC 23 // license that can be found in the LICENSE file. 24 25 // modified version of https://gist.github.com/iramiller/4ebfcdfbc332a9722c4a4abeb4e16454 26 27 package net.taler.common 28 29 import java.util.Locale.ROOT 30 import kotlin.experimental.and 31 import kotlin.experimental.or 32 33 infix fun Int.min(b: Int): Int = b.takeIf { this > b } ?: this 34 infix fun UByte.shl(bitCount: Int) = ((this.toInt() shl bitCount) and 0xff).toUByte() 35 infix fun UByte.shr(bitCount: Int) = (this.toInt() shr bitCount).toUByte() 36 37 /** 38 * Bech32 Data encoding instance containing data for encoding as well as a human readable prefix 39 */ 40 data class Bech32Data(val hrp: String, val fiveBitData: ByteArray) { 41 42 /** 43 * The encapsulated data as typical 8bit bytes. 44 */ 45 val data = Bech32.convertBits(fiveBitData, 5, 8, false) 46 47 /** 48 * Address is the Bech32 encoded value of the data prefixed with the human readable portion and 49 * protected by an appended checksum. 50 */ 51 val address = Bech32.encode(hrp, fiveBitData) 52 53 /** 54 * Checksum for encapsulated data + hrp 55 */ 56 val checksum = Bech32.checksum(this.hrp, this.fiveBitData.toTypedArray()) 57 58 /** 59 * The Bech32 Address toString prints state information for debugging purposes. 60 * @see address() for the bech32 encoded address string output. 61 */ 62 override fun toString(): String { 63 return "bech32 : ${this.address}\nhuman: ${this.hrp} \nbytes" 64 } 65 66 override fun equals(other: Any?): Boolean { 67 if (this === other) return true 68 if (javaClass != other?.javaClass) return false 69 70 other as Bech32Data 71 72 if (hrp != other.hrp) return false 73 if (!fiveBitData.contentEquals(other.fiveBitData)) return false 74 if (!data.contentEquals(other.data)) return false 75 if (address != other.address) return false 76 if (!checksum.contentEquals(other.checksum)) return false 77 78 return true 79 } 80 81 override fun hashCode(): Int { 82 var result = hrp.hashCode() 83 result = 31 * result + fiveBitData.contentHashCode() 84 result = 31 * result + data.contentHashCode() 85 result = 31 * result + address.hashCode() 86 result = 31 * result + checksum.contentHashCode() 87 return result 88 } 89 } 90 91 /** 92 * BIP173 compliant processing functions for handling Bech32 encoding for addresses 93 */ 94 class Bech32 { 95 96 companion object { 97 const val CHECKSUM_SIZE = 6 98 private const val MIN_VALID_LENGTH = 8 99 private const val MAX_VALID_LENGTH = 90 100 const val MIN_VALID_CODEPOINT = 33 101 private const val MAX_VALID_CODEPOINT = 126 102 103 const val charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" 104 private val gen = intArrayOf(0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3) 105 106 fun generateFakeSegwitAddress(reservePub: String?, addr: String): List<String> { 107 if (reservePub == null || reservePub.isEmpty()) return listOf() 108 val pub = CyptoUtils.decodeCrock(reservePub) 109 if (pub.size != 32) return listOf() 110 111 val firstRnd = pub.copyOfRange(0, 4) 112 val secondRnd = pub.copyOfRange(0, 4) 113 114 firstRnd[0] = firstRnd[0].and(0b0111_1111) 115 secondRnd[0] = secondRnd[0].or(0b1000_0000.toByte()) 116 117 val firstPart = ByteArray(20) 118 firstRnd.copyInto(firstPart, 0, 0, 4) 119 pub.copyInto(firstPart, 4, 0, 16) 120 121 val secondPart = ByteArray(20) 122 secondRnd.copyInto(secondPart, 0, 0, 4) 123 pub.copyInto(secondPart, 4, 16, 32) 124 125 val zero = ByteArray(1) 126 zero[0] = 0 127 val hrp = when { 128 addr[0] == 'b' && addr[1] == 'c' && addr[2] == 'r' && addr[3] == 't' -> "bcrt" 129 addr[0] == 't' && addr[1] == 'b' -> "tb" 130 addr[0] == 'b' && addr[1] == 'c' -> "bc" 131 else -> throw Error("unknown bitcoin net") 132 } 133 134 return listOf( 135 Bech32Data(hrp, zero + convertBits(firstPart, 8, 5, true)).address, 136 Bech32Data(hrp, zero + convertBits(secondPart, 8, 5, true)).address, 137 ) 138 } 139 140 /** 141 * Decodes a Bech32 String 142 */ 143 fun decode(bech32: String): Bech32Data { 144 require(bech32.length in MIN_VALID_LENGTH..MAX_VALID_LENGTH) { "invalid bech32 string length" } 145 require(bech32.toCharArray() 146 .none { c -> c.code < MIN_VALID_CODEPOINT || c.code > MAX_VALID_CODEPOINT }) 147 { 148 "invalid character in bech32: ${ 149 bech32.toCharArray().map { c -> c.code } 150 .filter { c -> c < MIN_VALID_CODEPOINT || c > MAX_VALID_CODEPOINT } 151 }" 152 } 153 154 require(bech32 == bech32.lowercase(ROOT) || bech32 == bech32.uppercase(ROOT)) 155 { "bech32 must be either all upper or lower case" } 156 require(bech32.substring(1).dropLast(CHECKSUM_SIZE) 157 .contains('1')) { "invalid index of '1'" } 158 159 val hrp = bech32.substringBeforeLast('1').lowercase(ROOT) 160 val dataString = bech32.substringAfterLast('1').lowercase(ROOT) 161 162 require(dataString.toCharArray() 163 .all { c -> charset.contains(c) }) { "invalid data encoding character in bech32" } 164 165 val dataBytes = dataString.map { c -> charset.indexOf(c).toByte() }.toByteArray() 166 val checkBytes = 167 dataString.takeLast(CHECKSUM_SIZE).map { c -> charset.indexOf(c).toByte() } 168 .toByteArray() 169 170 val actualSum = checksum(hrp, dataBytes.dropLast(CHECKSUM_SIZE).toTypedArray()) 171 require(1 == polymod(expandHrp(hrp).plus(dataBytes.map { d -> d.toInt() }))) { "checksum failed: $checkBytes != $actualSum" } 172 173 return Bech32Data(hrp, dataBytes.dropLast(CHECKSUM_SIZE).toByteArray()) 174 } 175 176 /** 177 * ConvertBits regroups bytes with toBits set based on reading groups of bits as a continuous stream group by fromBits. 178 * This process is used to convert from base64 (from 8) to base32 (to 5) or the inverse. 179 */ 180 fun convertBits(data: ByteArray, fromBits: Int, toBits: Int, pad: Boolean): ByteArray { 181 require(fromBits in 1..8 && toBits in 1..8) { "only bit groups between 1 and 8 are supported" } 182 183 // resulting bytes with each containing the toBits bits from the input set. 184 val regrouped = arrayListOf<Byte>() 185 186 var nextByte = 0.toUByte() 187 var filledBits = 0 188 189 data.forEach { d -> 190 // discard unused bits. 191 var b = (d.toUByte() shl (8 - fromBits)) 192 193 // How many bits remain to extract from input data. 194 var remainFromBits = fromBits 195 196 while (remainFromBits > 0) { 197 // How many bits remain to be copied in 198 val remainToBits = toBits - filledBits 199 200 // we extract the remaining bits unless that is more than we need. 201 val toExtract = 202 remainFromBits.takeUnless { remainToBits < remainFromBits } ?: remainToBits 203 check(toExtract >= 0) { "extract should be positive" } 204 205 // move existing bits to the left to make room for bits toExtract, copy in bits to extract 206 nextByte = (nextByte shl toExtract) or (b shr (8 - toExtract)) 207 208 // discard extracted bits and update position counters 209 b = b shl toExtract 210 remainFromBits -= toExtract 211 filledBits += toExtract 212 213 // if we have a complete group then reset. 214 if (filledBits == toBits) { 215 regrouped.add(nextByte.toByte()) 216 filledBits = 0 217 nextByte = 0.toUByte() 218 } 219 } 220 } 221 222 // pad any unfinished groups as required 223 if (pad && filledBits > 0) { 224 nextByte = nextByte shl (toBits - filledBits) 225 regrouped.add(nextByte.toByte()) 226 filledBits = 0 227 nextByte = 0.toUByte() 228 } 229 230 return regrouped.toByteArray() 231 } 232 233 /** 234 * Encodes data 5-bit bytes (data) with a given human readable portion (hrp) into a bech32 string. 235 * @see convertBits for conversion or ideally use the Bech32Data extension functions 236 */ 237 fun encode(hrp: String, fiveBitData: ByteArray): String { 238 return (fiveBitData.plus(checksum(hrp, fiveBitData.toTypedArray())) 239 .map { b -> charset[b.toInt()] }).joinToString("", hrp + "1") 240 } 241 242 /** 243 * Calculates a bech32 checksum based on BIP 173 specification 244 */ 245 fun checksum(hrp: String, data: Array<Byte>): ByteArray { 246 val values = expandHrp(hrp) 247 .plus(data.map { d -> d.toInt() }) 248 .plus(Array(6) { 0 }.toIntArray()) 249 250 val poly = polymod(values) xor 1 251 252 return (0..5).map { 253 ((poly shr (5 * (5 - it))) and 31).toByte() 254 }.toByteArray() 255 } 256 257 /** 258 * Expands the human readable prefix per BIP173 for Checksum encoding 259 */ 260 private fun expandHrp(hrp: String) = 261 hrp.map { c -> c.code shr 5 } 262 .plus(0) 263 .plus(hrp.map { c -> c.code and 31 }) 264 .toIntArray() 265 266 /** 267 * Polynomial division function for checksum calculation. For details see BIP173 268 */ 269 fun polymod(values: IntArray): Int { 270 var chk = 1 271 return values.map { num -> 272 val b = chk shr 25 273 chk = ((chk and 0x1ffffff) shl 5) xor num 274 (0..4).map { 275 if (((b shr it) and 1) == 1) { 276 chk = chk xor gen[it] 277 } 278 } 279 }.let { chk } 280 } 281 } 282 }