Skip to content

Commit c3ba253

Browse files
authored
Merge pull request #136 from multiformats/dependency-separation
Refactor registry system: no direct dependencies; expose standard hash.Hash; be a data carrier.
2 parents 6f1ea18 + cebc9f8 commit c3ba253

18 files changed

+462
-378
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ os:
44
language: go
55

66
go:
7-
- 1.11.x
7+
- 1.15.x
88

99
env:
1010
global:

core/errata.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package multihash
2+
3+
import (
4+
"bytes"
5+
"crypto/sha256"
6+
"hash"
7+
)
8+
9+
type identityMultihash struct {
10+
bytes.Buffer
11+
}
12+
13+
func (identityMultihash) BlockSize() int {
14+
return 32 // A prefered block size is nonsense for the "identity" "hash". An arbitrary but unsurprising and positive nonzero number has been chosen to minimize the odds of fascinating bugs.
15+
}
16+
17+
func (x *identityMultihash) Size() int {
18+
return x.Len()
19+
}
20+
21+
func (x *identityMultihash) Sum(digest []byte) []byte {
22+
return x.Bytes()
23+
}
24+
25+
type doubleSha256 struct {
26+
hash.Hash
27+
}
28+
29+
func (x doubleSha256) Sum(digest []byte) []byte {
30+
digest = x.Hash.Sum(digest)
31+
h2 := sha256.New()
32+
h2.Write(digest)
33+
return h2.Sum(digest[0:0])
34+
}

core/magic.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package multihash
2+
3+
import "errors"
4+
5+
// ErrSumNotSupported is returned when the Sum function code is not implemented
6+
var ErrSumNotSupported = errors.New("no such hash registered")
7+
8+
// constants
9+
const (
10+
IDENTITY = 0x00
11+
SHA1 = 0x11
12+
SHA2_256 = 0x12
13+
SHA2_512 = 0x13
14+
SHA3_224 = 0x17
15+
SHA3_256 = 0x16
16+
SHA3_384 = 0x15
17+
SHA3_512 = 0x14
18+
KECCAK_224 = 0x1A
19+
KECCAK_256 = 0x1B
20+
KECCAK_384 = 0x1C
21+
KECCAK_512 = 0x1D
22+
SHAKE_128 = 0x18
23+
SHAKE_256 = 0x19
24+
MD5 = 0xd5
25+
DBL_SHA2_256 = 0x56
26+
)

core/registry.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package multihash
2+
3+
import (
4+
"crypto/md5"
5+
"crypto/sha1"
6+
"crypto/sha256"
7+
"crypto/sha512"
8+
"fmt"
9+
"hash"
10+
)
11+
12+
// registry is a simple map which maps a multihash indicator number
13+
// to a standard golang Hash interface.
14+
//
15+
// Multihash indicator numbers are reserved and described in
16+
// https://github.com/multiformats/multicodec/blob/master/table.csv .
17+
// The keys used in this map must match those reservations.
18+
//
19+
// Hashers which are available in the golang stdlib will be registered automatically.
20+
// Others can be added using the Register function.
21+
var registry = make(map[uint64]func() hash.Hash)
22+
23+
// Register adds a new hash to the set available from GetHasher and Sum.
24+
//
25+
// Register has a global effect and should only be used at package init time to avoid data races.
26+
//
27+
// The indicator code should be per the numbers reserved and described in
28+
// https://github.com/multiformats/multicodec/blob/master/table.csv .
29+
//
30+
// If Register is called with the same indicator code more than once, the last call wins.
31+
// In practice, this means that if an application has a strong opinion about what implementation to use for a certain hash
32+
// (e.g., perhaps they want to override the sha256 implementation to use a special hand-rolled assembly variant
33+
// rather than the stdlib one which is registered by default),
34+
// then this can be done by making a Register call with that effect at init time in the application's main package.
35+
// This should have the desired effect because the root of the import tree has its init time effect last.
36+
func Register(indicator uint64, hasherFactory func() hash.Hash) {
37+
if hasherFactory == nil {
38+
panic("not sensible to attempt to register a nil function")
39+
}
40+
registry[indicator] = hasherFactory
41+
DefaultLengths[indicator] = hasherFactory().Size()
42+
}
43+
44+
// GetHasher returns a new hash.Hash according to the indicator code number provided.
45+
//
46+
// The indicator code should be per the numbers reserved and described in
47+
// https://github.com/multiformats/multicodec/blob/master/table.csv .
48+
//
49+
// The actual hashers available are determined by what has been registered.
50+
// The registry automatically contains those hashers which are available in the golang standard libraries
51+
// (which includes md5, sha1, sha256, sha384, sha512, and the "identity" mulithash, among others).
52+
// Other hash implementations can be made available by using the Register function.
53+
// The 'go-mulithash/register/*' packages can also be imported to gain more common hash functions.
54+
//
55+
// If an error is returned, it will match `errors.Is(err, ErrSumNotSupported)`.
56+
func GetHasher(indicator uint64) (hash.Hash, error) {
57+
factory, exists := registry[indicator]
58+
if !exists {
59+
return nil, fmt.Errorf("unknown multihash code %d (0x%x): %w", indicator, indicator, ErrSumNotSupported)
60+
}
61+
return factory(), nil
62+
}
63+
64+
// DefaultLengths maps a multihash indicator code to the output size for that hash, in units of bytes.
65+
//
66+
// This map is populated when a hash function is registered by the Register function.
67+
// It's effectively a shortcut for asking Size() on the hash.Hash.
68+
var DefaultLengths = map[uint64]int{}
69+
70+
func init() {
71+
Register(IDENTITY, func() hash.Hash { return &identityMultihash{} })
72+
Register(MD5, md5.New)
73+
Register(SHA1, sha1.New)
74+
Register(SHA2_256, sha256.New)
75+
Register(SHA2_512, sha512.New)
76+
Register(DBL_SHA2_256, func() hash.Hash { return &doubleSha256{sha256.New()} })
77+
}

