|
| 1 | +/* Copyright (c) 2018 Coinomi Ltd |
| 2 | + * |
| 3 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 4 | + * of this software and associated documentation files (the "Software"), to deal |
| 5 | + * in the Software without restriction, including without limitation the rights |
| 6 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 7 | + * copies of the Software, and to permit persons to whom the Software is |
| 8 | + * furnished to do so, subject to the following conditions: |
| 9 | + * |
| 10 | + * The above copyright notice and this permission notice shall be included in |
| 11 | + * all copies or substantial portions of the Software. |
| 12 | + * |
| 13 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 14 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 15 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 16 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 17 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 18 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 19 | + * THE SOFTWARE. |
| 20 | + */ |
| 21 | + |
| 22 | +package org.bitcoinj.core; |
| 23 | + |
| 24 | +import java.util.Arrays; |
| 25 | +import java.util.Locale; |
| 26 | + |
| 27 | +public class Bech32 { |
| 28 | + /** The Bech32 character set for encoding. */ |
| 29 | + private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; |
| 30 | + |
| 31 | + /** The Bech32 character set for decoding. */ |
| 32 | + private static final byte[] CHARSET_REV = { |
| 33 | + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| 34 | + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| 35 | + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| 36 | + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, |
| 37 | + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, |
| 38 | + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, |
| 39 | + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, |
| 40 | + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 |
| 41 | + }; |
| 42 | + |
| 43 | + public static class Bech32Data { |
| 44 | + final String hrp; |
| 45 | + final byte[] data; |
| 46 | + |
| 47 | + private Bech32Data(final String hrp, final byte[] data) { |
| 48 | + this.hrp = hrp; |
| 49 | + this.data = data; |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + /** Find the polynomial with value coefficients mod the generator as 30-bit. */ |
| 54 | + private static int polymod(final byte[] values) { |
| 55 | + int c = 1; |
| 56 | + for (byte v_i: values) { |
| 57 | + int c0 = (c >>> 25) & 0xff; |
| 58 | + c = ((c & 0x1ffffff) << 5) ^ (v_i & 0xff); |
| 59 | + if ((c0 & 1) != 0) c ^= 0x3b6a57b2; |
| 60 | + if ((c0 & 2) != 0) c ^= 0x26508e6d; |
| 61 | + if ((c0 & 4) != 0) c ^= 0x1ea119fa; |
| 62 | + if ((c0 & 8) != 0) c ^= 0x3d4233dd; |
| 63 | + if ((c0 & 16) != 0) c ^= 0x2a1462b3; |
| 64 | + } |
| 65 | + return c; |
| 66 | + } |
| 67 | + |
| 68 | + /** Expand a HRP for use in checksum computation. */ |
| 69 | + private static byte[] expandHrp(final String hrp) { |
| 70 | + int hrpLength = hrp.length(); |
| 71 | + byte ret[] = new byte[hrpLength * 2 + 1]; |
| 72 | + for (int i = 0; i < hrpLength; ++i) { |
| 73 | + int c = hrp.charAt(i) & 0x7f; // Limit to standard 7-bit ASCII |
| 74 | + ret[i] = (byte) ((c >>> 5) & 0x07); |
| 75 | + ret[i + hrpLength + 1] = (byte) (c & 0x1f); |
| 76 | + } |
| 77 | + ret[hrpLength] = 0; |
| 78 | + return ret; |
| 79 | + } |
| 80 | + |
| 81 | + /** Verify a checksum. */ |
| 82 | + private static boolean verifyChecksum(final String hrp, final byte[] values) { |
| 83 | + byte[] hrpExpanded = expandHrp(hrp); |
| 84 | + byte[] combined = new byte[hrpExpanded.length + values.length]; |
| 85 | + System.arraycopy(hrpExpanded, 0, combined, 0, hrpExpanded.length); |
| 86 | + System.arraycopy(values, 0, combined, hrpExpanded.length, values.length); |
| 87 | + return polymod(combined) == 1; |
| 88 | + } |
| 89 | + |
| 90 | + /** Create a checksum. */ |
| 91 | + private static byte[] createChecksum(final String hrp, final byte[] values) { |
| 92 | + byte[] hrpExpanded = expandHrp(hrp); |
| 93 | + byte[] enc = new byte[hrpExpanded.length + values.length + 6]; |
| 94 | + System.arraycopy(hrpExpanded, 0, enc, 0, hrpExpanded.length); |
| 95 | + System.arraycopy(values, 0, enc, hrpExpanded.length, values.length); |
| 96 | + int mod = polymod(enc) ^ 1; |
| 97 | + byte[] ret = new byte[6]; |
| 98 | + for (int i = 0; i < 6; ++i) { |
| 99 | + ret[i] = (byte) ((mod >>> (5 * (5 - i))) & 31); |
| 100 | + } |
| 101 | + return ret; |
| 102 | + } |
| 103 | + |
| 104 | + /** Encode a Bech32 string. */ |
| 105 | + public static String encode(final Bech32Data bech32) throws AddressFormatException { |
| 106 | + return encode(bech32.hrp, bech32.data); |
| 107 | + } |
| 108 | + |
| 109 | + /** Encode a Bech32 string. */ |
| 110 | + public static String encode(String hrp, final byte[] values) throws AddressFormatException { |
| 111 | + if (hrp.length() < 1) throw new AddressFormatException("Human-readable part is too short"); |
| 112 | + if (hrp.length() > 83) throw new AddressFormatException("Human-readable part is too long"); |
| 113 | + hrp = hrp.toLowerCase(Locale.ROOT); |
| 114 | + byte[] checksum = createChecksum(hrp, values); |
| 115 | + byte[] combined = new byte[values.length + checksum.length]; |
| 116 | + System.arraycopy(values, 0, combined, 0, values.length); |
| 117 | + System.arraycopy(checksum, 0, combined, values.length, checksum.length); |
| 118 | + StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length); |
| 119 | + sb.append(hrp); |
| 120 | + sb.append('1'); |
| 121 | + for (byte b : combined) { |
| 122 | + sb.append(CHARSET.charAt(b)); |
| 123 | + } |
| 124 | + return sb.toString(); |
| 125 | + } |
| 126 | + |
| 127 | + /** Decode a Bech32 string. */ |
| 128 | + public static Bech32Data decode(final String str) throws AddressFormatException { |
| 129 | + boolean lower = false, upper = false; |
| 130 | + if (str.length() < 8) throw new AddressFormatException("Input too short"); |
| 131 | + if (str.length() > 90) throw new AddressFormatException("Input too long"); |
| 132 | + for (int i = 0; i < str.length(); ++i) { |
| 133 | + char c = str.charAt(i); |
| 134 | + if (c < 33 || c > 126) throw new AddressFormatException("Characters out of range"); |
| 135 | + if (c >= 'a' && c <= 'z') lower = true; |
| 136 | + if (c >= 'A' && c <= 'Z') upper = true; |
| 137 | + } |
| 138 | + if (lower && upper) throw new AddressFormatException("Cannot mix upper and lower cases"); |
| 139 | + int pos = str.lastIndexOf('1'); |
| 140 | + if (pos < 1) throw new AddressFormatException("Missing human-readable part"); |
| 141 | + if (pos + 7 > str.length()) throw new AddressFormatException("Data part too short"); |
| 142 | + byte[] values = new byte[str.length() - 1 - pos]; |
| 143 | + for (int i = 0; i < str.length() - 1 - pos; ++i) { |
| 144 | + char c = str.charAt(i + pos + 1); |
| 145 | + if (CHARSET_REV[c] == -1) throw new AddressFormatException("Characters out of range"); |
| 146 | + values[i] = CHARSET_REV[c]; |
| 147 | + } |
| 148 | + String hrp = str.substring(0, pos).toLowerCase(Locale.ROOT); |
| 149 | + if (!verifyChecksum(hrp, values)) throw new AddressFormatException("Invalid checksum"); |
| 150 | + return new Bech32Data(hrp, Arrays.copyOfRange(values, 0, values.length - 6)); |
| 151 | + } |
| 152 | +} |
0 commit comments