Skip to content
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
27 changes: 27 additions & 0 deletions parser/internal/blake2b/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright 2009 The Go Authors.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
158 changes: 158 additions & 0 deletions parser/internal/blake2b/blake2b.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Vendored from golang.org/x/crypto@v0.45.0/blake2b with
// personalization support added for ZIP 244 transaction ID
// computation. Only BLAKE2b-256 is retained; ASM, XOF,
// marshal, and larger sizes have been removed.

package blake2b

import (
"encoding/binary"
"hash"
)

const (
// BlockSize is the block size of BLAKE2b in bytes.
BlockSize = 128
// Size256 is the hash size of BLAKE2b-256 in bytes.
Size256 = 32
// size is the internal full state size (64 bytes).
size = 64
)

var iv = [8]uint64{
0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179,
}

type digest struct {
h [8]uint64
c [2]uint64
sz int
block [BlockSize]byte
offset int
key [BlockSize]byte
keyLen int
personalization [16]byte
}

// New256Personalized returns a new hash.Hash computing BLAKE2b-256 with the
// given 16-byte personalization string (as required by ZIP 244).
func New256Personalized(personalization [16]byte) hash.Hash {
d := &digest{
sz: Size256,
personalization: personalization,
}
d.Reset()
return d
}

// Sum256Personalized returns the BLAKE2b-256 checksum of data with the given
// 16-byte personalization string.
func Sum256Personalized(personalization [16]byte, data []byte) [Size256]byte {
h := iv
h[0] ^= uint64(Size256) | (1 << 16) | (1 << 24)
h[6] ^= binary.LittleEndian.Uint64(personalization[:8])
h[7] ^= binary.LittleEndian.Uint64(personalization[8:16])

var c [2]uint64

if length := len(data); length > BlockSize {
n := length &^ (BlockSize - 1)
if length == n {
n -= BlockSize
}
hashBlocksGeneric(&h, &c, 0, data[:n])
data = data[n:]
}

var block [BlockSize]byte
offset := copy(block[:], data)
remaining := uint64(BlockSize - offset)
if c[0] < remaining {
c[1]--
}
c[0] -= remaining

hashBlocksGeneric(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:])

var sum [Size256]byte
for i := 0; i < Size256/8; i++ {
binary.LittleEndian.PutUint64(sum[8*i:], h[i])
}
return sum
}

func (d *digest) BlockSize() int { return BlockSize }
func (d *digest) Size() int { return d.sz }

func (d *digest) Reset() {
d.h = iv
d.h[0] ^= uint64(d.sz) | (uint64(d.keyLen) << 8) | (1 << 16) | (1 << 24)
d.h[6] ^= binary.LittleEndian.Uint64(d.personalization[:8])
d.h[7] ^= binary.LittleEndian.Uint64(d.personalization[8:16])
d.offset, d.c[0], d.c[1] = 0, 0, 0
if d.keyLen > 0 {
d.block = d.key
d.offset = BlockSize
}
}

func (d *digest) Write(p []byte) (n int, err error) {
n = len(p)

if d.offset > 0 {
remaining := BlockSize - d.offset
if n <= remaining {
d.offset += copy(d.block[d.offset:], p)
return
}
copy(d.block[d.offset:], p[:remaining])
hashBlocksGeneric(&d.h, &d.c, 0, d.block[:])
d.offset = 0
p = p[remaining:]
}

if length := len(p); length > BlockSize {
nn := length &^ (BlockSize - 1)
if length == nn {
nn -= BlockSize
}
hashBlocksGeneric(&d.h, &d.c, 0, p[:nn])
p = p[nn:]
}

if len(p) > 0 {
d.offset += copy(d.block[:], p)
}

return
}

func (d *digest) Sum(sum []byte) []byte {
var hash [size]byte
d.finalize(&hash)
return append(sum, hash[:d.sz]...)
}

