mirror of
https://codeberg.org/readeck/readeck.git
synced 2026-05-19 11:00:36 +00:00
Added pkg/base58
This provides a base58 encoder / decoder using the Bitcoin alphabet.
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
ISC License:
|
||||
|
||||
Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
|
||||
Copyright (c) 1995-2003 by Internet Software Consortium
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -0,0 +1,191 @@
|
||||
// SPDX-FileCopyrightText: © 2013-2015 The btcsuite developers
|
||||
//
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
// Package base58 implements base58 encoding
|
||||
package base58
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// An Encoding is a radix 58 encoding/decoding scheme, defined by a
|
||||
// 58-character alphabet.
|
||||
type Encoding struct {
|
||||
encode [58]byte
|
||||
decodeMap [256]uint8
|
||||
}
|
||||
|
||||
const (
|
||||
decodeMapInitialize = "" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" +
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
invalidIndex = '\xff'
|
||||
)
|
||||
|
||||
// ErrInvalidChar is returned on decoding errors.
|
||||
var ErrInvalidChar = errors.New("invalid base58 string")
|
||||
|
||||
// NewEncoding returns a new [Encoding] defined by the given alphabet,
|
||||
// which must be a 58-bit string that contains unique byte values and does
|
||||
// not contain CR or LF characters.
|
||||
func NewEncoding(encoder string) *Encoding {
|
||||
if len(encoder) != 58 {
|
||||
panic("encoding alphabet is not 58-bytes long")
|
||||
}
|
||||
|
||||
e := new(Encoding)
|
||||
copy(e.encode[:], encoder)
|
||||
copy(e.decodeMap[:], decodeMapInitialize)
|
||||
|
||||
for i := range len(encoder) {
|
||||
switch {
|
||||
case encoder[i] == '\n' || encoder[i] == '\r':
|
||||
panic("encoding alphabet contains newline character")
|
||||
case e.decodeMap[encoder[i]] != invalidIndex:
|
||||
panic("encoding alphabet includes duplicate symbols")
|
||||
}
|
||||
e.decodeMap[encoder[i]] = uint8(i)
|
||||
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// StdEncoding is an [Encoding] with the Bitcoin alphabet.
|
||||
var StdEncoding = NewEncoding("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
|
||||
|
||||
// EncodeToString is a shortcut to [Encoding.EncodeToString] on [StdEncoding].
|
||||
func EncodeToString(src []byte) string {
|
||||
return StdEncoding.EncodeToString(src)
|
||||
}
|
||||
|
||||
// DecodeString is a shortcut to [Encoding.DecodeString] on [StdEncoding].
|
||||
func DecodeString(s string) ([]byte, error) {
|
||||
return StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// EncodeToString returns the base58 encoding of src.
|
||||
func (enc *Encoding) EncodeToString(src []byte) string {
|
||||
// Since the conversion is from base256 to base58, the max possible number
|
||||
// of bytes of output per input byte is log_58(256) ~= 1.37. Thus, the max
|
||||
// total output size is ceil(len(input) * 137/100). Rather than worrying
|
||||
// about the ceiling, just add one even if it isn't needed since the final
|
||||
// output is truncated to the right size at the end.
|
||||
output := make([]byte, (len(src)*137/100)+1)
|
||||
|
||||
// Encode to base58 in reverse order to avoid extra calculations to
|
||||
// determine the final output size in favor of just keeping track while
|
||||
// iterating.
|
||||
var index int
|
||||
for _, r := range src {
|
||||
// Multiply each byte in the output by 256 and encode to base58 while
|
||||
// propagating the carry.
|
||||
val := uint32(r)
|
||||
for i, b := range output[:index] {
|
||||
val += uint32(b) << 8
|
||||
output[i] = byte(val % 58)
|
||||
val /= 58
|
||||
}
|
||||
for ; val > 0; val /= 58 {
|
||||
output[index] = byte(val % 58)
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the calculated remainders with their corresponding base58 digit.
|
||||
for i, b := range output[:index] {
|
||||
output[i] = enc.encode[b]
|
||||
}
|
||||
|
||||
// Account for the leading zeros in the input. They are appended since the
|
||||
// encoding is happening in reverse order.
|
||||
for _, r := range src {
|
||||
if r != 0 {
|
||||
break
|
||||
}
|
||||
|
||||
output[index] = enc.encode[0]
|
||||
index++
|
||||
}
|
||||
|
||||
// Truncate the output buffer to the actual number of encoded bytes and
|
||||
// reverse it since it was calculated in reverse order.
|
||||
output = output[:index:index]
|
||||
for i := range index / 2 {
|
||||
output[i], output[index-1-i] = output[index-1-i], output[i]
|
||||
}
|
||||
|
||||
return string(output)
|
||||
}
|
||||
|
||||
// DecodeString returns the bytes represented by the base58 string s.
|
||||
func (enc *Encoding) DecodeString(s string) ([]byte, error) {
|
||||
if len(s) == 0 {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
// The max possible output size is when a base58 encoding consists of
|
||||
// nothing but the alphabet character at index 0 which would result in the
|
||||
// same number of bytes as the number of input chars.
|
||||
output := make([]byte, len(s))
|
||||
|
||||
// Encode to base256 in reverse order to avoid extra calculations to
|
||||
// determine the final output size in favor of just keeping track while
|
||||
// iterating.
|
||||
var index int
|
||||
for _, r := range []byte(s) {
|
||||
// Invalid base58 character.
|
||||
val := uint32(enc.decodeMap[r])
|
||||
if val == 255 {
|
||||
return nil, fmt.Errorf("%w (got %q)", ErrInvalidChar, r)
|
||||
}
|
||||
|
||||
// Multiply each byte in the output by 58 and encode to base256 while
|
||||
// propagating the carry.
|
||||
for i, b := range output[:index] {
|
||||
val += uint32(b) * 58
|
||||
output[i] = byte(val)
|
||||
val >>= 8
|
||||
}
|
||||
for ; val > 0; val >>= 8 {
|
||||
output[index] = byte(val)
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
// Account for the leading zeros in the input. They are appended since the
|
||||
// encoding is happening in reverse order.
|
||||
for _, r := range []byte(s) {
|
||||
if r != enc.encode[0] {
|
||||
break
|
||||
}
|
||||
|
||||
output[index] = 0
|
||||
index++
|
||||
}
|
||||
|
||||
// Truncate the output buffer to the actual number of decoded bytes and
|
||||
// reverse it since it was calculated in reverse order.
|
||||
output = output[:index:index]
|
||||
for i := range index / 2 {
|
||||
output[i], output[index-1-i] = output[index-1-i], output[i]
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
// SPDX-FileCopyrightText: © 2013-2015 The btcsuite developers
|
||||
//
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
package base58_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"codeberg.org/readeck/readeck/pkg/base58"
|
||||
)
|
||||
|
||||
func h2b(s string) []byte {
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func uuid2b(s string) []byte {
|
||||
u, err := uuid.Parse(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u[:]
|
||||
}
|
||||
|
||||
func FuzzDecode(f *testing.F) {
|
||||
for range 50 {
|
||||
l, _ := rand.Int(rand.Reader, big.NewInt(48))
|
||||
l = l.Add(l, big.NewInt(1))
|
||||
data := make([]byte, l.Int64())
|
||||
_, _ = io.ReadFull(rand.Reader, data)
|
||||
f.Add(data)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, a []byte) {
|
||||
s := base58.EncodeToString(a)
|
||||
decoded, err := base58.DecodeString(s)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err.Error())
|
||||
}
|
||||
if !bytes.Equal(decoded, a) {
|
||||
t.Fatalf("got: %q, wanted: %q", decoded, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestB58(t *testing.T) {
|
||||
tests := []struct {
|
||||
decoded []byte
|
||||
encoded string
|
||||
}{
|
||||
// String inputs
|
||||
{[]byte(""), ""},
|
||||
{[]byte(" "), "Z"},
|
||||
{[]byte("-"), "n"},
|
||||
{[]byte("0"), "q"},
|
||||
{[]byte("1"), "r"},
|
||||
{[]byte("-1"), "4SU"},
|
||||
{[]byte("11"), "4k8"},
|
||||
{[]byte("abc"), "ZiCa"},
|
||||
{[]byte("1234598760"), "3mJr7AoUXx2Wqd"},
|
||||
{[]byte("abcdefghijklmnopqrstuvwxyz"), "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"},
|
||||
{[]byte("00000000000000000000000000000000000000000000000000000000000000"), "3sN2THZeE9Eh9eYrwkvZqNstbHGvrxSAM7gXUXvyFQP8XvQLUqNCS27icwUeDT7ckHm4FUHM2mTVh1vbLmk7y"},
|
||||
|
||||
// Hex inputs.
|
||||
{h2b("61"), "2g"},
|
||||
{h2b("626262"), "a3gV"},
|
||||
{h2b("636363"), "aPEr"},
|
||||
{h2b("73696d706c792061206c6f6e6720737472696e67"), "2cFupjhnEsSn59qHXstmK2ffpLv2"},
|
||||
{h2b("00eb15231dfceb60925886b67d065299925915aeb172c06647"), "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"},
|
||||
{h2b("516b6fcd0f"), "ABnLTmg"},
|
||||
{h2b("bf4f89001e670274dd"), "3SEo3LWLoPntC"},
|
||||
{h2b("572e4794"), "3EFU7m"},
|
||||
{h2b("ecac89cad93923c02321"), "EJDM8drfXA6uyA"},
|
||||
{h2b("10c8511e"), "Rt5zm"},
|
||||
{h2b("00000000000000000000"), "1111111111"},
|
||||
|
||||
// some uuids
|
||||
{uuid2b("dc5eb8be-935e-48b4-a875-bb6d56ab9484"), "UDJwD3JQyUzSXnDfcPwAPM"},
|
||||
{uuid2b("b27d7e58-0c7e-4417-9840-9e68d75ac596"), "P3N2J7smMHsDXtdXWsvmLH"},
|
||||
{uuid2b("fc9ab497-af12-4fbc-9ef3-346ffea2e599"), "YCB6ukdJeQQ8jbLMNauV9A"},
|
||||
{uuid2b("ac95ae33-3c18-4ab3-b8c8-526132c55218"), "NK4s4dXFhbwtWwKH9yXCFq"},
|
||||
{uuid2b("3f865103-a1da-45cc-b894-b9e1dec58eec"), "8qyDaaR9jNMbz4rYWTk2zf"},
|
||||
{uuid2b("a318acb5-3ee9-4a61-8975-730e27549e7b"), "M97QsaYJkj7Tip6F9WLUT4"},
|
||||
{uuid2b("80dd05ff-67cf-4de4-b240-5d3db038f62c"), "Guvywu2ufH7AMMUUsMPU6F"},
|
||||
{uuid2b("21842526-3c7d-4e99-816e-43f7dd37350a"), "593fNsVRSkgnKrib8SFPHs"},
|
||||
{uuid2b("b34e9b20-2bb5-4a09-9d99-bf4f20b649b2"), "P9DLmQXawztNSGtkrsAugd"},
|
||||
{uuid2b("a2449574-ac5b-4511-87d9-99bfd001e3a7"), "M3BG6Jn4pMENMkTfXk5a7Q"},
|
||||
{uuid2b("77bef40c-13d4-46a2-9116-ad7092c680e8"), "FndaZicXRsG5zSqeFFoFSw"},
|
||||
{uuid2b("9159cde1-627f-4b85-9e43-da4a05f1be44"), "Jx1snke6pW2RtHkNnjDCQb"},
|
||||
{uuid2b("259e84fe-2555-4367-a248-2d42bd3f96c8"), "5eS4da452PjehJ2DdpnnGP"},
|
||||
{uuid2b("6357b08e-6c64-4834-bb90-a917d572bb27"), "DGVzkMz9nykcjcQNZLdRUS"},
|
||||
{uuid2b("03630f49-289d-4a95-9f68-c07531319208"), "RFwimz6svzb8tTeTMBXiw"},
|
||||
{uuid2b("6ba37d99-6e30-42a9-9f6a-63d284c7b460"), "EHvCo2reWqyVXMXSMLBFXV"},
|
||||
{uuid2b("9e378a71-3df5-460e-b56c-5db68f9289f9"), "LYAVWbvkvBaeDA8U9N4Gba"},
|
||||
{uuid2b("7ffa494b-73de-4da2-95f0-8411462bf899"), "Gob4jwjbav6BVn4DKfdmHA"},
|
||||
{uuid2b("451f6ec9-d04a-40ac-83d6-9598f17303e4"), "9Y4gKd7pkQTseaLS7sUYy1"},
|
||||
{uuid2b("17e5c85f-2f96-48c9-8cf4-4034d845aedd"), "3xA5raxdqfTHkPvPKqfQwv"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if s := base58.EncodeToString(test.decoded); s != test.encoded {
|
||||
t.Errorf("Encode test %d failed: got: %q, wanted: %q", i, s, test.encoded)
|
||||
}
|
||||
|
||||
b, err := base58.DecodeString(test.encoded)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error in test %d: %s", i, err.Error())
|
||||
}
|
||||
if !bytes.Equal(b, test.decoded) {
|
||||
t.Errorf("Decode test %d failed: got: %x, wanted: %x", i, b, test.decoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeError(t *testing.T) {
|
||||
_, err := base58.DecodeString("🙂")
|
||||
if err == nil || !errors.Is(err, base58.ErrInvalidChar) {
|
||||
t.Errorf("wanted an error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewEncodingErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
encoder string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
"aa",
|
||||
"encoding alphabet is not 58-bytes long",
|
||||
},
|
||||
{
|
||||
"\n23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",
|
||||
"encoding alphabet contains newline character",
|
||||
},
|
||||
{
|
||||
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnZpqrstuvwxyz",
|
||||
"encoding alphabet includes duplicate symbols",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(strconv.Itoa(i+1), func(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != test.err {
|
||||
t.Fatalf("wanted: %q, got: %q", test.err, r)
|
||||
}
|
||||
}()
|
||||
|
||||
base58.NewEncoding(test.encoder)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzUUID(f *testing.F) {
|
||||
for range 5 {
|
||||
data := base58.NewUUID()
|
||||
f.Add(data)
|
||||
}
|
||||
|
||||
f.Fuzz(func(t *testing.T, a string) {
|
||||
decoded, err := base58.DecodeUUID(a)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err.Error())
|
||||
}
|
||||
encoded := base58.EncodeUUID(decoded)
|
||||
if encoded != a {
|
||||
t.Fatalf("got: %q, wanted: %q", decoded, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncodeUUID(t *testing.T) {
|
||||
tests := []struct {
|
||||
decoded []byte
|
||||
encoded string
|
||||
}{
|
||||
// some uuids
|
||||
{uuid2b("dc5eb8be-935e-48b4-a875-bb6d56ab9484"), "UDJwD3JQyUzSXnDfcPwAPM"},
|
||||
{uuid2b("b27d7e58-0c7e-4417-9840-9e68d75ac596"), "P3N2J7smMHsDXtdXWsvmLH"},
|
||||
{uuid2b("fc9ab497-af12-4fbc-9ef3-346ffea2e599"), "YCB6ukdJeQQ8jbLMNauV9A"},
|
||||
{uuid2b("ac95ae33-3c18-4ab3-b8c8-526132c55218"), "NK4s4dXFhbwtWwKH9yXCFq"},
|
||||
{uuid2b("3f865103-a1da-45cc-b894-b9e1dec58eec"), "8qyDaaR9jNMbz4rYWTk2zf"},
|
||||
{uuid2b("a318acb5-3ee9-4a61-8975-730e27549e7b"), "M97QsaYJkj7Tip6F9WLUT4"},
|
||||
{uuid2b("80dd05ff-67cf-4de4-b240-5d3db038f62c"), "Guvywu2ufH7AMMUUsMPU6F"},
|
||||
{uuid2b("21842526-3c7d-4e99-816e-43f7dd37350a"), "593fNsVRSkgnKrib8SFPHs"},
|
||||
{uuid2b("b34e9b20-2bb5-4a09-9d99-bf4f20b649b2"), "P9DLmQXawztNSGtkrsAugd"},
|
||||
{uuid2b("a2449574-ac5b-4511-87d9-99bfd001e3a7"), "M3BG6Jn4pMENMkTfXk5a7Q"},
|
||||
{uuid2b("77bef40c-13d4-46a2-9116-ad7092c680e8"), "FndaZicXRsG5zSqeFFoFSw"},
|
||||
{uuid2b("9159cde1-627f-4b85-9e43-da4a05f1be44"), "Jx1snke6pW2RtHkNnjDCQb"},
|
||||
{uuid2b("259e84fe-2555-4367-a248-2d42bd3f96c8"), "5eS4da452PjehJ2DdpnnGP"},
|
||||
{uuid2b("6357b08e-6c64-4834-bb90-a917d572bb27"), "DGVzkMz9nykcjcQNZLdRUS"},
|
||||
{uuid2b("03630f49-289d-4a95-9f68-c07531319208"), "RFwimz6svzb8tTeTMBXiw"},
|
||||
{uuid2b("6ba37d99-6e30-42a9-9f6a-63d284c7b460"), "EHvCo2reWqyVXMXSMLBFXV"},
|
||||
{uuid2b("9e378a71-3df5-460e-b56c-5db68f9289f9"), "LYAVWbvkvBaeDA8U9N4Gba"},
|
||||
{uuid2b("7ffa494b-73de-4da2-95f0-8411462bf899"), "Gob4jwjbav6BVn4DKfdmHA"},
|
||||
{uuid2b("451f6ec9-d04a-40ac-83d6-9598f17303e4"), "9Y4gKd7pkQTseaLS7sUYy1"},
|
||||
{uuid2b("17e5c85f-2f96-48c9-8cf4-4034d845aedd"), "3xA5raxdqfTHkPvPKqfQwv"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
u := uuid.UUID(test.decoded)
|
||||
|
||||
if s := base58.EncodeUUID(u); s != test.encoded {
|
||||
t.Errorf("Encode test %d failed: got: %q, wanted: %q", i, s, test.encoded)
|
||||
}
|
||||
|
||||
decoded, err := base58.DecodeUUID(test.encoded)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error in test %d: %s", i, err.Error())
|
||||
}
|
||||
if !bytes.Equal(decoded[:], test.decoded) {
|
||||
t.Errorf("Decode test %d failed: got: %x, wanted: %x", i, decoded, test.decoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeUUIDError(t *testing.T) {
|
||||
_, err := base58.DecodeUUID("abcd")
|
||||
if err == nil || !errors.Is(err, base58.ErrInvalidLenght) {
|
||||
t.Errorf("wanted an error, got: %v", err)
|
||||
}
|
||||
|
||||
_, err = base58.DecodeUUID("🙂")
|
||||
if err == nil || !errors.Is(err, base58.ErrInvalidChar) {
|
||||
t.Errorf("wanted an error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkB32EncodeToString(b *testing.B) {
|
||||
id := uuid.New()
|
||||
b.ResetTimer()
|
||||
|
||||
for b.Loop() {
|
||||
base32.HexEncoding.EncodeToString(id[:])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkB32DecodeString(b *testing.B) {
|
||||
id := uuid.New()
|
||||
encoded := base32.HexEncoding.EncodeToString(id[:])
|
||||
var err error
|
||||
b.ResetTimer()
|
||||
|
||||
for b.Loop() {
|
||||
_, err = base32.HexEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeToString(b *testing.B) {
|
||||
id := uuid.New()
|
||||
b.ResetTimer()
|
||||
|
||||
for b.Loop() {
|
||||
base58.EncodeToString(id[:])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeString(b *testing.B) {
|
||||
id := uuid.New()
|
||||
encoded := base58.EncodeToString(id[:])
|
||||
var err error
|
||||
b.ResetTimer()
|
||||
|
||||
for b.Loop() {
|
||||
_, err = base58.DecodeString(encoded)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: © 2025 Olivier Meunier <olivier@neokraft.net>
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
package base58
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ErrInvalidLenght is returned on UUID decoding errors.
|
||||
var ErrInvalidLenght = errors.New("invalid length")
|
||||
|
||||
// NewUUID returns a base58 encoded UUIDv4.
|
||||
func NewUUID() string {
|
||||
return EncodeUUID(uuid.New())
|
||||
}
|
||||
|
||||
// EncodeUUID encodes an UUID to a base58 string.
|
||||
func EncodeUUID(u uuid.UUID) string {
|
||||
return EncodeToString(u[:])
|
||||
}
|
||||
|
||||
// DecodeUUID decodes a string to an UUID.
|
||||
func DecodeUUID(s string) (u uuid.UUID, err error) {
|
||||
var b []byte
|
||||
if b, err = DecodeString(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(b) != 16 {
|
||||
err = fmt.Errorf("%w (wants 16, got %d)", ErrInvalidLenght, len(b))
|
||||
return
|
||||
}
|
||||
|
||||
return uuid.UUID(b), nil
|
||||
}
|
||||
Reference in New Issue
Block a user