trie/bintrie: fix overflow management in slot key computation (#33951)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Keeper Build (push) Waiting to run
/ Docker Image (push) Waiting to run
/ Windows Build (push) Waiting to run

The computation of `MAIN_STORAGE_OFFSET` was incorrect, causing the last
byte of the stem to be dropped. This means that there would be a
collision in the hash computation (at the preimage level, not a hash
collision of course) if two keys were only differing at byte 31.
This commit is contained in:
Guillaume Ballet 2026-03-05 14:43:31 +01:00 committed by GitHub
parent 344ce84a43
commit a0fb8102fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 17 deletions

View file

@ -308,7 +308,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
}, },
} }
expected := common.FromHex("b94812c1674dcf4f2bc98f4503d15f4cc674265135bcf3be6e4417b60881042a") expected := common.FromHex("1fd154971d9a386c4ec75fe7138c17efb569bfc2962e46e94a376ba997e3fadc")
got := genesis.ToBlock().Root().Bytes() got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) { if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)

View file

@ -47,13 +47,26 @@ var (
) )
func GetBinaryTreeKey(addr common.Address, key []byte) []byte { func GetBinaryTreeKey(addr common.Address, key []byte) []byte {
return getBinaryTreeKey(addr, key, false)
}
func getBinaryTreeKey(addr common.Address, offset []byte, overflow bool) []byte {
hasher := sha256.New() hasher := sha256.New()
hasher.Write(zeroHash[:12]) hasher.Write(zeroHash[:12])
hasher.Write(addr[:]) hasher.Write(addr[:])
hasher.Write(key[:31]) var buf [32]byte
hasher.Write([]byte{0}) // key is big endian, hashed value is little endian
for i := range offset[:31] {
buf[i] = offset[30-i]
}
if overflow {
// Overflow detected when adding MAIN_STORAGE_OFFSET,
// reporting it in the shifter 32 byte value.
buf[31] = 1
}
hasher.Write(buf[:])
k := hasher.Sum(nil) k := hasher.Sum(nil)
k[31] = key[31] k[31] = offset[31]
return k return k
} }
@ -69,24 +82,29 @@ func GetBinaryTreeKeyCodeHash(addr common.Address) []byte {
return GetBinaryTreeKey(addr, k[:]) return GetBinaryTreeKey(addr, k[:])
} }
func GetBinaryTreeKeyStorageSlot(address common.Address, key []byte) []byte { func GetBinaryTreeKeyStorageSlot(address common.Address, slotnum []byte) []byte {
var k [32]byte var offset [32]byte
// Case when the key belongs to the account header // Case when the key belongs to the account header
if bytes.Equal(key[:31], zeroHash[:31]) && key[31] < 64 { if bytes.Equal(slotnum[:31], zeroHash[:31]) && slotnum[31] < 64 {
k[31] = 64 + key[31] offset[31] = 64 + slotnum[31]
return GetBinaryTreeKey(address, k[:]) return GetBinaryTreeKey(address, offset[:])
} }
// Set the main storage offset // Set the main storage offset offset = MAIN_STORAGE_OFFSET + slotnum
// note that the first 64 bytes of the main offset storage // * Note that MAIN_STORAGE_OFFSET is 1 << 248, so the number
// are unreachable, which is consistent with the spec and // can overflow into a 33rd byte, but since the value is
// what verkle does. // shifted by one byte in getBinaryTreeKey, this only takes
k[0] = 1 // 1 << 248 // note of the overflow, and the value will be added after
copy(k[1:], key[:31]) // the shift, in order to avoid allocating an extra byte.
k[31] = key[31] // * Note that the first 64 bytes of the main offset storage
// are unreachable, which is consistent with the spec.
// * Note that `slotnum` is big-endian
overflow := slotnum[0] == 255
copy(offset[:], slotnum)
offset[0] += 1 // 1 << 248, handle overflow out of band
return GetBinaryTreeKey(address, k[:]) return getBinaryTreeKey(address, offset[:], overflow)
} }
func GetBinaryTreeKeyCodeChunk(address common.Address, chunknr *uint256.Int) []byte { func GetBinaryTreeKeyCodeChunk(address common.Address, chunknr *uint256.Int) []byte {