From b48cbf8904e2ff716f32a0366b44e66839369ed4 Mon Sep 17 00:00:00 2001 From: JackCC Date: Tue, 14 Jul 2026 11:12:37 +0800 Subject: [PATCH 1/2] cl/merkle_tree: add EIP-7916 progressive merkleization --- cl/merkle_tree/progressive.go | 52 +++++++++++++++++++++ cl/merkle_tree/progressive_test.go | 73 ++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 cl/merkle_tree/progressive.go create mode 100644 cl/merkle_tree/progressive_test.go diff --git a/cl/merkle_tree/progressive.go b/cl/merkle_tree/progressive.go new file mode 100644 index 00000000000..23926c33005 --- /dev/null +++ b/cl/merkle_tree/progressive.go @@ -0,0 +1,52 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Erigon is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Erigon. If not, see . + +package merkle_tree + +import "github.com/erigontech/erigon/cl/utils" + +// MerkleizeProgressive computes the progressive Merkle tree root specified by +// EIP-7916. Successive right-hand subtrees have capacities 1, 4, 16, 64, ... +// chunks, and the sequence is terminated by a zero chunk. +func MerkleizeProgressive(chunks [][32]byte) ([32]byte, error) { + return merkleizeProgressive(chunks, 1) +} + +func merkleizeProgressive(chunks [][32]byte, numLeaves uint64) ([32]byte, error) { + if len(chunks) == 0 { + return [32]byte{}, nil + } + + subtreeLen := len(chunks) + if uint64(subtreeLen) > numLeaves { + subtreeLen = int(numLeaves) + } + + // MerkleizeVector hashes in place, so copy the current subtree to preserve + // the caller's chunks and the unconsumed suffix. + subtree := append([][32]byte(nil), chunks[:subtreeLen]...) + left, err := MerkleizeVector(subtree, numLeaves) + if err != nil { + return [32]byte{}, err + } + + right, err := merkleizeProgressive(chunks[subtreeLen:], numLeaves*4) + if err != nil { + return [32]byte{}, err + } + + return utils.Sha256(left[:], right[:]), nil +} diff --git a/cl/merkle_tree/progressive_test.go b/cl/merkle_tree/progressive_test.go new file mode 100644 index 00000000000..f7b0e1fd21f --- /dev/null +++ b/cl/merkle_tree/progressive_test.go @@ -0,0 +1,73 @@ +// Copyright 2026 The Erigon Authors +// This file is part of Erigon. +// +// Erigon is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Erigon is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Erigon. If not, see . + +package merkle_tree_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/erigontech/erigon/cl/merkle_tree" + "github.com/erigontech/erigon/common" +) + +func TestMerkleizeProgressiveReferenceVectors(t *testing.T) { + // EIP-7916 is pinned here to ethereum/EIPs revision + // 88da569e65d32ad6efad017f1e40d107f1de8394 (merkleize to the right). + // The expected roots were generated with ethereum/remerkleable's + // subtree_fill_progressive at 97d970e107214b59d146dffa7d837e7144b457e6 + // (v0.1.31). These cases cover each transition among the 1, 4, 16, 64, + // and 256-leaf subtrees. + tests := []struct { + name string + chunkCount int + expected string + }{ + {name: "empty", chunkCount: 0, expected: "0x0000000000000000000000000000000000000000000000000000000000000000"}, + {name: "end first subtree", chunkCount: 1, expected: "0x037d6dfb3a369a41e01100fdd53c35ee3fb69ddec5830d61e1138d066a4c2285"}, + {name: "start four-leaf subtree", chunkCount: 2, expected: "0x2dfe47da19ad9ff11afe44dd8de4db8517cefd5a9bddffe6652b26a1b91ea5ac"}, + {name: "end four-leaf subtree", chunkCount: 5, expected: "0x3fd53b812118ddea60b9deab5c72d32b0c4dcfd2c94deda753e6e1d548fbc274"}, + {name: "start sixteen-leaf subtree", chunkCount: 6, expected: "0x2e2a2abd4d0e28498ec0cdd817c715b246aa15e7b34767061b7632337188429e"}, + {name: "end sixteen-leaf subtree", chunkCount: 21, expected: "0xf148f679afbfebfe5616080a45461aee3d1f4ce2cc752ce824c3f067d2707623"}, + {name: "start sixty-four-leaf subtree", chunkCount: 22, expected: "0x040be60071c540aafc1d44f366239ab6a41bf8740a38f9d52ab0bbd9cd974c45"}, + {name: "end sixty-four-leaf subtree", chunkCount: 85, expected: "0x24ea21562226364be74fd2696d0824a4347cfac7dd4b2ae28cd0e9cc22bc341d"}, + {name: "start two-hundred-fifty-six-leaf subtree", chunkCount: 86, expected: "0xb73c4c427974f47c74c2812d353c966f5dadae70c44f6fe9a15e179b86914977"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + chunks := progressiveTestChunks(test.chunkCount) + original := make([][32]byte, len(chunks)) + copy(original, chunks) + + root, err := merkle_tree.MerkleizeProgressive(chunks) + require.NoError(t, err) + require.Equal(t, [32]byte(common.HexToHash(test.expected)), root) + require.Equal(t, original, chunks, "input chunks must not be modified") + }) + } +} + +func progressiveTestChunks(count int) [][32]byte { + chunks := make([][32]byte, count) + for i := range chunks { + for j := range chunks[i] { + chunks[i][j] = byte(i + 1) + } + } + return chunks +} From e0e58878b39301044fb06201607aef8ccfcb3453 Mon Sep 17 00:00:00 2001 From: JackCC Date: Tue, 14 Jul 2026 16:58:03 +0800 Subject: [PATCH 2/2] cl/merkle_tree: guard progressive capacity overflow --- cl/merkle_tree/progressive.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cl/merkle_tree/progressive.go b/cl/merkle_tree/progressive.go index 23926c33005..4cf5a696fa4 100644 --- a/cl/merkle_tree/progressive.go +++ b/cl/merkle_tree/progressive.go @@ -16,7 +16,12 @@ package merkle_tree -import "github.com/erigontech/erigon/cl/utils" +import ( + "errors" + "math" + + "github.com/erigontech/erigon/cl/utils" +) // MerkleizeProgressive computes the progressive Merkle tree root specified by // EIP-7916. Successive right-hand subtrees have capacities 1, 4, 16, 64, ... @@ -43,6 +48,10 @@ func merkleizeProgressive(chunks [][32]byte, numLeaves uint64) ([32]byte, error) return [32]byte{}, err } + if numLeaves > math.MaxUint64/4 { + return [32]byte{}, errors.New("progressive tree capacity overflow") + } + right, err := merkleizeProgressive(chunks[subtreeLen:], numLeaves*4) if err != nil { return [32]byte{}, err