27 Commits

Author SHA1 Message Date
Kevin Atkinson
caebba6233 gx publish 0.2.7 2018-07-27 17:34:30 -04:00
Kevin Atkinson
83915a874d Merge pull request #21 from multiformats/kevina/map
Enhance Multibase
2018-07-27 17:29:59 -04:00
Steven Allen
03643c33f5 ci: bump minimum go version 2018-07-27 17:02:59 -04:00
Kevin Atkinson
2eb83a994b Remove "magical" NewPrefix function, rename Prefix to Encoder. 2018-07-27 17:02:59 -04:00
Kevin Atkinson
5547437445 Enhance constructor for Prefix type. 2018-07-27 16:59:07 -04:00
Kevin Atkinson
a0557075ec Add prefix type that guarantees a valid multibase prefix. 2018-07-27 16:59:07 -04:00
Kevin Atkinson
3ea5c212ef Add maps for converting from the string repr. to the code and back. 2018-07-27 16:59:01 -04:00
Steven Allen
dfd5076869 gx publish 0.2.6 2017-12-18 10:32:30 -08:00
Steven Allen
3f064509b3 Merge pull request #20 from multiformats/feat/faster
Switch to a faster base58 library
2017-12-18 18:19:05 +00:00
Steven Allen
23774f6467 add benchmarks 2017-12-18 10:08:13 -08:00
Steven Allen
f61622a056 significantly faster b58
* 8-10x faster round trip.
* only 3 allocations each way.
2017-12-17 18:15:25 -08:00
Steven Allen
54686aabd4 gx publish 0.2.5 2017-08-28 20:11:12 -07:00
Steven Allen
307fbd560c Merge pull request #19 from multiformats/kevina/case-preserving
Make base16 and base32 encodings case preserving
2017-08-28 19:32:25 -07:00
Kevin Atkinson
36e6b228ae Move base16 and base32 specific bits to their own file. 2017-08-28 21:33:06 -04:00
Kevin Atkinson
e81eb6473b Change length of test string to encode
to distinguish between padded and unpadded base32 encodings.
2017-08-28 21:33:06 -04:00
Kevin Atkinson
e98e07ec91 Make base16 encoding case preserving. 2017-08-28 21:33:06 -04:00
Kevin Atkinson
8d1441db4b Make base32 encodings case preserving. 2017-08-28 21:33:06 -04:00
Kevin Atkinson
5e86107a34 Update base32 dep. and bump go version to 1.8.3 in CI tests. 2017-08-28 21:32:51 -04:00
Jeromy
af68ad2dd7 gx publish 0.2.4 2017-05-01 17:33:12 -07:00
Jeromy
b3341b58aa Add in base64 raw encodings 2017-04-19 18:02:57 -07:00
Jakub Sztandera
81ab0b304e Merge pull request #13 from multiformats/kevina/multibase-conv
Add simple utility to convert from one base to another.
2017-03-18 00:22:41 +01:00
Jakub Sztandera
2de609b735 Merge pull request #15 from multiformats/docs-improvements
README/golint: improve readme and make golint happy
2017-03-17 18:57:48 +01:00
Jakub Sztandera
67ee8b7fc0 address CR 2017-03-17 18:47:36 +01:00
Hector Sanjuan
d74f4f4a44 README/golint: improve readme and make golint happy
License: MIT
Signed-off-by: Hector Sanjuan <code@hector.link>
2017-03-17 16:34:42 +01:00
Jakub Sztandera
8cb3334d95 Add streaming to multibase-conv 2017-03-17 15:39:31 +01:00
Kevin Atkinson
7e9a23df22 Add simple utility to convert from one base to another. 2017-03-17 15:35:26 +01:00
Jakub Sztandera
158b7d8e3c ci: fixup coverage collection 2017-03-17 15:35:26 +01:00
13 changed files with 369 additions and 36 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*.swp
multibase-conv/multibase-conv

View File

@@ -1 +1 @@
0.2.3: QmcxkxTVuURV2Ptse8TvkqH5BQDwV62X1x19JqqvbBzwUM
0.2.7: QmSbvata2WqNkqGtZNg8MR3SKwnB8iQ7vTPJgWqB8bC5kR

View File

