Repository: ferranbt/fastssz
Pull request #147 (2024-03-19 16:22:48)

fix(hasher): Fix chunk count in `merkleizeImpl`
Per ssz spec, when packing values, the values is right-padded to a multiple of 32 bytes and the number of chunks is calculated after the values are packed. The current implementation does not handle the case when the length of values is not a multiple of 32 bytes.


Patch:
diff --git a/hasher.go b/hasher.go
index 33d1bc3..ec3975d 100755
--- a/hasher.go
+++ b/hasher.go
@@ -341,7 +341,10 @@ func getDepth(d uint64) uint8 {
 }
 
 func (h *Hasher) merkleizeImpl(dst []byte, input []byte, limit uint64) []byte {
-	count := uint64(len(input) / 32)
+	// count is the number of 32 byte chunks from the input, after right-padding
+	// with zeroes to the next multiple of 32 bytes when the input is not aligned
+	// to a multiple of 32 bytes.
+	count := uint64((len(input) + 31) / 32)
 	if limit == 0 {
 		limit = count
 	} else if count > limit {


Test patch:
diff --git a/sszgen/testcases/container.go b/sszgen/testcases/container.go
new file mode 100644
index 0000000..4285c00
--- /dev/null
+++ b/sszgen/testcases/container.go
@@ -0,0 +1,7 @@
+package testcases
+
+//go:generate go run ../main.go --path container.go
+
+type Vec struct {
+	Values []uint64 `ssz-size:"6"`
+}
diff --git a/sszgen/testcases/container_encoding.go b/sszgen/testcases/container_encoding.go
new file mode 100644
index 0000000..166bb37
--- /dev/null
+++ b/sszgen/testcases/container_encoding.go
@@ -0,0 +1,83 @@
+// Code generated by fastssz. DO NOT EDIT.
+// Hash: acb511949371bd93802219589c0d97145e86ad6f3573e53f612cf206ac868df4
+// Version: 0.1.3
+package testcases
+
+import (
+	ssz "github.com/ferranbt/fastssz"
+)
+
+// MarshalSSZ ssz marshals the Vec object
+func (v *Vec) MarshalSSZ() ([]byte, error) {
+	return ssz.MarshalSSZ(v)
+}
+
+// MarshalSSZTo ssz marshals the Vec object to a target array
+func (v *Vec) MarshalSSZTo(buf []byte) (dst []byte, err error) {
+	dst = buf
+
+	// Field (0) 'Values'
+	if size := len(v.Values); size != 6 {
+		err = ssz.ErrVectorLengthFn("Vec.Values", size, 6)
+		return
+	}
+	for ii := 0; ii < 6; ii++ {
+		dst = ssz.MarshalUint64(dst, v.Values[ii])
+	}
+
+	return
+}
+
+// UnmarshalSSZ ssz unmarshals the Vec object
+func (v *Vec) UnmarshalSSZ(buf []byte) error {
+	var err error
+	size := uint64(len(buf))
+	if size != 48 {
+		return ssz.ErrSize
+	}
+
+	// Field (0) 'Values'
+	v.Values = ssz.ExtendUint64(v.Values, 6)
+	for ii := 0; ii < 6; ii++ {
+		v.Values[ii] = ssz.UnmarshallUint64(buf[0:48][ii*8 : (ii+1)*8])
+	}
+
+	return err
+}
+
+// SizeSSZ returns the ssz encoded size in bytes for the Vec object
+func (v *Vec) SizeSSZ() (size int) {
+	size = 48
+	return
+}
+
+// HashTreeRoot ssz hashes the Vec object
+func (v *Vec) HashTreeRoot() ([32]byte, error) {
+	return ssz.HashWithDefaultHasher(v)
+}
+
+// HashTreeRootWith ssz hashes the Vec object with a hasher
+func (v *Vec) HashTreeRootWith(hh ssz.HashWalker) (err error) {
+	indx := hh.Index()
+
+	// Field (0) 'Values'
+	{
+		if size := len(v.Values); size != 6 {
+			err = ssz.ErrVectorLengthFn("Vec.Values", size, 6)
+			return
+		}
+		subIndx := hh.Index()
+		for _, i := range v.Values {
+			hh.AppendUint64(i)
+		}
+		hh.Merkleize(subIndx)
+	}
+
+	hh.Merkleize(indx)
+	return
+}
+
+// GetTree ssz hashes the Vec object
+func (v *Vec) GetTree() (*ssz.Node, error) {
+	return ssz.ProofTree(v)
+}
diff --git a/sszgen/testcases/container_test.go b/sszgen/testcases/container_test.go
new file mode 100644
index 0000000..7201d87
--- /dev/null
+++ b/sszgen/testcases/container_test.go
@@ -0,0 +1,25 @@
+package testcases
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestVectorContainer(t *testing.T) {
+	v := &Vec{
+		Values: []uint64{1, 2, 3, 4, 5, 6},
+	}
+	expectedHash := [32]byte{
+		0xac, 0x13, 0x6e, 0xdd, 0xa3, 0xbd, 0xd2, 0xe9,
+		0x49, 0xa1, 0x9a, 0x94, 0x5b, 0x1a, 0xc5, 0x54,
+		0xe4, 0xb6, 0x07, 0xd3, 0x39, 0xa4, 0x3c, 0x54,
+		0x0c, 0x33, 0x60, 0x98, 0xff, 0xf9, 0x7f, 0x2b,
+	}
+	h, err := v.HashTreeRoot()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !bytes.Equal(h[:], expectedHash[:]) {
+		t.Fatalf("hash mismatch")
+	}
+}