Skip to content

Commit 66e27ab

Browse files
committed
digest: promote blake3 to first-class digest
The dual module approach for blake3 was slightly awkward. Since it provides similar usability with a massive bump in performance, it's extremely likely to land as a registered algorithm in the image-spec. This PR removes the secondary module, which made it difficult to test as a unit. This may break users who are using HEAD versions of the package. For a new release, this will be backwards compatible. The other drawback is that the zeebo/blake3 will now be a dependency but this can be replaced transparently by the standard libary in the future. Signed-off-by: Stephen Day <[email protected]>
1 parent 7dfe611 commit 66e27ab

File tree

10 files changed

+204
-266
lines changed

10 files changed

+204
-266
lines changed

algorithm.go

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,22 @@ const (
3434
// project. Other digests may be used but this one is the primary storage
3535
// digest.
3636
Canonical = SHA256
37-
// BLAKE3 is the blake3 algorithm with the default 256-bit output size
38-
// github.com/opencontainers/go-digest/blake3 should be imported to make it available
39-
BLAKE3 Algorithm = "blake3"
4037
)
4138

4239
var (
4340
algorithmRegexp = regexp.MustCompile(`^[a-z0-9]+([+._-][a-z0-9]+)*$`)
4441
)
4542

46-
// CryptoHash is the interface that any hash algorithm must implement
43+
// CryptoHash is the interface that any digest algorithm must implement
4744
type CryptoHash interface {
48-
// Available reports whether the given hash function is usable in the current binary.
45+
// Available reports whether the given hash function is usable in the
46+
// current binary.
4947
Available() bool
50-
// Size returns the length, in bytes, of a digest resulting from the given hash function.
48+
// Size returns the length, in bytes, of a digest resulting from the given
49+
// hash function.
5150
Size() int
52-
// New returns a new hash.Hash calculating the given hash function. If the hash function is not
53-
// available, it may panic.
51+
// New returns a new hash.Hash calculating the given hash function. If the
52+
// hash function is not available, it may panic.
5453
New() hash.Hash
5554
}
5655

@@ -69,29 +68,33 @@ var (
6968
algorithmsLock sync.RWMutex
7069
)
7170