go.mod

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ module github.com/multiformats/go-multihash
22

33
require (
44
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
5-
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771
6-
github.com/mr-tron/base58 v1.1.3
7-
github.com/multiformats/go-varint v0.0.5
8-
github.com/spaolacci/murmur3 v1.1.0
9-
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8
5+
github.com/minio/sha256-simd v1.0.0
6+
github.com/mr-tron/base58 v1.2.0
7+
github.com/multiformats/go-varint v0.0.6
8+
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
9+
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 // indirect
1010
)
1111

1212
go 1.13

go.sum

+14-16
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1+
github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw=
2+
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
13
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
24
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
3-
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771 h1:MHkK1uRtFbVqvAgvWxafZe54+5uBxLluGylDiKgdhwo=
4-
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
5-
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
6-
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
7-
github.com/multiformats/go-varint v0.0.3 h1:1OZFaq4XbSNQE6ujqgr6/EIZlgHE7DmojAFsLqAJ26M=
8-
github.com/multiformats/go-varint v0.0.3/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
9-
github.com/multiformats/go-varint v0.0.4 h1:CplQWhUouUgTZ53vNFE8VoWr2VjaKXci+xyrKyyFuSw=
10-
github.com/multiformats/go-varint v0.0.4/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
11-
github.com/multiformats/go-varint v0.0.5 h1:XVZwSo04Cs3j/jS0uAEPpT3JY6DzMcVLLoWOSnCxOjg=
12-
github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
13-
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
14-
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
5+
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
6+
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
7+
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
8+
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
9+
github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
10+
github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
1511
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
16-
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU=
17-
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
12+
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
13+
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
1814
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
1915
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
20-
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
21-
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
16+
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
17+
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
18+
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
19+
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
2220
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

multihash.go

+4-39
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ func init() {
8080
name := fmt.Sprintf("blake2b-%d", n*8)
8181
Names[name] = c
8282
Codes[c] = name
83-
DefaultLengths[c] = int(n)
8483
}
8584

8685
// Add blake2s (32 codes)
@@ -89,7 +88,6 @@ func init() {
8988
name := fmt.Sprintf("blake2s-%d", n*8)
9089
Names[name] = c
9190
Codes[c] = name
92-
DefaultLengths[c] = int(n)
9391
}
9492
}
9593

@@ -142,28 +140,6 @@ var Codes = map[uint64]string{
142140
MD5: "md5",
143141
}
144142