@@ -4,7 +4,7 @@ os:
language: go
go:
- 1.7
- 1.10.2
install:
- go get -u github.com/whyrusleeping/gx
@@ -14,7 +14,7 @@ install:
script:
- gx-go rewrite
- go test -race -coverprofile=unittest.coverprofile -covermode=atomic ./...
- go test -race -coverprofile=unittest.coverprofile -covermode=atomic .
after_success:

View File

@@ -7,17 +7,34 @@
[![Travis CI](https://img.shields.io/travis/multiformats/go-multibase.svg?style=flat-square&branch=master)](https://travis-ci.org/multiformats/go-multibase)
[![codecov.io](https://img.shields.io/codecov/c/github/multiformats/go-multibase.svg?style=flat-square&branch=master)](https://codecov.io/github/multiformats/go-multibase?branch=master)
> Implementation of [multibase](https://github.com/multiformats/multibase) parser in go
> Implementation of [multibase](https://github.com/multiformats/multibase) -self identifying base encodings- in Go.
## Install
`go-multibase` is a standard Go module which can be installed with:
```sh
go get github.com/multiformats/go-multibase
```
Note that `go-multibase` is packaged with Gx, so it is recommended to use Gx to install and use it (see Usage section).
## Usage
TODO
This module is packaged with [Gx](https://github.com/whyrusleeping/gx). In order to use it in your own project it is recommended that you:
```sh
go get -u github.com/whyrusleeping/gx
go get -u github.com/whyrusleeping/gx-go
cd <your-project-repository>
gx init
gx import github.com/multiformats/go-multibase
gx install --global
gx-go --rewrite
```
Please check [Gx](https://github.com/whyrusleeping/gx) and [Gx-go](https://github.com/whyrusleeping/gx-go) documentation for more information.
## Maintainers

21
base16.go Normal file
View File

@@ -0,0 +1,21 @@
package multibase
func hexEncodeToStringUpper(src []byte) string {
dst := make([]byte, len(src)*2)
hexEncodeUpper(dst, src)
return string(dst)
}
var hextableUpper = [16]byte{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F',
}
func hexEncodeUpper(dst, src []byte) int {
for i, v := range src {
dst[i*2] = hextableUpper[v>>4]
dst[i*2+1] = hextableUpper[v&0x0f]
}
return len(src) * 2
}

17
base32.go Normal file
View File

@@ -0,0 +1,17 @@
package multibase
import (
b32 "github.com/whyrusleeping/base32"
)
var base32StdLowerPad = b32.NewEncodingCI("abcdefghijklmnopqrstuvwxyz234567")
var base32StdLowerNoPad = base32StdLowerPad.WithPadding(b32.NoPadding)
var base32StdUpperPad = b32.NewEncodingCI("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")
var base32StdUpperNoPad = base32StdUpperPad.WithPadding(b32.NoPadding)
var base32HexLowerPad = b32.NewEncodingCI("0123456789abcdefghijklmnopqrstuv")
var base32HexLowerNoPad = base32HexLowerPad.WithPadding(b32.NoPadding)
var base32HexUpperPad = b32.NewEncodingCI("0123456789ABCDEFGHIJKLMNOPQRSTUV")
var base32HexUpperNoPad = base32HexUpperPad.WithPadding(b32.NoPadding)

53
encoder.go Normal file
View File

@@ -0,0 +1,53 @@
package multibase
import (
"fmt"
)
// Encoder is a multibase encoding that is verified to be supported and
// supports an Encode method that does not return an error
type Encoder struct {
enc Encoding
}
// NewEncoder create a new Encoder from an Encoding
func NewEncoder(base Encoding) (Encoder, error) {
_, ok := EncodingToStr[base]
if !ok {
return Encoder{-1}, fmt.Errorf("Unsupported multibase encoding: %d", base)
}
return Encoder{base}, nil
}
// EncoderByName creates an encoder from a string, the string can
// either be the multibase name or single character multibase prefix
func EncoderByName(str string) (Encoder, error) {
var base Encoding
ok := true
if len(str) == 0 {
return Encoder{-1}, fmt.Errorf("Empty multibase encoding")
} else if len(str) == 1 {
base = Encoding(str[0])
_, ok = EncodingToStr[base]
} else {
base, ok = Encodings[str]
}
if !ok {
return Encoder{-1}, fmt.Errorf("Unsupported multibase encoding: %s", str)
}
return Encoder{base}, nil
}
func (p Encoder) Encoding() Encoding {
return p.enc
}
// Encode encodes the multibase using the given Encoder.
func (p Encoder) Encode(data []byte) string {
str, err := Encode(p.enc, data)
if err != nil {
// should not happen
panic(err)
}
return str
}

33
encoder_test.go Normal file
View File

@@ -0,0 +1,33 @@
package multibase
import (
"testing"
)
func TestInvalidPrefix(t *testing.T) {
_, err := NewEncoder('q')
if err == nil {
t.Error("expected failure")
}
}
func TestPrefix(t *testing.T) {
for str, base := range Encodings {
prefix, err := NewEncoder(base)
if err != nil {
t.Fatalf("NewEncoder(%c) failed: %v", base, err)
}
str1, err := Encode(base, sampleBytes)
if err != nil {
t.Fatal(err)
}
str2 := prefix.Encode(sampleBytes)
if str1 != str2 {
t.Errorf("encoded string mismatch: %s != %s", str1, str2)
}
_, err = EncoderByName(str)
if err != nil {
t.Fatalf("NewEncoder(%s) failed: %v", str, err)
}
}
}

41
multibase-conv/main.go Normal file
View File

@@ -0,0 +1,41 @@
package main
import (
"fmt"
"os"
multibase "github.com/multiformats/go-multibase"
)
func main() {
if len(os.Args) < 3 {
fmt.Printf("usage: %s <new-base> <multibase-str>...\n", os.Args[0])
os.Exit(1)
}
var newBase multibase.Encoding
if baseParm := os.Args[1]; len(baseParm) != 0 {
newBase = multibase.Encoding(baseParm[0])
} else {
fmt.Fprintln(os.Stderr, "<new-base> is empty")
os.Exit(1)
}
input := os.Args[2:]
for _, strmbase := range input {
_, data, err := multibase.Decode(strmbase)
if err != nil {
fmt.Fprintf(os.Stderr, "error while decoding: %s\n", err)
os.Exit(1)
}
newCid, err := multibase.Encode(newBase, data)
if err != nil {
fmt.Fprintf(os.Stderr, "error while encoding: %s\n", err)
os.Exit(1)
}
fmt.Println(newCid)
}
}

View File

@@ -5,12 +5,15 @@ import (
"encoding/hex"
"fmt"
b58 "github.com/jbenet/go-base58"
b58 "github.com/mr-tron/base58/base58"
b32 "github.com/whyrusleeping/base32"
)
// Encoding identifies the type of base-encoding that a multibase is carrying.
type Encoding int
// These are the encodings specified in the standard, not are all
// supported yet
const (
Identity = 0x00
Base1 = '1'
@@ -35,23 +38,80 @@ const (
Base64urlPad = 'U'
)
// Encodigs is a map of the supported encoding, unsupported encoding
// specified in standard are left out
var Encodings = map[string]Encoding{
"identity": 0x00,
"base16": 'f',
"base16upper": 'F',
"base32": 'b',
"base32upper": 'B',
"base32pad": 'c',
"base32padupper": 'C',
"base32hex": 'v',
"base32hexupper": 'V',
"base32hexpad": 't',
"base32hexpadupper": 'T',
"base58flickr": 'Z',
"base58btc": 'z',
"base64": 'm',
"base64url": 'u',
"base64pad": 'M',
"base64urlpad": 'U',
}
var EncodingToStr = map[Encoding]string{
0x00: "identity",
'f': "base16",
'F': "base16upper",
'b': "base32",
'B': "base32upper",
'c': "base32pad",
'C': "base32padupper",
'v': "base32hex",
'V': "base32hexupper",
't': "base32hexpad",
'T': "base32hexpadupper",
'Z': "base58flickr",
'z': "base58btc",
'm': "base64",
'u': "base64url",
'M': "base64pad",
'U': "base64urlpad",
}
// ErrUnsupportedEncoding is returned when the selected encoding is not known or
// implemented.
var ErrUnsupportedEncoding = fmt.Errorf("selected encoding not supported")
// Encode encodes a given byte slice with the selected encoding and returns a
// multibase string (<encoding><base-encoded-string>). It will return
// an error if the selected base is not known.
func Encode(base Encoding, data []byte) (string, error) {
switch base {
case Identity:
// 0x00 inside a string is OK in golang and causes no problems with the length calculation.
return string(Identity) + string(data), nil
case Base16, Base16Upper:
case Base16:
return string(Base16) + hex.EncodeToString(data), nil
case Base32, Base32Upper:
return string(Base32) + b32.RawStdEncoding.EncodeToString(data), nil
case Base32hex, Base32hexUpper:
return string(Base32hex) + b32.RawHexEncoding.EncodeToString(data), nil
case Base32pad, Base32padUpper:
return string(Base32pad) + b32.StdEncoding.EncodeToString(data), nil
case Base32hexPad, Base32hexPadUpper:
return string(Base32hexPad) + b32.HexEncoding.EncodeToString(data), nil
case Base16Upper:
return string(Base16Upper) + hexEncodeToStringUpper(data), nil
case Base32:
return string(Base32) + base32StdLowerNoPad.EncodeToString(data), nil
case Base32Upper:
return string(Base32Upper) + base32StdUpperNoPad.EncodeToString(data), nil
case Base32hex:
return string(Base32hex) + base32HexLowerNoPad.EncodeToString(data), nil
case Base32hexUpper:
return string(Base32hexUpper) + base32HexUpperNoPad.EncodeToString(data), nil
case Base32pad:
return string(Base32pad) + base32StdLowerPad.EncodeToString(data), nil
case Base32padUpper:
return string(Base32padUpper) + base32StdUpperPad.EncodeToString(data), nil
case Base32hexPad:
return string(Base32hexPad) + base32HexLowerPad.EncodeToString(data), nil
case Base32hexPadUpper:
return string(Base32hexPadUpper) + base32HexUpperPad.EncodeToString(data), nil
case Base58BTC:
return string(Base58BTC) + b58.EncodeAlphabet(data, b58.BTCAlphabet), nil
case Base58Flickr:
@@ -60,44 +120,60 @@ func Encode(base Encoding, data []byte) (string, error) {
return string(Base64pad) + base64.StdEncoding.EncodeToString(data), nil
case Base64urlPad:
return string(Base64urlPad) + base64.URLEncoding.EncodeToString(data), nil
case Base64url:
return string(Base64url) + base64.RawURLEncoding.EncodeToString(data), nil
case Base64:
return string(Base64) + base64.RawStdEncoding.EncodeToString(data), nil
default:
return "", ErrUnsupportedEncoding
}
}
// Decode takes a multibase string and decodes into a bytes buffer.
// It will return an error if the selected base is not known.
func Decode(data string) (Encoding, []byte, error) {
if len(data) == 0 {
return 0, nil, fmt.Errorf("cannot decode multibase for zero length string")
}
switch data[0] {
enc := Encoding(data[0])
switch enc {
case Identity:
return Identity, []byte(data[1:]), nil
case Base16, Base16Upper:
bytes, err := hex.DecodeString(data[1:])
return Base16, bytes, err
return enc, bytes, err
case Base32, Base32Upper:
bytes, err := b32.RawStdEncoding.DecodeString(data[1:])
return Base32, bytes, err
return enc, bytes, err
case Base32hex, Base32hexUpper:
bytes, err := b32.RawHexEncoding.DecodeString(data[1:])
return Base32hex, bytes, err
return enc, bytes, err
case Base32pad, Base32padUpper:
bytes, err := b32.StdEncoding.DecodeString(data[1:])
return Base32pad, bytes, err
return enc, bytes, err
case Base32hexPad, Base32hexPadUpper:
bytes, err := b32.HexEncoding.DecodeString(data[1:])
return Base32hexPad, bytes, err
return enc, bytes, err
case Base58BTC:
return Base58BTC, b58.DecodeAlphabet(data[1:], b58.BTCAlphabet), nil
bytes, err := b58.DecodeAlphabet(data[1:], b58.BTCAlphabet)
return Base58BTC, bytes, err
case Base58Flickr:
return Base58Flickr, b58.DecodeAlphabet(data[1:], b58.FlickrAlphabet), nil
bytes, err := b58.DecodeAlphabet(data[1:], b58.FlickrAlphabet)
return Base58Flickr, bytes, err
case Base64pad:
bytes, err := base64.StdEncoding.DecodeString(data[1:])
return Base64pad, bytes, err
case Base64urlPad:
bytes, err := base64.URLEncoding.DecodeString(data[1:])
return Base64urlPad, bytes, err
case Base64:
bytes, err := base64.RawStdEncoding.DecodeString(data[1:])
return Base64, bytes, err
case Base64url:
bytes, err := base64.RawURLEncoding.DecodeString(data[1:])
return Base64url, bytes, err
default:
return -1, nil, ErrUnsupportedEncoding
}

View File

@@ -6,13 +6,37 @@ import (
"testing"
)
var sampleBytes = []byte("Decentralize everything!!")
func TestMap(t *testing.T) {
for s,e := range Encodings {
s2 := EncodingToStr[e]
if s != s2 {
t.Errorf("round trip failed on encoding map: %s != %s", s, s2)
}
}
for e,s := range EncodingToStr {
e2 := Encodings[s]
if e != e2 {
t.Errorf("round trip failed on encoding map: '%c' != '%c'", e, e2)
}
}
}
var sampleBytes = []byte("Decentralize everything!!!")
var encodedSamples = map[Encoding]string{
Identity: string(0x00) + "Decentralize everything!!",
Base16: "f446563656e7472616c697a652065766572797468696e672121",
Base58BTC: "zUXE7GvtEk8XTXs1GF8HSGbVA9FCX9SEBPe",
Base64pad: "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ==",
Base64urlPad: "URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchIQ==",
Identity: string(0x00) + "Decentralize everything!!!",
Base16: "f446563656e7472616c697a652065766572797468696e67212121",
Base16Upper: "F446563656E7472616C697A652065766572797468696E67212121",
Base32: "birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee",
Base32Upper: "BIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE",
Base32pad: "cirswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee======",
Base32padUpper: "CIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE======",
Base32hex: "v8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144",
Base32hexUpper: "V8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144",
Base32hexPad: "t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======",
Base32hexPadUpper: "T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======",
Base58BTC: "z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt",
Base64pad: "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
Base64urlPad: "URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
}
func testEncode(t *testing.T, encoding Encoding, bytes []byte, expected string) {
@@ -83,3 +107,53 @@ func TestRoundTrip(t *testing.T) {
t.Fatal("shouldnt be able to decode empty string")
}
}
func BenchmarkRoundTrip(b *testing.B) {
buf := make([]byte, 32)
rand.Read(buf)
b.ResetTimer()
bases := map[string]Encoding{
"Identity": Identity,
"Base16": Base16,
"Base16Upper": Base16Upper,
"Base32": Base32,
"Base32Upper": Base32Upper,
"Base32pad": Base32pad,
"Base32padUpper": Base32padUpper,
"Base32hex": Base32hex,
"Base32hexUpper": Base32hexUpper,
"Base32hexPad": Base32hexPad,
"Base32hexPadUpper": Base32hexPadUpper,
"Base58Flickr": Base58Flickr,
"Base58BTC": Base58BTC,
"Base64": Base64,
"Base64url": Base64url,
"Base64pad": Base64pad,
"Base64urlPad": Base64urlPad,
}
for name, base := range bases {
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
enc, err := Encode(base, buf)
if err != nil {
b.Fatal(err)
}
e, out, err := Decode(enc)
if err != nil {
b.Fatal(err)
}
if e != base {
b.Fatal("got wrong encoding out")
}
if !bytes.Equal(buf, out) {
b.Fatal("input wasnt the same as output", buf, out)
}
}
})
}
}

View File

@@ -9,15 +9,15 @@
"gxDependencies": [
{
"author": "whyrusleeping",
"hash": "QmZvZSVtvxak4dcTkhsQhqd1SQ6rg5UzaSTu62WfWKjj93",
"hash": "QmfVj3x4D6Jkq9SEoi5n2NmoUomLwoeiwnYz2KQa15wRw6",
"name": "base32",
"version": "0.0.1"
"version": "0.0.2"
},
{
"author": "whyrusleeping",
"hash": "QmT8rehPR3F6bmwL6zjUN8XpiDBFFpMP2myPdC6ApsWfJf",
"name": "go-base58",
"version": "0.0.0"
"author": "mr-tron",
"hash": "QmWFAMPqsEyUX7gDUsRVmMWz59FxSpJ1b2v6bJ1yYzo7jY",
"name": "go-base58-fast",
"version": "0.1.1"
}
],
"gxVersion": "0.8.0",
@@ -25,6 +25,6 @@
"license": "",
"name": "go-multibase",
"releaseCmd": "git commit -a -m \"gx publish $VERSION\"",
"version": "0.2.3"
"version": "0.2.7"
}