pax_global_header 0000666 0000000 0000000 00000000064 15124024272 0014511 g ustar 00root root 0000000 0000000 52 comment=90d381ea80a9b935a64264dc985e5f1732f5c9bf golang-github-zeebo-blake3-0.2.4/ 0000775 0000000 0000000 00000000000 15124024272 0016504 5 ustar 00root root 0000000 0000000 golang-github-zeebo-blake3-0.2.4/.gitignore 0000664 0000000 0000000 00000000057 15124024272 0020476 0 ustar 00root root 0000000 0000000 *.pprof *.test *.txt *.out /upstream /go.work golang-github-zeebo-blake3-0.2.4/LICENSE 0000664 0000000 0000000 00000016025 15124024272 0017515 0 ustar 00root root 0000000 0000000 This work is released into the public domain with CC0 1.0. ------------------------------------------------------------------------------- Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. golang-github-zeebo-blake3-0.2.4/Makefile 0000664 0000000 0000000 00000001643 15124024272 0020150 0 ustar 00root root 0000000 0000000 asm: internal/alg/hash/hash_avx2/impl_amd64.s internal/alg/compress/compress_sse41/impl_amd64.s internal/alg/hash/hash_avx2/impl_amd64.s: avo/avx2/*.go ( cd avo; go run ./avx2 ) > internal/alg/hash/hash_avx2/impl_amd64.s internal/alg/compress/compress_sse41/impl_amd64.s: avo/sse41/*.go ( cd avo; go run ./sse41 ) > internal/alg/compress/compress_sse41/impl_amd64.s .PHONY: fmt fmt: go fmt ./... .PHONY: clean clean: rm -f internal/alg/hash/hash_avx2/impl_amd64.s rm -f internal/alg/compress/compress_sse41/impl_amd64.s .PHONY: test test: go test -race -bench=. -benchtime=1x .PHONY: vet vet: go tool dist list \ | sed -e 's#/# #g' \ | while read goos goarch; \ do \ echo $$goos $$goarch; \ GOOS=$$goos GOARCH=$$goarch CGO_ENABLED=1 GO386=softfloat go vet ./...; \ GOOS=$$goos GOARCH=$$goarch CGO_ENABLED=1 GO386=softfloat go vet -tags=purego ./...; \ done golang-github-zeebo-blake3-0.2.4/README.md 0000664 0000000 0000000 00000012724 15124024272 0017771 0 ustar 00root root 0000000 0000000 # BLAKE3
Pure Go implementation of [BLAKE3](https://blake3.io) with AVX2 and SSE4.1 acceleration. Special thanks to the excellent [avo](https://github.com/mmcloughlin/avo) making writing vectorized version much easier. # Benchmarks ## Caveats This library makes some different design decisions than the upstream Rust crate around internal buffering. Specifically, because it does not target the embedded system space, nor does it support multithreading, it elects to do its own internal buffering. This means that a user does not have to worry about providing large enough buffers to get the best possible performance, but it does worse on smaller input sizes. So some notes: - The Rust benchmarks below are all single-threaded to match this Go implementation. - I make no attempt to get precise measurements (cpu throttling, noisy environment, etc.) so please benchmark on your own systems. - These benchmarks are run on an i7-6700K which does not support AVX-512, so Rust is limited to use AVX2 at sizes above 8 kib. - I tried my best to make them benchmark the same thing, but who knows? :smile: ## Charts In this case, both libraries are able to avoid a lot of data copying and will use vectorized instructions to hash as fast as possible, and perform similarly.  For incremental writes, you must provide the Rust version large enough buffers so that it can use vectorized instructions. This Go library performs consistently regardless of the size being sent into the update function.  The downside of internal buffering is most apparent with small sizes as most time is spent initializing the hasher state. In terms of hashing rate, the difference is 3-4x, but in an absolute sense it's ~100ns (see tables below). If you wish to hash a large number of very small strings and you care about those nanoseconds, be sure to use the Reset method to avoid re-initializing the state.  ## Timing Tables ### Small | Size | Full Buffer | Reset | | Full Buffer Rate | Reset Rate | |--------|-------------|------------|-|------------------|--------------| | 64 b | `205ns` | `86.5ns` | | `312MB/s` | `740MB/s` | | 256 b | `364ns` | `250ns` | | `703MB/s` | `1.03GB/s` | | 512 b | `575ns` | `468ns` | | `892MB/s` | `1.10GB/s` | | 768 b | `795ns` | `682ns` | | `967MB/s` | `1.13GB/s` | ### Large | Size | Incremental | Full Buffer | Reset | | Incremental Rate | Full Buffer Rate | Reset Rate | |----------|-------------|-------------|------------|-|------------------|------------------|--------------| | 1 kib | `1.02µs` | `1.01µs` | `891ns` | | `1.00GB/s` | `1.01GB/s` | `1.15GB/s` | | 2 kib | `2.11µs` | `2.07µs` | `1.95µs` | | `968MB/s` | `990MB/s` | `1.05GB/s` | | 4 kib | `2.28µs` | `2.15µs` | `2.05µs` | | `1.80GB/s` | `1.90GB/s` | `2.00GB/s` | | 8 kib | `2.64µs` | `2.52µs` | `2.44µs` | | `3.11GB/s` | `3.25GB/s` | `3.36GB/s` | | 16 kib | `4.93µs` | `4.54µs` | `4.48µs` | | `3.33GB/s` | `3.61GB/s` | `3.66GB/s` | | 32 kib | `9.41µs` | `8.62µs` | `8.54µs` | | `3.48GB/s` | `3.80GB/s` | `3.84GB/s` | | 64 kib | `18.2µs` | `16.7µs` | `16.6µs` | | `3.59GB/s` | `3.91GB/s` | `3.94GB/s` | | 128 kib | `36.3µs` | `32.9µs` | `33.1µs` | | `3.61GB/s` | `3.99GB/s` | `3.96GB/s` | | 256 kib | `72.5µs` | `65.7µs` | `66.0µs` | | `3.62GB/s` | `3.99GB/s` | `3.97GB/s` | | 512 kib | `145µs` | `131µs` | `132µs` | | `3.60GB/s` | `4.00GB/s` | `3.97GB/s` | | 1024 kib | `290µs` | `262µs` | `262µs` | | `3.62GB/s` | `4.00GB/s` | `4.00GB/s` | ### No ASM | Size | Incremental | Full Buffer | Reset | | Incremental Rate | Full Buffer Rate | Reset Rate | |----------|-------------|-------------|------------|-|------------------|------------------|-------------| | 64 b | `253ns` | `254ns` | `134ns` | | `253MB/s` | `252MB/s` | `478MB/s` | | 256 b | `553ns` | `557ns` | `441ns` | | `463MB/s` | `459MB/s` | `580MB/s` | | 512 b | `948ns` | `953ns` | `841ns` | | `540MB/s` | `538MB/s` | `609MB/s` | | 768 b | `1.38µs` | `1.40µs` | `1.35µs` | | `558MB/s` | `547MB/s` | `570MB/s` | | 1 kib | `1.77µs` | `1.77µs` | `1.70µs` | | `577MB/s` | `580MB/s` | `602MB/s` | | | | | | | | | | | 1024 kib | `880µs` | `883µs` | `878µs` | | `596MB/s` | `595MB/s` | `598MB/s` | The speed caps out at around 1 kib, so most rows have been elided from the presentation. golang-github-zeebo-blake3-0.2.4/api.go 0000664 0000000 0000000 00000010165 15124024272 0017607 0 ustar 00root root 0000000 0000000 // Package blake3 provides an SSE4.1/AVX2 accelerated BLAKE3 implementation. package blake3 import ( "errors" "github.com/zeebo/blake3/internal/consts" "github.com/zeebo/blake3/internal/utils" ) // Hasher is a hash.Hash for BLAKE3. type Hasher struct { size int h hasher } // New returns a new Hasher that has a digest size of 32 bytes. // // If you need more or less output bytes than that, use Digest method. func New() *Hasher { return &Hasher{ size: 32, h: hasher{ key: consts.IV, }, } } // NewKeyed returns a new Hasher that uses the 32 byte input key and has // a digest size of 32 bytes. // // If you need more or less output bytes than that, use the Digest method. func NewKeyed(key []byte) (*Hasher, error) { if len(key) != 32 { return nil, errors.New("invalid key size") } h := &Hasher{ size: 32, h: hasher{ flags: consts.Flag_Keyed, }, } utils.KeyFromBytes(key, &h.h.key) return h, nil } // DeriveKey derives a key based on reusable key material of any // length, in the given context. The key will be stored in out, using // all of its current length. // // Context strings must be hardcoded constants, and the recommended // format is "[application] [commit timestamp] [purpose]", e.g., // "example.com 2019-12-25 16:18:03 session tokens v1". func DeriveKey(context string, material []byte, out []byte) { h := NewDeriveKey(context) _, _ = h.Write(material) _, _ = h.Digest().Read(out) } // NewDeriveKey returns a Hasher that is initialized with the context // string. See DeriveKey for details. It has a digest size of 32 bytes. // // If you need more or less output bytes than that, use the Digest method. func NewDeriveKey(context string) *Hasher { // hash the context string and use that instead of IV h := &Hasher{ size: 32, h: hasher{ key: consts.IV, flags: consts.Flag_DeriveKeyContext, }, } var buf [32]byte _, _ = h.WriteString(context) _, _ = h.Digest().Read(buf[:]) h.Reset() utils.KeyFromBytes(buf[:], &h.h.key) h.h.flags = consts.Flag_DeriveKeyMaterial return h } // Write implements part of the hash.Hash interface. It never returns an error. func (h *Hasher) Write(p []byte) (int, error) { h.h.update(p) return len(p), nil } // WriteString is like Write but specialized to strings to avoid allocations. func (h *Hasher) WriteString(p string) (int, error) { h.h.updateString(p) return len(p), nil } // Reset implements part of the hash.Hash interface. It causes the Hasher to // act as if it was newly created. func (h *Hasher) Reset() { h.h.reset() } // Clone returns a new Hasher with the same internal state. // // Modifying the resulting Hasher will not modify the original Hasher, and vice versa. func (h *Hasher) Clone() *Hasher { return &Hasher{size: h.size, h: h.h} } // Size implements part of the hash.Hash interface. It returns the number of // bytes the hash will output in Sum. func (h *Hasher) Size() int { return h.size } // BlockSize implements part of the hash.Hash interface. It returns the most // natural size to write to the Hasher. func (h *Hasher) BlockSize() int { return 64 } // Sum implements part of the hash.Hash interface. It appends the digest of // the Hasher to the provided buffer and returns it. func (h *Hasher) Sum(b []byte) []byte { if top := len(b) + h.size; top <= cap(b) && top >= len(b) { h.h.finalize(b[len(b):top]) return b[:top] } tmp := make([]byte, h.size) h.h.finalize(tmp) return append(b, tmp...) } // Digest takes a snapshot of the hash state and returns an object that can // be used to read and seek through 2^64 bytes of digest output. func (h *Hasher) Digest() *Digest { var d Digest h.h.finalizeDigest(&d) return &d } // Sum256 returns the first 256 bits of the unkeyed digest of the data. func Sum256(data []byte) (sum [32]byte) { out := Sum512(data) copy(sum[:], out[:32]) return sum } // Sum512 returns the first 512 bits of the unkeyed digest of the data. func Sum512(data []byte) (sum [64]byte) { if len(data) <= consts.ChunkLen { var d Digest compressAll(&d, data, 0, consts.IV) _, _ = d.Read(sum[:]) return sum } else { h := hasher{key: consts.IV} h.update(data) h.finalize(sum[:]) return sum } } golang-github-zeebo-blake3-0.2.4/api_example_test.go 0000664 0000000 0000000 00000007450 15124024272 0022364 0 ustar 00root root 0000000 0000000 package blake3_test import ( "bytes" "fmt" "io" "github.com/zeebo/blake3" ) func ExampleNew() { h := blake3.New() h.Write([]byte("some data")) fmt.Printf("%x\n", h.Sum(nil)) //output: // b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2 } func ExampleNewKeyed() { h1, err := blake3.NewKeyed(bytes.Repeat([]byte("1"), 32)) if err != nil { panic(err) } h2, err := blake3.NewKeyed(bytes.Repeat([]byte("2"), 32)) if err != nil { panic(err) } h1.Write([]byte("some data")) h2.Write([]byte("some data")) fmt.Printf("%x\n", h1.Sum(nil)) fmt.Printf("%x\n", h2.Sum(nil)) //output: // 107c6f88638356d73cdb80f4d56ffe50abcbd9664a80c8ab2b83b1f946ebaba1 // b4be81075bef5a2448158ee5eeddaed897fe44a564c2cb088facbe7824a25073 } func ExampleDeriveKey() { out := make([]byte, 32) // See the documentation for good practices on what the context should be. blake3.DeriveKey( "my-application v0.1.1 session tokens v1", // context []byte("some material to derive key from"), // material out, ) fmt.Printf("%x\n", out) //output: // 98a3333af735f89eb301b56eaf6a77713aa03cdb0057e5b04352a63ea9204add } func ExampleNewDeriveKey() { // See the documentation for good practices on what the context should be. h := blake3.NewDeriveKey("my-application v0.1.1 session tokens v1") h.Write([]byte("some material to derive key from")) fmt.Printf("%x\n", h.Sum(nil)) //output: // 98a3333af735f89eb301b56eaf6a77713aa03cdb0057e5b04352a63ea9204add } func ExampleHasher_Reset() { h := blake3.New() h.Write([]byte("some data")) fmt.Printf("%x\n", h.Sum(nil)) h.Reset() h.Write([]byte("some data")) fmt.Printf("%x\n", h.Sum(nil)) //output: // b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2 // b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2 } func ExampleHasher_Digest() { h := blake3.New() h.Write([]byte("some data")) d := h.Digest() out := make([]byte, 64) d.Read(out) fmt.Printf("%x\n", out[0:32]) fmt.Printf("%x\n", out[32:64]) //output: // b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2 // 1b55688951738e3a7155d6398eb56c6bc35d5bca5f139d98eb7409be51d1be32 } func ExampleHasher_Clone() { h1 := blake3.New() h1.WriteString("some") h2 := h1.Clone() fmt.Println("before:") fmt.Printf("h1: %x\n", h1.Sum(nil)) fmt.Printf("h2: %x\n\n", h2.Sum(nil)) h2.WriteString(" data") fmt.Println("h2 modified:") fmt.Printf("h1: %x\n", h1.Sum(nil)) fmt.Printf("h2: %x\n\n", h2.Sum(nil)) h1.WriteString(" data") fmt.Println("h1 converged:") fmt.Printf("h1: %x\n", h1.Sum(nil)) fmt.Printf("h2: %x\n", h2.Sum(nil)) //output: // before: // h1: 2f610cf2e7e0dc09384cbaa75b2ae5d9704ac9a5ac7f28684342856e2867c707 // h2: 2f610cf2e7e0dc09384cbaa75b2ae5d9704ac9a5ac7f28684342856e2867c707 // // h2 modified: // h1: 2f610cf2e7e0dc09384cbaa75b2ae5d9704ac9a5ac7f28684342856e2867c707 // h2: b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2 // // h1 converged: // h1: b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2 // h2: b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2 } func ExampleDigest_Seek() { h := blake3.New() h.Write([]byte("some data")) d := h.Digest() out := make([]byte, 32) d.Seek(32, io.SeekStart) d.Read(out) fmt.Printf("%x\n", out) //output: // 1b55688951738e3a7155d6398eb56c6bc35d5bca5f139d98eb7409be51d1be32 } func ExampleSum256() { digest := blake3.Sum256([]byte("some data")) fmt.Printf("%x\n", digest[:]) //output: // b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2 } func ExampleSum512() { digest := blake3.Sum512([]byte("some data")) fmt.Printf("%x\n", digest[0:32]) fmt.Printf("%x\n", digest[32:64]) //output: // b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2 // 1b55688951738e3a7155d6398eb56c6bc35d5bca5f139d98eb7409be51d1be32 } golang-github-zeebo-blake3-0.2.4/api_test.go 0000664 0000000 0000000 00000015512 15124024272 0020647 0 ustar 00root root 0000000 0000000 package blake3 import ( "bytes" "encoding/hex" "io" "strings" "testing" "github.com/zeebo/assert" ) func TestAPI_Vectors(t *testing.T) { check := func(t *testing.T, h *Hasher, input []byte, hash string) { buf := make([]byte, len(hash)/2) n, err := h.Write(input) assert.NoError(t, err) assert.Equal(t, n, len(input)) n, err = h.Digest().Read(buf) assert.NoError(t, err) assert.Equal(t, n, len(buf)) assert.Equal(t, hash, hex.EncodeToString(buf)) } t.Run("Basic", func(t *testing.T) { for _, tv := range vectors { h := New() check(t, h, tv.input(), tv.hash) } }) t.Run("Keyed", func(t *testing.T) { for _, tv := range vectors { h, err := NewKeyed([]byte(testVectorKey)) assert.NoError(t, err) check(t, h, tv.input(), tv.keyedHash) } }) t.Run("DeriveKey", func(t *testing.T) { for _, tv := range vectors { h := NewDeriveKey(testVectorContext) check(t, h, tv.input(), tv.deriveKey) } }) } func TestAPI(t *testing.T) { key := bytes.Repeat([]byte("a"), 32) context := strings.Repeat("c", 32) cases := []struct { name string new func() (*Hasher, error) data string result string size int }{ { name: "New", new: func() (*Hasher, error) { return New(), nil }, data: "", size: 32, result: "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262", }, { name: "NewKeyed", new: func() (*Hasher, error) { return NewKeyed(key) }, data: "", size: 32, result: "cbf50f0463d68fd443cdb0826f387a6f57ba6dc4983ba2460fe822552d15d2f4", }, { name: "NewDeriveKey", new: func() (*Hasher, error) { return NewDeriveKey(context), nil }, data: "", size: 32, result: "c5ce1763648ca67eecc8a471f8efccf19dd16178e91d33130d3ae67eadde71cc", }, { name: "New+SmallInput", new: func() (*Hasher, error) { return New(), nil }, data: "some data", size: 32, result: "b224a1da2bf5e72b337dc6dde457a05265a06dec8875be379e2ad2be5edb3bf2", }, { name: "New+LargeInput", new: func() (*Hasher, error) { return New(), nil }, data: strings.Repeat("a", 10240), size: 32, result: "9afd0ba102b2cc68be10ba4d383b3139b97ed36d425b82631a7a1e2424088f7e", }, { name: "New+LargeOutput", new: func() (*Hasher, error) { return New(), nil }, data: "", size: 256, result: "" + "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262" + "e00f03e7b69af26b7faaf09fcd333050338ddfe085b8cc869ca98b206c08243a" + "26f5487789e8f660afe6c99ef9e0c52b92e7393024a80459cf91f476f9ffdbda" + "7001c22e159b402631f277ca96f2defdf1078282314e763699a31c5363165421" + "cce14d30f8a03e49ee25d2ea3cd48a568957b378a65af65fc35fb3e9e12b81ca" + "2d82cdee16c68908a6772f827564336933c89e6908b2f9c7d1811c0eb795cbd5" + "898fe6f5e8af763319ca863718a59aff3d99660ef642483e217ef0c878582728" + "4fea90d42225e3cdd6a179bee852fd24e7d45b38c27b9c2f9469ea8dbdb893f0", }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { h, err := c.new() assert.NoError(t, err) n, err := h.Write([]byte(c.data)) assert.NoError(t, err) assert.Equal(t, n, len(c.data)) t.Run("Size", func(t *testing.T) { assert.Equal(t, h.Size(), 32) }) // check that we can sum multiple times, and that it does an append t.Run("Sum", func(t *testing.T) { assert.Equal(t, hex.EncodeToString(h.Sum(nil)), c.result[:64]) for i := 0; i < 64; i++ { assert.Equal(t, hex.EncodeToString(h.Sum(make([]byte, i)[:0])), c.result[:64]) } assert.Equal(t, hex.EncodeToString(h.Sum(make([]byte, 1))), "00"+c.result[:64]) }) // ensure that reset works by issuing the write again t.Run("Reset", func(t *testing.T) { _, _ = h.Write([]byte("some fake wrong data")) h.Reset() n, err := h.Write([]byte(c.data)) assert.NoError(t, err) assert.Equal(t, n, len(c.data)) assert.Equal(t, hex.EncodeToString(h.Sum(nil)), c.result[:64]) }) t.Run("Digest", func(t *testing.T) { t.Run("Read", func(t *testing.T) { // read up to i bytes of output in batches of at most size j for i := 0; i < len(c.result)/2; i++ { for j := 1; j < i; j++ { buf, d := make([]byte, i), h.Digest() for rem := buf; len(rem) > 0; { tmp := rem if len(tmp) > j { tmp = tmp[:j] } n, err := d.Read(tmp) assert.NoError(t, err) assert.Equal(t, n, len(tmp)) rem = rem[n:] } assert.Equal(t, hex.EncodeToString(buf), c.result[:2*i]) } } }) t.Run("SeekStart", func(t *testing.T) { // seek to position i and read the remainder for i := 0; i < len(c.result)/2; i++ { buf, d := make([]byte, len(c.result)/2-i), h.Digest() n64, err := d.Seek(int64(i), io.SeekStart) assert.NoError(t, err) assert.Equal(t, n64, i) n, err := d.Read(buf) assert.NoError(t, err) assert.Equal(t, n, len(buf)) assert.Equal(t, hex.EncodeToString(buf), c.result[2*i:]) } }) t.Run("SeekCurrent", func(t *testing.T) { buf, d := make([]byte, len(c.result)/2), h.Digest() // read then seek backward the amount we just read for i := 0; i < len(c.result)/2; i++ { n, err := d.Read(buf) assert.NoError(t, err) assert.Equal(t, n, len(buf)) assert.Equal(t, hex.EncodeToString(buf[:len(c.result)/2-i]), c.result[2*i:]) n64, err := d.Seek(-int64(n)+1, io.SeekCurrent) assert.NoError(t, err) assert.Equal(t, n64, i+1) } }) }) }) } } func TestAPI_Errors(t *testing.T) { var err error _, err = NewKeyed(make([]byte, 31)) assert.Error(t, err) d := New().Digest() _, err = d.Seek(-1, io.SeekStart) assert.Error(t, err) _, err = d.Seek(-1, io.SeekCurrent) assert.Error(t, err) _, err = d.Seek(0, io.SeekEnd) assert.Error(t, err) _, err = d.Seek(0, 9999) assert.Error(t, err) } func TestSum256(t *testing.T) { h := New() x := make([]byte, 1<<16) for i := range x { x[i] = byte(i) % 251 if i%32 != 0 { continue } h.Reset() _, _ = h.Write(x[:i]) var exp [32]byte _, _ = h.Digest().Read(exp[:]) got := Sum256(x[:i]) assert.Equal(t, hex.EncodeToString(got[:]), hex.EncodeToString(exp[:])) } } func TestSum512(t *testing.T) { h := New() x := make([]byte, 1<<16) for i := range x { x[i] = byte(i) % 251 if i%32 != 0 { continue } h.Reset() _, _ = h.Write(x[:i]) var exp [64]byte _, _ = h.Digest().Read(exp[:]) got := Sum512(x[:i]) assert.Equal(t, hex.EncodeToString(got[:]), hex.EncodeToString(exp[:])) } } func TestClone(t *testing.T) { sum := func(h *Hasher) string { return hex.EncodeToString(h.Sum(nil)) } h1 := New() h1.WriteString("1") h0 := h1.Clone() assert.Equal(t, sum(h1), sum(h0)) h2 := h1.Clone() assert.Equal(t, sum(h1), sum(h2)) h2.WriteString("2") assert.Equal(t, sum(h1), sum(h0)) h1.WriteString("2") assert.Equal(t, sum(h1), sum(h2)) } golang-github-zeebo-blake3-0.2.4/assets/ 0000775 0000000 0000000 00000000000 15124024272 0020006 5 ustar 00root root 0000000 0000000 golang-github-zeebo-blake3-0.2.4/assets/incremental.svg 0000664 0000000 0000000 00000330574 15124024272 0023044 0 ustar 00root root 0000000 0000000