github.com/Asutorufa/yuhaiin@v0.3.6-0.20240502055049-7984da7023a0/pkg/utils/uuid/base58/base58.go (about) 1 package base58 2 3 import ( 4 "crypto/sha256" 5 "errors" 6 "math/big" 7 ) 8 9 const ( 10 alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 11 12 alphabetIdx0 = '1' 13 ) 14 15 var b58 = [256]byte{ 16 255, 255, 255, 255, 255, 255, 255, 255, 17 255, 255, 255, 255, 255, 255, 255, 255, 18 255, 255, 255, 255, 255, 255, 255, 255, 19 255, 255, 255, 255, 255, 255, 255, 255, 20 255, 255, 255, 255, 255, 255, 255, 255, 21 255, 255, 255, 255, 255, 255, 255, 255, 22 255, 0, 1, 2, 3, 4, 5, 6, 23 7, 8, 255, 255, 255, 255, 255, 255, 24 255, 9, 10, 11, 12, 13, 14, 15, 25 16, 255, 17, 18, 19, 20, 21, 255, 26 22, 23, 24, 25, 26, 27, 28, 29, 27 30, 31, 32, 255, 255, 255, 255, 255, 28 255, 33, 34, 35, 36, 37, 38, 39, 29 40, 41, 42, 43, 255, 44, 45, 46, 30 47, 48, 49, 50, 51, 52, 53, 54, 31 55, 56, 57, 255, 255, 255, 255, 255, 32 255, 255, 255, 255, 255, 255, 255, 255, 33 255, 255, 255, 255, 255, 255, 255, 255, 34 255, 255, 255, 255, 255, 255, 255, 255, 35 255, 255, 255, 255, 255, 255, 255, 255, 36 255, 255, 255, 255, 255, 255, 255, 255, 37 255, 255, 255, 255, 255, 255, 255, 255, 38 255, 255, 255, 255, 255, 255, 255, 255, 39 255, 255, 255, 255, 255, 255, 255, 255, 40 255, 255, 255, 255, 255, 255, 255, 255, 41 255, 255, 255, 255, 255, 255, 255, 255, 42 255, 255, 255, 255, 255, 255, 255, 255, 43 255, 255, 255, 255, 255, 255, 255, 255, 44 255, 255, 255, 255, 255, 255, 255, 255, 45 255, 255, 255, 255, 255, 255, 255, 255, 46 255, 255, 255, 255, 255, 255, 255, 255, 47 255, 255, 255, 255, 255, 255, 255, 255, 48 } 49 50 var bigRadix = [...]*big.Int{ 51 big.NewInt(0), 52 big.NewInt(58), 53 big.NewInt(58 * 58), 54 big.NewInt(58 * 58 * 58), 55 big.NewInt(58 * 58 * 58 * 58), 56 big.NewInt(58 * 58 * 58 * 58 * 58), 57 big.NewInt(58 * 58 * 58 * 58 * 58 * 58), 58 big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58), 59 big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58), 60 big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58), 61 bigRadix10, 62 } 63 64 var bigRadix10 = big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58) // 58^10 65 66 // Decode decodes a modified base58 string to a byte slice. 67 func Decode(b string) []byte { 68 answer := big.NewInt(0) 69 scratch := new(big.Int) 70 71 // Calculating with big.Int is slow for each iteration. 72 // x += b58[b[i]] * j 73 // j *= 58 74 // 75 // Instead we can try to do as many calculations on int64. 76 // We can represent a 10 digit base58 number using an int64. 77 // 78 // Hence, we'll try to convert 10, base58 digits at a time. 79 // The rough idea is to calculate `t`, such that: 80 // 81 // t := b58[b[i+9]] * 58^9 ... + b58[b[i+1]] * 58^1 + b58[b[i]] * 58^0 82 // x *= 58^10 83 // x += t 84 // 85 // Of course, in addition, we'll need to handle boundary condition when `b` is not multiple of 58^10. 86 // In that case we'll use the bigRadix[n] lookup for the appropriate power. 87 for t := b; len(t) > 0; { 88 n := len(t) 89 if n > 10 { 90 n = 10 91 } 92 93 total := uint64(0) 94 for _, v := range t[:n] { 95 if v > 255 { 96 return []byte("") 97 } 98 99 tmp := b58[v] 100 if tmp == 255 { 101 return []byte("") 102 } 103 total = total*58 + uint64(tmp) 104 } 105 106 answer.Mul(answer, bigRadix[n]) 107 scratch.SetUint64(total) 108 answer.Add(answer, scratch) 109 110 t = t[n:] 111 } 112 113 tmpval := answer.Bytes() 114 115 var numZeros int 116 for numZeros = 0; numZeros < len(b); numZeros++ { 117 if b[numZeros] != alphabetIdx0 { 118 break 119 } 120 } 121 flen := numZeros + len(tmpval) 122 val := make([]byte, flen) 123 copy(val[numZeros:], tmpval) 124 125 return val 126 } 127 128 // Encode encodes a byte slice to a modified base58 string. 129 func Encode(b []byte) string { 130 x := new(big.Int) 131 x.SetBytes(b) 132 133 // maximum length of output is log58(2^(8*len(b))) == len(b) * 8 / log(58) 134 maxlen := int(float64(len(b))*1.365658237309761) + 1 135 answer := make([]byte, 0, maxlen) 136 mod := new(big.Int) 137 for x.Sign() > 0 { 138 // Calculating with big.Int is slow for each iteration. 139 // x, mod = x / 58, x % 58 140 // 141 // Instead we can try to do as many calculations on int64. 142 // x, mod = x / 58^10, x % 58^10 143 // 144 // Which will give us mod, which is 10 digit base58 number. 145 // We'll loop that 10 times to convert to the answer. 146 147 x.DivMod(x, bigRadix10, mod) 148 if x.Sign() == 0 { 149 // When x = 0, we need to ensure we don't add any extra zeros. 150 m := mod.Int64() 151 for m > 0 { 152 answer = append(answer, alphabet[m%58]) 153 m /= 58 154 } 155 } else { 156 m := mod.Int64() 157 for i := 0; i < 10; i++ { 158 answer = append(answer, alphabet[m%58]) 159 m /= 58 160 } 161 } 162 } 163 164 // leading zero bytes 165 for _, i := range b { 166 if i != 0 { 167 break 168 } 169 answer = append(answer, alphabetIdx0) 170 } 171 172 // reverse 173 alen := len(answer) 174 for i := 0; i < alen/2; i++ { 175 answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] 176 } 177 178 return string(answer) 179 } 180 181 // ErrChecksum indicates that the checksum of a check-encoded string does not verify against 182 // the checksum. 183 var ErrChecksum = errors.New("checksum error") 184 185 // ErrInvalidFormat indicates that the check-encoded string has an invalid format. 186 var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing") 187 188 // checksum: first four bytes of sha256^2 189 func checksum(input []byte) (cksum [4]byte) { 190 h := sha256.Sum256(input) 191 h2 := sha256.Sum256(h[:]) 192 copy(cksum[:], h2[:4]) 193 return 194 } 195 196 // CheckEncode prepends a version byte and appends a four byte checksum. 197 func CheckEncode(input []byte, version byte) string { 198 b := make([]byte, 0, 1+len(input)+4) 199 b = append(b, version) 200 b = append(b, input...) 201 cksum := checksum(b) 202 b = append(b, cksum[:]...) 203 return Encode(b) 204 } 205 206 // CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum. 207 func CheckDecode(input string) (result []byte, version byte, err error) { 208 decoded := Decode(input) 209 if len(decoded) < 5 { 210 return nil, 0, ErrInvalidFormat 211 } 212 version = decoded[0] 213 var cksum [4]byte 214 copy(cksum[:], decoded[len(decoded)-4:]) 215 if checksum(decoded[:len(decoded)-4]) != cksum { 216 return nil, 0, ErrChecksum 217 } 218 payload := decoded[1 : len(decoded)-4] 219 result = append(result, payload...) 220 return 221 }