145-
// DefaultLengths maps a hash code to it's default length
146-
var DefaultLengths = map[uint64]int{
147-
IDENTITY: -1,
148-
SHA1: 20,
149-
SHA2_256: 32,
150-
SHA2_512: 64,
151-
SHA3_224: 28,
152-
SHA3_256: 32,
153-
SHA3_384: 48,
154-
SHA3_512: 64,
155-
DBL_SHA2_256: 32,
156-
KECCAK_224: 28,
157-
KECCAK_256: 32,
158-
MURMUR3_128: 4,
159-
KECCAK_384: 48,
160-
KECCAK_512: 64,
161-
SHAKE_128: 32,
162-
SHAKE_256: 64,
163-
X11: 64,
164-
MD5: 16,
165-
}
166-
167143
func uvarint(buf []byte) (uint64, []byte, error) {
168144
n, c, err := varint.FromUvarint(buf)
169145
if err != nil {
@@ -231,15 +207,11 @@ func FromB58String(s string) (m Multihash, err error) {
231207
// Cast casts a buffer onto a multihash, and returns an error
232208
// if it does not work.
233209
func Cast(buf []byte) (Multihash, error) {
234-
dm, err := Decode(buf)
210+
_, err := Decode(buf)
235211
if err != nil {
236212
return Multihash{}, err
237213
}
238214

239-
if !ValidCode(dm.Code) {
240-
return Multihash{}, ErrUnknownCode
241-
}
242-
243215
return Multihash(buf), nil
244216
}
245217

@@ -266,11 +238,10 @@ func Decode(buf []byte) (*DecodedMultihash, error) {
266238

267239
// Encode a hash digest along with the specified function code.
268240
// Note: the length is derived from the length of the digest itself.
241+
//
242+
// The error return is legacy; it is always nil.
269243
func Encode(buf []byte, code uint64) ([]byte, error) {
270-
if !ValidCode(code) {
271-
return nil, ErrUnknownCode
272-
}
273-
244+
// FUTURE: this function always causes heap allocs... but when used, this value is almost always going to be appended to another buffer (either as part of CID creation, or etc) -- should this whole function be rethought and alternatives offered?
274245
newBuf := make([]byte, varint.UvarintSize(code)+varint.UvarintSize(uint64(len(buf)))+len(buf))
275246
n := varint.PutUvarint(newBuf, code)
276247
n += varint.PutUvarint(newBuf[n:], uint64(len(buf)))
@@ -285,12 +256,6 @@ func EncodeName(buf []byte, name string) ([]byte, error) {
285256
return Encode(buf, Names[name])
286257
}
287258

288-
// ValidCode checks whether a multihash code is valid.
289-
func ValidCode(code uint64) bool {
290-
_, ok := Codes[code]
291-
return ok
292-
}
293-
294259
// readMultihashFromBuf reads a multihash from the given buffer, returning the
295260
// individual pieces of the multihash.
296261
// Note: the returned digest is a slice over the passed in data and should be

multihash/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
mh "github.com/multiformats/go-multihash"
1010
mhopts "github.com/multiformats/go-multihash/opts"
11+
_ "github.com/multiformats/go-multihash/register/all"
12+
_ "github.com/multiformats/go-multihash/register/miniosha256"
1113
)
1214

1315
var usage = `usage: %s [options] [FILE]

multihash_test.go

-10
Original file line numberDiff line numberDiff line change
@@ -224,16 +224,6 @@ func ExampleDecode() {
224224
// obj: sha1 0x11 20 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33
225225
}
226226

227-
func TestValidCode(t *testing.T) {
228-
for i := uint64(0); i < 0xff; i++ {
229-
_, ok := tCodes[i]
230-
231-
if ValidCode(i) != ok {
232-
t.Error("ValidCode incorrect for: ", i)
233-
}
234-
}
235-
}
236-
237227
func TestCast(t *testing.T) {
238228
for _, tc := range testCases {
239229
ob, err := hex.DecodeString(tc.hex)

opts/opts.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,13 @@ func (o *Options) ParseError() error {
110110
}
111111
o.Length = o.Length / 8
112112

113-
if o.Length > mh.DefaultLengths[o.AlgorithmCode] {
114-
o.Length = mh.DefaultLengths[o.AlgorithmCode]
113+
h, _ := mh.GetHasher(o.AlgorithmCode)
114+
hsize := 0
115+
if h != nil {
116+
hsize = h.Size()
117+
}
118+
if o.Length > hsize {
119+
o.Length = hsize
115120
}
116121
}
117122
return nil

register/all/multihash_all.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
This package has no purpose except to perform registration of mulithashes.
3+
4+
It is meant to be used as a side-effecting import, e.g.
5+
6+
import (
7+
_ "github.com/multiformats/go-multihash/register/all"
8+
)
9+
10+
This package registers many multihashes at once.
11+
Importing it will increase the size of your dependency tree significantly.
12+
It's recommended that you import this package if you're building some
13+
kind of data broker application, which may need to handle many different kinds of hashes;
14+
if you're building an application which you know only handles a specific hash,
15+
importing this package may bloat your builds unnecessarily.
16+
*/
17+
package all
18+
19+
import (
20+
_ "github.com/multiformats/go-multihash/register/blake2"
21+
_ "github.com/multiformats/go-multihash/register/sha3"
22+
)

0 commit comments

Comments
 (0)