Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

digest: promote blake3 to first-class digest #66

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,3 @@ jobs:
uses: actions/checkout@v2
- name: Test
run: go test -v ./...
- name: Test Blake3
run: go test -v ./...
working-directory: blake3
37 changes: 20 additions & 17 deletions algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,22 @@ const (
// project. Other digests may be used but this one is the primary storage
// digest.
Canonical = SHA256
// BLAKE3 is the blake3 algorithm with the default 256-bit output size
// github.com/opencontainers/go-digest/blake3 should be imported to make it available
BLAKE3 Algorithm = "blake3"
)

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

// CryptoHash is the interface that any hash algorithm must implement
// CryptoHash is the interface that any digest algorithm must implement
type CryptoHash interface {
// Available reports whether the given hash function is usable in the current binary.
// Available reports whether the given hash function is usable in the
// current binary.
Available() bool
// Size returns the length, in bytes, of a digest resulting from the given hash function.
// Size returns the length, in bytes, of a digest resulting from the given
// hash function.
Size() int
// New returns a new hash.Hash calculating the given hash function. If the hash function is not
// available, it may panic.
// New returns a new hash.Hash calculating the given hash function. If the
// hash function is not available, it may panic.
New() hash.Hash
}

Expand All @@ -69,29 +68,33 @@ var (
algorithmsLock sync.RWMutex
)

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

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

if _, ok := algorithms[algorithm]; ok {
return false
}

algorithms[algorithm] = implementation
// We can do this since the Digest function below only implements a hex digest. If we open this in the future
// we need to allow for alternative digest algorithms to be implemented and for the user to pass their own

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

// NOTE(stevvooe): A missing hash is usually a programming error that
Expand Down
13 changes: 10 additions & 3 deletions blake3/blake3.go → blake3.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,24 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package blake3
package digest

import (
"hash"

"github.com/opencontainers/go-digest"
"github.com/zeebo/blake3"
)

const (
// Blake3 is the blake3 algorithm with the default 256-bit output size
Blake3 Algorithm = "blake3"

// BLAKE3 is deprecated. Use the symbol "Blake3" instead.
BLAKE3 = Blake3
)

func init() {
digest.RegisterAlgorithm(digest.BLAKE3, &blake3hash{})
RegisterAlgorithm(Blake3, &blake3hash{})
}

type blake3hash struct{}
Expand Down
38 changes: 0 additions & 38 deletions blake3/blake3_test.go

This file was deleted.

12 changes: 0 additions & 12 deletions blake3/go.mod

This file was deleted.

157 changes: 157 additions & 0 deletions digest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright 2019, 2020 OCI Contributors
// Copyright 2017 Docker, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package digest

import (
"testing"
)

func TestParseDigest(t *testing.T) {
for _, testcase := range []struct {
input string
err error
algorithm Algorithm
encoded string
}{
{
input: "sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b",
algorithm: "sha256",
encoded: "e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b",
},
{
input: "sha384:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
algorithm: "sha384",
encoded: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
},
{
input: "blake3:af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
algorithm: "blake3",
encoded: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
},
{
// empty
input: "",
err: ErrDigestInvalidFormat,
},
{
// whitespace only
input: " ",
err: ErrDigestInvalidFormat,
},
{
// empty hex
input: "sha256:",
err: ErrDigestInvalidFormat,
},
{
// hex with correct length, but whitespace only
input: "sha256: ",
err: ErrDigestInvalidFormat,
},
{
// empty hex
input: ":",
err: ErrDigestInvalidFormat,
},
{
// just hex
input: "d41d8cd98f00b204e9800998ecf8427e",
err: ErrDigestInvalidFormat,
},
{
// not hex
input: "sha256:d41d8cd98f00b204e9800m98ecf8427e",
err: ErrDigestInvalidLength,
},
{
// too short
input: "sha256:abcdef0123456789",
err: ErrDigestInvalidLength,
},
{
// too short (from different algorithm)
input: "sha512:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
err: ErrDigestInvalidLength,
},
{
input: "foo:d41d8cd98f00b204e9800998ecf8427e",
err: ErrDigestUnsupported,
},
{
// repeated separators
input: "sha384__foo+bar:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
err: ErrDigestInvalidFormat,
},
{
// ensure that we parse, but we don't have support for the algorithm
input: "sha384.foo+bar:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
algorithm: "sha384.foo+bar",
encoded: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
err: ErrDigestUnsupported,
},
{
input: "sha384_foo+bar:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
algorithm: "sha384_foo+bar",
encoded: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
err: ErrDigestUnsupported,
},
{
input: "sha256+b64:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564",
algorithm: "sha256+b64",
encoded: "LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564",
err: ErrDigestUnsupported,
},
{
input: "sha256:E58FCF7418D4390DEC8E8FB69D88C06EC07039D651FEDD3AA72AF9972E7D046B",
err: ErrDigestInvalidFormat,
},
} {
t.Run(testcase.input, func(t *testing.T) {
digest, err := Parse(testcase.input)
if err != testcase.err {
t.Fatalf("error differed from expected while parsing %q: %v != %v", testcase.input, err, testcase.err)
}

if testcase.err != nil {
return
}

if digest.Algorithm() != testcase.algorithm {
t.Fatalf("incorrect algorithm for parsed digest: %q != %q", digest.Algorithm(), testcase.algorithm)
}

if digest.Encoded() != testcase.encoded {
t.Fatalf("incorrect hex for parsed digest: %q != %q", digest.Encoded(), testcase.encoded)
}

// Parse string return value and check equality
newParsed, err := Parse(digest.String())

if err != nil {
t.Fatalf("unexpected error parsing input %q: %v", testcase.input, err)
}

if newParsed != digest {
t.Fatalf("expected equal: %q != %q", newParsed, digest)
}

newFromHex := NewDigestFromEncoded(newParsed.Algorithm(), newParsed.Encoded())
if newFromHex != digest {
t.Fatalf("%v != %v", newFromHex, digest)
}
})
}
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/opencontainers/go-digest

go 1.13

require github.com/zeebo/blake3 v0.2.0
6 changes: 4 additions & 2 deletions blake3/go.sum → go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.1.1 h1:Nbsts7DdKThRHHd+YNlqiGlRqGEF2bE2eXN+xQ1hsEs=
github.com/zeebo/blake3 v0.1.1/go.mod h1:G9pM4qQwjRzF1/v7+vabMj/c5mWpGZ2Wzo3Eb4z0pb4=
github.com/zeebo/blake3 v0.2.0 h1:1SGx3IvKWFUU/xl+/7kjdcjjMcvVSm+3dMo/N42afC8=
github.com/zeebo/blake3 v0.2.0/go.mod h1:G9pM4qQwjRzF1/v7+vabMj/c5mWpGZ2Wzo3Eb4z0pb4=
github.com/zeebo/pcg v1.0.0 h1:dt+dx+HvX8g7Un32rY9XWoYnd0NmKmrIzpHF7qiTDj0=
github.com/zeebo/pcg v1.0.0/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
golang.org/x/sys v0.0.0-20201014080544-cc95f250f6bc h1:HVFDs9bKvTxP6bh1Rj9MCSo+UmafQtI8ZWDPVwVk9g4=
golang.org/x/sys v0.0.0-20201014080544-cc95f250f6bc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Loading