104 lines
2.7 KiB
Go
104 lines
2.7 KiB
Go
package moneroutil
|
|
|
|
import (
|
|
"lukechampine.com/uint128"
|
|
)
|
|
|
|
const BASE58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
|
|
var base58Lookup = map[string]int{
|
|
"1": 0, "2": 1, "3": 2, "4": 3, "5": 4, "6": 5, "7": 6, "8": 7,
|
|
"9": 8, "A": 9, "B": 10, "C": 11, "D": 12, "E": 13, "F": 14, "G": 15,
|
|
"H": 16, "J": 17, "K": 18, "L": 19, "M": 20, "N": 21, "P": 22, "Q": 23,
|
|
"R": 24, "S": 25, "T": 26, "U": 27, "V": 28, "W": 29, "X": 30, "Y": 31,
|
|
"Z": 32, "a": 33, "b": 34, "c": 35, "d": 36, "e": 37, "f": 38, "g": 39,
|
|
"h": 40, "i": 41, "j": 42, "k": 43, "m": 44, "n": 45, "o": 46, "p": 47,
|
|
"q": 48, "r": 49, "s": 50, "t": 51, "u": 52, "v": 53, "w": 54, "x": 55,
|
|
"y": 56, "z": 57,
|
|
}
|
|
|
|
var base128 = uint128.From64(58)
|
|
|
|
func encodeChunk(raw []byte, padding int, buf []byte) []byte {
|
|
var data [16]byte
|
|
copy(data[16-len(raw):], raw)
|
|
remainder := uint128.FromBytesBE(data[:])
|
|
var current uint128.Uint128
|
|
for remainder.Cmp64(0) > 0 {
|
|
remainder, current = remainder.QuoRem(base128)
|
|
buf = append(buf, BASE58[current.Lo])
|
|
}
|
|
for len(buf) < padding {
|
|
buf = append(buf, '1')
|
|
}
|
|
for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 {
|
|
buf[i], buf[j] = buf[j], buf[i]
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func decodeChunk(encoded string) (result []byte) {
|
|
var bigResult uint128.Uint128
|
|
currentMultiplier := uint128.From64(1)
|
|
for i := len(encoded) - 1; i >= 0; i-- {
|
|
bigResult = bigResult.Add(currentMultiplier.Mul64(uint64(base58Lookup[string(encoded[i])])))
|
|
currentMultiplier = currentMultiplier.Mul(base128)
|
|
}
|
|
result = make([]byte, 16)
|
|
bigResult.ReverseBytes().PutBytes(result)
|
|
switch len(encoded) {
|
|
case 0:
|
|
return result[8+8:]
|
|
case 2:
|
|
return result[8+7:]
|
|
case 3:
|
|
return result[8+6:]
|
|
case 5:
|
|
return result[8+5:]
|
|
case 6:
|
|
return result[8+4:]
|
|
case 7:
|
|
return result[8+3:]
|
|
case 9:
|
|
return result[8+2:]
|
|
case 10:
|
|
return result[8+1:]
|
|
case 11:
|
|
return result[8:]
|
|
default:
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func EncodeMoneroBase58(data ...[]byte) string {
|
|
//preallocate common case
|
|
combined := make([]byte, 0, 96)
|
|
for _, item := range data {
|
|
combined = append(combined, item...)
|
|
}
|
|
|
|
result := make([]byte, 0, len(combined)*2)
|
|
buf := make([]byte, 0, len(combined)*2)
|
|
length := len(combined)
|
|
rounds := length / 8
|
|
for i := 0; i < rounds; i++ {
|
|
result = append(result, encodeChunk(combined[i*8:(i+1)*8], 11, buf[:0])...)
|
|
}
|
|
if length%8 > 0 {
|
|
result = append(result, encodeChunk(combined[rounds*8:], 7, buf[:0])...)
|
|
}
|
|
return string(result)
|
|
}
|
|
|
|
func DecodeMoneroBase58(data string) (result []byte) {
|
|
length := len(data)
|
|
rounds := length / 11
|
|
for i := 0; i < rounds; i++ {
|
|
result = append(result, decodeChunk(data[i*11:i*11+11])...)
|
|
}
|
|
if length%11 > 0 {
|
|
result = append(result, decodeChunk(data[rounds*11:])...)
|
|
}
|
|
return
|
|
}
|