1
0
mirror of https://github.com/opencontainers/go-digest.git synced 2025-10-28 00:55:30 +03:00

Add BLAKE3 support

This adds support for the BLAKE3 hash family with a default output
size of 256-bit.

Signed-off-by: Sargun Dhillon <sargun@sargun.me>
This commit is contained in:
Sargun Dhillon
2021-04-21 23:26:14 -07:00
parent 404628eedb
commit a63e545fe7
7 changed files with 288 additions and 152 deletions

View File

@@ -34,6 +34,9 @@ const (
// project. Other digests may be used but this one is the primary storage // project. Other digests may be used but this one is the primary storage
// digest. // digest.
Canonical = SHA256 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 ( var (

39
blake3/blake3.go Normal file
View File

@@ -0,0 +1,39 @@
// Copyright 2021 OCI Contributors
//
// 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 blake3
import (
"hash"
"github.com/opencontainers/go-digest"
"github.com/zeebo/blake3"
)
func init() {
digest.RegisterAlgorithm(digest.BLAKE3, &blake3hash{})
}
type blake3hash struct{}
func (blake3hash) Available() bool {
return true
}
func (blake3hash) Size() int {
return blake3.New().Size()
}
func (blake3hash) New() hash.Hash {
return blake3.New()
}

38
blake3/blake3_test.go Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2021 OCI Contributors
//
// 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 blake3
import (
"testing"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/go-digest/testdigest"
)
func TestBLAKE3(t *testing.T) {
testdigest.RunTestCase(t, testdigest.TestCase{
Input: "blake3:af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
Algorithm: "blake3",
Encoded: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
})
}
func TestBLAKE3Vector(t *testing.T) {
// From the BLAKE3 test vectors.
testvector := digest.BLAKE3.FromBytes([]byte{0, 1, 2, 3, 4})
expected := "blake3:b40b44dfd97e7a84a996a91af8b85188c66c126940ba7aad2e7ae6b385402aa2"
if string(testvector) != expected {
t.Fatalf("Expected: %s; Got: %s", expected, testvector)
}
}

12
blake3/go.mod Normal file
View File

@@ -0,0 +1,12 @@
module github.com/opencontainers/go-digest/blake3
go 1.15
require (
github.com/opencontainers/go-digest v0.0.0
github.com/zeebo/blake3 v0.1.1
)
replace (
github.com/opencontainers/go-digest => ../
)

View File

@@ -1,152 +0,0 @@
// 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",
},
{
// 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)
}
})
}
}

80
testdigest/testdigest.go Normal file
View File

@@ -0,0 +1,80 @@
// Copyright 2021 OCI Contributors
//
// 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.
// testdigest is a separate package, because it has some testing utilities in it that may be useful
// to other internal Algorithm implementors.
//
// It is not a stable interface and not meant for consumption outside of digest developers.
package testdigest
import (
"testing"
pkgdigest "github.com/opencontainers/go-digest"
)
type TestCase struct {
// Input the formal format of the hash, for example sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b
Input string
// If err is non-nil, then the parsing of Input is expected to return this error
Err error
// Algorithm should be an available or registered algorithm
Algorithm pkgdigest.Algorithm
// Encoded is the the encoded portion of the digest to expect, for example e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b
Encoded string
}
func RunTestCase(t *testing.T, testcase TestCase) {
digest, err := pkgdigest.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 := pkgdigest.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 := pkgdigest.NewDigestFromEncoded(newParsed.Algorithm(), newParsed.Encoded())
if newFromHex != digest {
t.Fatalf("%v != %v", newFromHex, digest)
}
}
func RunTestCases(t *testing.T, testcases []TestCase) {
for _, testcase := range testcases {
t.Run(testcase.Input, func(t *testing.T) {
RunTestCase(t, testcase)
})
}
}

View File

@@ -0,0 +1,116 @@
// Copyright 2021 OCI Contributors
//
// 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 testdigest
import (
"testing"
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/opencontainers/go-digest"
)
func TestParseDigest(t *testing.T) {
RunTestCases(t, []TestCase{
{
Input: "sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b",
Algorithm: "sha256",
Encoded: "e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b",
},
{
Input: "sha384:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
Algorithm: "sha384",
Encoded: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
},
{
// empty
Input: "",
Err: digest.ErrDigestInvalidFormat,
},
{
// whitespace only
Input: " ",
Err: digest.ErrDigestInvalidFormat,
},
{
// empty hex
Input: "sha256:",
Err: digest.ErrDigestInvalidFormat,
},
{
// hex with correct length, but whitespace only
Input: "sha256: ",
Err: digest.ErrDigestInvalidFormat,
},
{
// empty hex
Input: ":",
Err: digest.ErrDigestInvalidFormat,
},
{
// just hex
Input: "d41d8cd98f00b204e9800998ecf8427e",
Err: digest.ErrDigestInvalidFormat,
},
{
// not hex
Input: "sha256:d41d8cd98f00b204e9800m98ecf8427e",
Err: digest. ErrDigestInvalidLength,
},
{
// too short
Input: "sha256:abcdef0123456789",
Err: digest. ErrDigestInvalidLength,
},
{
// too short (from different Algorithm)
Input: "sha512:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
Err: digest.ErrDigestInvalidLength,
},
{
Input: "foo:d41d8cd98f00b204e9800998ecf8427e",
Err: digest.ErrDigestUnsupported,
},
{
// repeated separators
Input: "sha384__foo+bar:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
Err: digest.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: digest. ErrDigestUnsupported,
},
{
Input: "sha384_foo+bar:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
Algorithm: "sha384_foo+bar",
Encoded: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
Err: digest. ErrDigestUnsupported,
},
{
Input: "sha256+b64:LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564",
Algorithm: "sha256+b64",
Encoded: "LCa0a2j_xo_5m0U8HTBBNBNCLXBkg7-g-YpeiGJm564",
Err: digest. ErrDigestUnsupported,
},
{
Input: "sha256:E58FCF7418D4390DEC8E8FB69D88C06EC07039D651FEDD3AA72AF9972E7D046B",
Err: digest. ErrDigestInvalidFormat,
},
})
}