| 1 | // algorthim is adapted from https://github.com/mr-tron/base58 under the MIT license |
| 2 | |
| 3 | module base58 |
| 4 | |
| 5 | import math |
| 6 | |
| 7 | // encode_int encodes any integer type to base58 string with Bitcoin alphabet |
| 8 | pub fn encode_int(input int) !string { |
| 9 | return encode_int_walpha(input, btc_alphabet) |
| 10 | } |
| 11 | |
| 12 | // encode_int_walpha any integer type to base58 string with custom alphabet |
| 13 | pub fn encode_int_walpha(input int, alphabet Alphabet) !string { |
| 14 | if input <= 0 { |
| 15 | return error(@MOD + '.' + @FN + ': input must be greater than zero') |
| 16 | } |
| 17 | |
| 18 | mut buffer := []u8{} |
| 19 | |
| 20 | mut i := input |
| 21 | for i > 0 { |
| 22 | remainder := i % 58 |
| 23 | buffer << alphabet.encode[i8(remainder)] |
| 24 | // This needs to be casted so byte inputs can |
| 25 | // be used. i8 because remainder will never be |
| 26 | // over 58. |
| 27 | i = i / 58 |
| 28 | } |
| 29 | |
| 30 | return buffer.reverse().bytestr() |
| 31 | } |
| 32 | |
| 33 | // encode encodes the input string to base58 with the Bitcoin alphabet |
| 34 | pub fn encode(input string) string { |
| 35 | return encode_walpha(input, btc_alphabet) |
| 36 | } |
| 37 | |
| 38 | // encode_bytes encodes the input array to base58, with the Bitcoin alphabet |
| 39 | pub fn encode_bytes(input []u8) []u8 { |
| 40 | return encode_walpha_bytes(input, btc_alphabet) |
| 41 | } |
| 42 | |
| 43 | // encode_walpha encodes the input string to base58 with a custom aplhabet |
| 44 | pub fn encode_walpha(input string, alphabet Alphabet) string { |
| 45 | if input.len == 0 { |
| 46 | return '' |
| 47 | } |
| 48 | bin := input.bytes() |
| 49 | return encode_walpha_bytes(bin, alphabet).bytestr() |
| 50 | } |
| 51 | |
| 52 | // encode_walpha encodes the input array to base58 with a custom aplhabet |
| 53 | @[direct_array_access] |
| 54 | pub fn encode_walpha_bytes(input []u8, alphabet Alphabet) []u8 { |
| 55 | if input.len == 0 { |
| 56 | return [] |
| 57 | } |
| 58 | mut sz := input.len |
| 59 | |
| 60 | mut zcount := 0 |
| 61 | for zcount < sz && input[zcount] == 0 { |
| 62 | zcount++ |
| 63 | } |
| 64 | |
| 65 | // It is crucial to make this as short as possible, especially for |
| 66 | // the usual case of Bitcoin addresses |
| 67 | sz = zcount + (sz - zcount) * 555 / 406 + 1 |
| 68 | // integer simplification of |
| 69 | // ceil(log(256)/log(58)) |
| 70 | |
| 71 | mut out := []u8{len: sz} |
| 72 | mut i := 0 |
| 73 | mut high := 0 |
| 74 | mut carry := u32(0) |
| 75 | |
| 76 | high = sz - 1 |
| 77 | for b in input { |
| 78 | i = sz - 1 |
| 79 | for carry = u32(b); i > high || carry != 0; i-- { |
| 80 | carry = carry + 256 * u32(out[i]) |
| 81 | out[i] = u8(carry % 58) |
| 82 | carry /= 58 |
| 83 | } |
| 84 | high = i |
| 85 | } |
| 86 | |
| 87 | // determine additional "zero-gap" in the buffer, aside from zcount |
| 88 | for i = zcount; i < sz && out[i] == 0; i++ {} |
| 89 | |
| 90 | // now encode the values with actual alphabet in-place |
| 91 | val := unsafe { out[i - zcount..] } |
| 92 | sz = val.len |
| 93 | for i = 0; i < sz; i++ { |
| 94 | out[i] = alphabet.encode[val[i]] |
| 95 | } |
| 96 | |
| 97 | return out[..sz] |
| 98 | } |
| 99 | |
| 100 | // decode_int decodes base58 string to an integer with Bitcoin alphabet |
| 101 | pub fn decode_int(input string) !int { |
| 102 | return decode_int_walpha(input, btc_alphabet) |
| 103 | } |
| 104 | |
| 105 | // decode_int_walpha decodes base58 string to an integer with custom alphabet |
| 106 | pub fn decode_int_walpha(input string, alphabet Alphabet) !int { |
| 107 | mut total := 0 // to hold the results |
| 108 | b58 := input.reverse() |
| 109 | for i, ch in b58 { |
| 110 | ch_i := alphabet.encode.bytestr().index_u8(ch) |
| 111 | if ch_i == -1 { |
| 112 | return error(@MOD + '.' + @FN + |
| 113 | ': input string contains values not found in the provided alphabet') |
| 114 | } |
| 115 | |
| 116 | val := ch_i * math.pow(58, i) |
| 117 | |
| 118 | total += int(val) |
| 119 | } |
| 120 | |
| 121 | return total |
| 122 | } |
| 123 | |
| 124 | // decode decodes the base58 input string, using the Bitcoin alphabet |
| 125 | pub fn decode(str string) !string { |
| 126 | return decode_walpha(str, btc_alphabet) |
| 127 | } |
| 128 | |
| 129 | // decode_bytes decodes the base58 encoded input array, using the Bitcoin alphabet |
| 130 | pub fn decode_bytes(input []u8) ![]u8 { |
| 131 | return decode_walpha_bytes(input, btc_alphabet) |
| 132 | } |
| 133 | |
| 134 | // decode_walpha decodes the base58 encoded input string, using custom alphabet |
| 135 | pub fn decode_walpha(input string, alphabet Alphabet) !string { |
| 136 | if input.len == 0 { |
| 137 | return '' |
| 138 | } |
| 139 | bin := input.bytes() |
| 140 | res := decode_walpha_bytes(bin, alphabet)! |
| 141 | return res.bytestr() |
| 142 | } |
| 143 | |
| 144 | // decode_walpha_bytes decodes the base58 encoded input array using a custom alphabet |
| 145 | pub fn decode_walpha_bytes(input []u8, alphabet Alphabet) ![]u8 { |
| 146 | if input.len == 0 { |
| 147 | return [] |
| 148 | } |
| 149 | |
| 150 | zero := alphabet.encode[0] |
| 151 | b58sz := input.len |
| 152 | |
| 153 | mut zcount := 0 |
| 154 | for i := 0; i < b58sz && input[i] == zero; i++ { |
| 155 | zcount++ |
| 156 | } |
| 157 | |
| 158 | mut t := u64(0) |
| 159 | mut c := u64(0) |
| 160 | |
| 161 | // the 32-bit algorithm stretches the result up to 2x |
| 162 | mut binu := []u8{len: 2 * ((b58sz * 406 / 555) + 1)} |
| 163 | mut outi := []u32{len: (b58sz + 3) / 4} |
| 164 | |
| 165 | for _, r in input { |
| 166 | if r > 127 { |
| 167 | panic(@MOD + '.' + @FN + |
| 168 | ': high-bit set on invalid digit; outside of ascii range (${r}). This should never happen.') |
| 169 | } |
| 170 | if alphabet.decode[r] == -1 { |
| 171 | return error(@MOD + '.' + @FN + ': invalid base58 digit (${r})') |
| 172 | } |
| 173 | |
| 174 | c = u64(alphabet.decode[r]) |
| 175 | |
| 176 | for j := outi.len - 1; j >= 0; j-- { |
| 177 | t = u64(outi[j]) * 58 + c |
| 178 | c = t >> 32 |
| 179 | outi[j] = u32(t & 0xffffffff) |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | // initial mask depend on b58sz, on further loops it always starts at 24 bits |
| 184 | mut mask := (u32(b58sz % 4) * 8) |
| 185 | if mask == 0 { |
| 186 | mask = 32 |
| 187 | } |
| 188 | mask -= 8 |
| 189 | |
| 190 | mut out_len := 0 |
| 191 | for j := 0; j < outi.len; j++ { |
| 192 | for mask < 32 { |
| 193 | binu[out_len] = u8(outi[j] >> mask) |
| 194 | mask -= 8 |
| 195 | out_len++ |
| 196 | } |
| 197 | mask = 24 |
| 198 | } |
| 199 | |
| 200 | // find the most significant byte post-decode, if any |
| 201 | for msb := zcount; msb < binu.len; msb++ { // loop relies on u32 overflow |
| 202 | if binu[msb] > 0 { |
| 203 | return binu[msb - zcount..out_len] |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | // it's all zeroes |
| 208 | return binu[..out_len] |
| 209 | } |
| 210 | |