72-
// RegisterAlgorithm may be called to dynamically register an algorithm. The implementation is a CryptoHash, and
73-
// the regex is meant to match the hash portion of the algorithm. If a duplicate algorithm is already registered,
74-
// the return value is false, otherwise if registration was successful the return value is true.
71+
// RegisterAlgorithm may be called to dynamically register an algorithm. The
72+
// implementation is a CryptoHash, and the regex is meant to match the hash
73+
// portion of the algorithm. If a duplicate algorithm is already registered, the
74+
// return value is false, otherwise if registration was successful the return
75+
// value is true.
7576
//
7677
// The algorithm encoding format must be based on hex.
7778
//
78-
// The algorithm name must be conformant to the BNF specification in the OCI image-spec, otherwise the function
79-
// will panic.
79+
// The algorithm name must be conformant to the BNF specification in the OCI
80+
// image-spec, otherwise the function will panic.
8081
func RegisterAlgorithm(algorithm Algorithm, implementation CryptoHash) bool {
8182
algorithmsLock.Lock()
8283
defer algorithmsLock.Unlock()
8384

8485
if !algorithmRegexp.MatchString(string(algorithm)) {
85-
panic(fmt.Sprintf("Algorithm %s has a name which does not fit within the allowed grammar", algorithm))
86+
panic(fmt.Sprintf("algorithm %s has a name which does not fit within the allowed grammar", algorithm))
8687
}
8788

8889
if _, ok := algorithms[algorithm]; ok {
8990
return false
9091
}
9192

9293
algorithms[algorithm] = implementation
93-
// We can do this since the Digest function below only implements a hex digest. If we open this in the future
94-
// we need to allow for alternative digest algorithms to be implemented and for the user to pass their own
94+
95+
// We can do this since the Digest function below only implements a hex
96+
// digest. If we open this in the future we need to allow for alternative
97+
// digest algorithms to be implemented and for the user to pass their own
9598
// custom regexp.
9699
anchoredEncodedRegexps[algorithm] = hexDigestRegex(implementation)
97100
return true
@@ -166,7 +169,7 @@ func (a Algorithm) Hash() hash.Hash {
166169
if !a.Available() {
167170
// Empty algorithm string is invalid
168171
if a == "" {
169-
panic(fmt.Sprintf("empty digest algorithm, validate before calling Algorithm.Hash()"))
172+
panic("empty digest algorithm, validate before calling Algorithm.Hash()")
170173
}
171174

172175
// NOTE(stevvooe): A missing hash is usually a programming error that

blake3/blake3.go renamed to blake3.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,24 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14-
package blake3
14+
package digest
1515

1616
import (
1717
"hash"
1818

19-
"github.com/opencontainers/go-digest"
2019
"github.com/zeebo/blake3"
2120
)
2221

22+
const (
23+
// Blake3 is the blake3 algorithm with the default 256-bit output size
24+
Blake3 Algorithm = "blake3"
25+
26+
// BLAKE3 is deprecated. Use the symbol "Blake3" instead.
27+
BLAKE3 = Blake3
28+
)
29+
2330
func init() {
24-
digest.RegisterAlgorithm(digest.BLAKE3, &blake3hash{})
31+
RegisterAlgorithm(Blake3, &blake3hash{})
2532
}
2633

2734
type blake3hash struct{}

blake3/blake3_test.go

Lines changed: 0 additions & 38 deletions
This file was deleted.

blake3/go.mod

Lines changed: 0 additions & 12 deletions
This file was deleted.

digest_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Copyright 2019, 2020 OCI Contributors
2+
// Copyright 2017 Docker, Inc.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package digest
17+
18+
import (
19+
"testing"
20+
)
21+
22+
func TestParseDigest(t *testing.T) {
23+
for _, testcase := range []struct {
24+
input string
25+
err error
26+
algorithm Algorithm
27+
encoded string
28+
}{
29+
{
30+
input: "sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b",
31+
algorithm: "sha256",
32+
encoded: "e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b",
33+
},
34+
{
35+
input: "sha384:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
36+
algorithm: "sha384",
37+
encoded: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
38+
},
39+
{
40+
input: "blake3:af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
41+
algorithm: "blake3",
42+
encoded: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
43+
},
44+
{
45+
// empty
46+
input: "",
47+
err: ErrDigestInvalidFormat,
48+
},
49+
{
50+
// whitespace only
51+
input: " ",
52+
err: ErrDigestInvalidFormat,
53+
},
54+
{
55+
// empty hex
56+
input: "sha256:",
57+
err: ErrDigestInvalidFormat,
58+
},
59+
{
60+
// hex with correct length, but whitespace only
61+
input: "sha256: ",
62+
err: ErrDigestInvalidFormat,
63+
},
64+
{
65+
// empty hex
66+
input: ":",
67+
err: ErrDigestInvalidFormat,
68+
},
69+
{
70+
// just hex
71+
input: "d41d8cd98f00b204e9800998ecf8427e",
72+
err: ErrDigestInvalidFormat,
73+
},
74+
{
75+
// not hex
76+
input: "sha256:d41d8cd98f00b204e9800m98ecf8427e",
77+
err: ErrDigestInvalidLength,
78+
},
79+
{
80+
// too short
81+
input: "sha256:abcdef0123456789",
82+
err: ErrDigestInvalidLength,
83+
},
84+
{
85+
// too short (from different algorithm)
86+
input: "sha512:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
87+
err: ErrDigestInvalidLength,
88+
},
89+
{
90+
input: "foo:d41d8cd98f00b204e9800998ecf8427e",
91+
err: ErrDigestUnsupported,
92+
},
93+
{
94+
// repeated separators
95+
input: "sha384__foo+bar:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
96+
err: ErrDigestInvalidFormat,
97+
},
98+
{
99+
// ensure that we parse, but we don't have support for the algorithm
100+
input: "sha384.foo+bar:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
101+
algorithm: "sha384.foo+bar",
102+
encoded: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
103+
err: ErrDigestUnsupported,
104+
},
105+
{
106+
input: "sha384_foo+bar:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
107+
algorithm: "sha384_foo+bar",
108+
encoded: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
109+
err: ErrDigestUnsupported,
110+
},
111+
{
112+
input: "sha256+b64:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564",
113+
algorithm: "sha256+b64",
114+
encoded: "LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564",
115+
err: ErrDigestUnsupported,
116+
},
117+
{
118+
input: "sha256:E58FCF7418D4390DEC8E8FB69D88C06EC07039D651FEDD3AA72AF9972E7D046B",
119+
err: ErrDigestInvalidFormat,
120+
},
121+
} {
122+
t.Run(testcase.input, func(t *testing.T) {
123+
digest, err := Parse(testcase.input)
124+
if err != testcase.err {
125+
t.Fatalf("error differed from expected while parsing %q: %v != %v", testcase.input, err, testcase.err)
126+
}
127+
128+
if testcase.err != nil {
129+
return
130+
}
131+
132+
if digest.Algorithm() != testcase.algorithm {
133+
t.Fatalf("incorrect algorithm for parsed digest: %q != %q", digest.Algorithm(), testcase.algorithm)
134+
}
135+
136+
if digest.Encoded() != testcase.encoded {
137+
t.Fatalf("incorrect hex for parsed digest: %q != %q", digest.Encoded(), testcase.encoded)
138+
}
139+
140+
// Parse string return value and check equality
141+
newParsed, err := Parse(digest.String())
142+
143+
if err != nil {
144+
t.Fatalf("unexpected error parsing input %q: %v", testcase.input, err)
145+
}
146+
147+
if newParsed != digest {
148+
t.Fatalf("expected equal: %q != %q", newParsed, digest)
149+
}
150+
151+
newFromHex := NewDigestFromEncoded(newParsed.Algorithm(), newParsed.Encoded())
152+
if newFromHex != digest {
153+
t.Fatalf("%v != %v", newFromHex, digest)
154+
}
155+
})
156+
}
157+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module github.com/opencontainers/go-digest
22

33
go 1.13
4+
5+
require github.com/zeebo/blake3 v0.2.0 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
2+
github.com/zeebo/blake3 v0.2.0 h1:1SGx3IvKWFUU/xl+/7kjdcjjMcvVSm+3dMo/N42afC8=
3+
github.com/zeebo/blake3 v0.2.0/go.mod h1:G9pM4qQwjRzF1/v7+vabMj/c5mWpGZ2Wzo3Eb4z0pb4=
4+
github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
5+
golang.org/x/sys v0.0.0-20201014080544-cc95f250f6bc h1:HVFDs9bKvTxP6bh1Rj9MCSo+UmafQtI8ZWDPVwVk9g4=
6+
golang.org/x/sys v0.0.0-20201014080544-cc95f250f6bc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

0 commit comments

Comments
 (0)