func (d *digest) finalize(hash *[size]byte) {
var block [BlockSize]byte
copy(block[:], d.block[:d.offset])
remaining := uint64(BlockSize - d.offset)

c := d.c
if c[0] < remaining {
c[1]--
}
c[0] -= remaining

h := d.h
hashBlocksGeneric(&h, &c, 0xFFFFFFFFFFFFFFFF, block[:])

for i, v := range h {
binary.LittleEndian.PutUint64(hash[8*i:], v)
}
}
88 changes: 88 additions & 0 deletions parser/internal/blake2b/blake2b_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2025 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .

package blake2b

import (
"encoding/hex"
"testing"
)

func TestSum256NoPersonalization(t *testing.T) {
// BLAKE2b-256 of empty string (no personalization = all zeros).
// Verified with Python: hashlib.blake2b(b"", digest_size=32).hexdigest()
got := Sum256Personalized([16]byte{}, []byte{})
want := "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8"
if hex.EncodeToString(got[:]) != want {
t.Fatalf("empty hash mismatch:\n got %s\n want %s", hex.EncodeToString(got[:]), want)
}
}

func TestSum256Abc(t *testing.T) {
// BLAKE2b-256 of "abc".
// Verified with Python: hashlib.blake2b(b"abc", digest_size=32).hexdigest()
got := Sum256Personalized([16]byte{}, []byte("abc"))
want := "bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319"
if hex.EncodeToString(got[:]) != want {
t.Fatalf("abc hash mismatch:\n got %s\n want %s", hex.EncodeToString(got[:]), want)
}
}

func TestNew256PersonalizedStreaming(t *testing.T) {
// Verify streaming mode matches one-shot for "abc".
h := New256Personalized([16]byte{})
h.Write([]byte("a"))
h.Write([]byte("bc"))
got := h.Sum(nil)
want := "bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319"
if hex.EncodeToString(got) != want {
t.Fatalf("streaming abc hash mismatch:\n got %s\n want %s", hex.EncodeToString(got), want)
}
}

func TestPersonalizedHash(t *testing.T) {
// Verified with Python:
// hashlib.blake2b(b"", digest_size=32,
// person=b"ZcashTxHash_\xbb\x09\xb8\x76").hexdigest()
person := [16]byte{
'Z', 'c', 'a', 's', 'h', 'T', 'x', 'H',
'a', 's', 'h', '_', 0xbb, 0x09, 0xb8, 0x76,
}
got := Sum256Personalized(person, []byte{})
want := "da5ea35a7ceb9507dbdd7a1dd0c1c2bf5d61f12781704e5613c8c8d3226f6e26"
if hex.EncodeToString(got[:]) != want {
t.Fatalf("personalized empty hash mismatch:\n got %s\n want %s", hex.EncodeToString(got[:]), want)
}
}

func TestPersonalizedHashWithData(t *testing.T) {
// Verified with Python:
// hashlib.blake2b(b"Zcash", digest_size=32,
// person=b"ZTxIdHeadersHash").hexdigest()
person := [16]byte{
'Z', 'T', 'x', 'I', 'd', 'H', 'e', 'a',
'd', 'e', 'r', 's', 'H', 'a', 's', 'h',
}
got := Sum256Personalized(person, []byte("Zcash"))
want := "1a9162a394083a3a8020bff265625864f9a4cb7f8a28038822f78c6a17bc4f45"
if hex.EncodeToString(got[:]) != want {
t.Fatalf("personalized data hash mismatch:\n got %s\n want %s", hex.EncodeToString(got[:]), want)
}
}

func TestReset(t *testing.T) {
person := [16]byte{
'Z', 'T', 'x', 'I', 'd', 'H', 'e', 'a',
'd', 'e', 'r', 's', 'H', 'a', 's', 'h',
}
h := New256Personalized(person)
h.Write([]byte("garbage"))
h.Reset()
h.Write([]byte("Zcash"))
got := h.Sum(nil)
want := "1a9162a394083a3a8020bff265625864f9a4cb7f8a28038822f78c6a17bc4f45"
if hex.EncodeToString(got) != want {
t.Fatalf("reset hash mismatch:\n got %s\n want %s", hex.EncodeToString(got), want)
}
}
Loading