/src/main/scala/fr/acinq/bitcoin/Bech32.scala

https://github.com/ACINQ/bitcoin-lib · Scala · 203 lines · 137 code · 15 blank · 51 comment · 46 complexity · 6e30b134ab5fbfc7f27f39efdccb6fa0 MD5 · raw file

  1. package fr.acinq.bitcoin
  2. import scodec.bits.ByteVector
  3. /**
  4. * Bech32 and Bech32m address formats.
  5. * See https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki and https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki.
  6. */
  7. object Bech32 {
  8. val alphabet = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
  9. // @formatter:off
  10. sealed trait Encoding
  11. case object Bech32Encoding extends Encoding
  12. case object Bech32mEncoding extends Encoding
  13. // @formatter:on
  14. // 5 bits integer
  15. // Bech32 works with 5 bits values, we use this type to make it explicit: whenever you see Int5 it means 5 bits values,
  16. // and whenever you see Byte it means 8 bits values
  17. type Int5 = Byte
  18. // char -> 5 bits value
  19. private val InvalidChar = 255.toByte
  20. val map = {
  21. val result = new Array[Int5](255)
  22. for (i <- result.indices) result(i) = InvalidChar
  23. alphabet.zipWithIndex.foreach { case (c, i) => result(c) = i.toByte }
  24. result
  25. }
  26. private def expand(hrp: String): Array[Int5] = {
  27. val result = new Array[Int5](2 * hrp.length + 1)
  28. var i = 0
  29. while (i < hrp.length) {
  30. result(i) = (hrp(i).toInt >>> 5).toByte
  31. result(hrp.length() + 1 + i) = (hrp(i).toInt & 31).toByte
  32. i = i + 1
  33. }
  34. result(hrp.length()) = 0.toByte
  35. result
  36. }
  37. private def polymod(values: Array[Int5], values1: Array[Int5]): Int = {
  38. val GEN = Array(0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3)
  39. var chk = 1
  40. values.foreach(v => {
  41. val b = chk >>> 25
  42. chk = ((chk & 0x1ffffff) << 5) ^ v
  43. for (i <- 0 until 5) {
  44. if (((b >>> i) & 1) != 0) chk = chk ^ GEN(i)
  45. }
  46. })
  47. values1.foreach(v => {
  48. val b = chk >>> 25
  49. chk = ((chk & 0x1ffffff) << 5) ^ v
  50. for (i <- 0 until 5) {
  51. if (((b >>> i) & 1) != 0) chk = chk ^ GEN(i)
  52. }
  53. })
  54. chk
  55. }
  56. /**
  57. * @param hrp human readable prefix
  58. * @param int5s 5-bit data
  59. * @return hrp + data encoded as a Bech32 string
  60. */
  61. def encode(hrp: String, int5s: Array[Int5], encoding: Encoding): String = {
  62. require(hrp.toLowerCase == hrp || hrp.toUpperCase == hrp, "mixed case strings are not valid bech32 prefixes")
  63. val checksum = Bech32.checksum(hrp, int5s, encoding)
  64. hrp + "1" + new String((int5s ++ checksum).map(i => alphabet(i)))
  65. }
  66. /**
  67. * decodes a bech32 or bech32m string
  68. *
  69. * @param bech32 bech32 or bech32m string
  70. * @return a (encoding, hrp, data) tuple
  71. */
  72. def decode(bech32: String): (String, Array[Int5], Encoding) = {
  73. require(bech32.toLowerCase == bech32 || bech32.toUpperCase == bech32, "mixed case strings are not valid bech32")
  74. bech32.foreach(c => require(c >= 33 && c <= 126, "invalid character"))
  75. val input = bech32.toLowerCase()
  76. val pos = input.lastIndexOf('1')
  77. val hrp = input.take(pos)
  78. require(hrp.nonEmpty && hrp.length <= 83, "hrp must contain 1 to 83 characters")
  79. val data = new Array[Int5](input.length - pos - 1)
  80. for (i <- data.indices) {
  81. val elt = map(input(pos + 1 + i))
  82. require(elt != InvalidChar, s"invalid bech32 character ${input(pos + 1 + i)}")
  83. data(i) = elt
  84. }
  85. val checksum = polymod(expand(hrp), data)
  86. val encoding = checksum match {
  87. case 1 => Bech32Encoding
  88. case 0x2bc830a3 => Bech32mEncoding
  89. case _ => throw new IllegalArgumentException(s"invalid checksum for $bech32")
  90. }
  91. (hrp, data.dropRight(6), encoding)
  92. }
  93. /**
  94. * @param hrp Human Readable Part
  95. * @param data data (a sequence of 5 bits integers)
  96. * @param encoding encoding to use (bech32 or bech32m)
  97. * @return a checksum computed over hrp and data
  98. */
  99. private def checksum(hrp: String, data: Array[Int5], encoding: Encoding): Array[Int5] = {
  100. val constant = encoding match {
  101. case Bech32Encoding => 1
  102. case Bech32mEncoding => 0x2bc830a3
  103. }
  104. val values = expand(hrp) ++ data
  105. val poly = polymod(values, Array(0.toByte, 0.toByte, 0.toByte, 0.toByte, 0.toByte, 0.toByte)) ^ constant
  106. val result = new Array[Int5](6)
  107. for (i <- 0 to 5) result(i) = ((poly >>> 5 * (5 - i)) & 31).toByte
  108. result
  109. }
  110. /**
  111. * @param input a sequence of 8 bits integers
  112. * @return a sequence of 5 bits integers
  113. */
  114. def eight2five(input: Array[Byte]): Array[Int5] = {
  115. var buffer = 0L
  116. val output = collection.mutable.ArrayBuffer.empty[Byte]
  117. var count = 0
  118. input.foreach(b => {
  119. buffer = (buffer << 8) | (b & 0xff)
  120. count = count + 8
  121. while (count >= 5) {
  122. output.append(((buffer >> (count - 5)) & 31).toByte)
  123. count = count - 5
  124. }
  125. })
  126. if (count > 0) output.append(((buffer << (5 - count)) & 31).toByte)
  127. output.toArray
  128. }
  129. /**
  130. * @param input a sequence of 5 bits integers
  131. * @return a sequence of 8 bits integers
  132. */
  133. def five2eight(input: Array[Int5]): Array[Byte] = {
  134. var buffer = 0L
  135. val output = collection.mutable.ArrayBuffer.empty[Byte]
  136. var count = 0
  137. input.foreach(b => {
  138. buffer = (buffer << 5) | (b & 31)
  139. count = count + 5
  140. while (count >= 8) {
  141. output.append(((buffer >> (count - 8)) & 0xff).toByte)
  142. count = count - 8
  143. }
  144. })
  145. require(count <= 4, "Zero-padding of more than 4 bits")
  146. require((buffer & ((1 << count) - 1)) == 0, "Non-zero padding in 8-to-5 conversion")
  147. output.toArray
  148. }
  149. /**
  150. * encode a bitcoin witness address
  151. *
  152. * @param hrp should be "bc" or "tb"
  153. * @param witnessVersion witness version (0 to 16)
  154. * @param data witness program: if version is 0, either 20 bytes (P2WPKH) or 32 bytes (P2WSH)
  155. * @return a bech32 encoded witness address
  156. */
  157. def encodeWitnessAddress(hrp: String, witnessVersion: Byte, data: ByteVector): String = {
  158. require(0 <= witnessVersion && witnessVersion <= 16, "invalid segwit version")
  159. val encoding = witnessVersion match {
  160. case 0 => Bech32Encoding
  161. case _ => Bech32mEncoding
  162. }
  163. val data1 = witnessVersion +: Bech32.eight2five(data.toArray)
  164. val checksum = Bech32.checksum(hrp, data1, encoding)
  165. hrp + "1" + new String((data1 ++ checksum).map(i => alphabet(i)))
  166. }
  167. /**
  168. * decode a bitcoin witness address
  169. *
  170. * @param address witness address
  171. * @return a (prefix, version, program) tuple where prefix is the human-readable prefix, version
  172. * is the witness version and program the decoded witness program.
  173. * If version is 0, it will be either 20 bytes (P2WPKH) or 32 bytes (P2WSH).
  174. */
  175. def decodeWitnessAddress(address: String): (String, Byte, ByteVector) = {
  176. if (address.indexWhere(_.isLower) != -1 && address.indexWhere(_.isUpper) != -1) throw new IllegalArgumentException("input mixes lowercase and uppercase characters")
  177. val (hrp, data, encoding) = decode(address)
  178. require(hrp == "bc" || hrp == "tb" || hrp == "bcrt", s"invalid HRP $hrp")
  179. val version = data(0)
  180. require(version >= 0 && version <= 16, "invalid segwit version")
  181. val bin = five2eight(data.drop(1))
  182. require(bin.length >= 2 && bin.length <= 40, s"invalid witness program length ${bin.length}")
  183. if (version == 0) require(encoding == Bech32Encoding, "version 0 must be encoded with Bech32")
  184. if (version == 0) require(bin.length == 20 || bin.length == 32, s"invalid witness program length ${bin.length}")
  185. if (version != 0) require(encoding == Bech32mEncoding, "version 1 to 16 must be encoded with Bech32m")
  186. (hrp, version, ByteVector.view(bin))
  187. }
  188. }