From 7939dd7f3b7cf6b784b6fec1196c88ba54e5e195 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 16 Oct 2024 15:40:06 +0200 Subject: [PATCH] experimental no-alloc noescape merkle funcs --- rvgo/fast/keccakfast.go | 43 ++++++++++++++++++++++++++++++++++++++++ rvgo/fast/memory.go | 11 ++-------- rvgo/fast/memory_test.go | 22 ++++++++++---------- rvgo/fast/page.go | 11 +++------- rvgo/fast/page_test.go | 4 ++-- rvgo/fast/radix.go | 13 ++++++------ 6 files changed, 67 insertions(+), 37 deletions(-) create mode 100644 rvgo/fast/keccakfast.go diff --git a/rvgo/fast/keccakfast.go b/rvgo/fast/keccakfast.go new file mode 100644 index 00000000..86079607 --- /dev/null +++ b/rvgo/fast/keccakfast.go @@ -0,0 +1,43 @@ +package fast + +import ( + "reflect" + _ "unsafe" // we use go:linkname + + "golang.org/x/crypto/sha3" +) + +type keccakState struct { + a [25]uint64 // main state of the hash + // and other fields, unimportant +} + +//go:noescape +//go:linkname keccakReset golang.org/x/crypto/sha3.(*state).Reset +func keccakReset(st *keccakState) + +//go:noescape +//go:linkname keccakWrite golang.org/x/crypto/sha3.(*state).Write +func keccakWrite(st *keccakState, p []byte) (n int, err error) + +//go:noescape +//go:linkname keccakRead golang.org/x/crypto/sha3.(*state).Read +func keccakRead(st *keccakState, out []byte) (n int, err error) + +// example of how to get access to a hasher where the call arguments do not escape to the heap +var hasher = (*keccakState)(reflect.ValueOf(sha3.NewLegacyKeccak256()).UnsafePointer()) + +func hashPair(left, right [32]byte) (out [32]byte) { + keccakReset(hasher) + _, _ = keccakWrite(hasher, left[:]) + _, _ = keccakWrite(hasher, right[:]) + _, _ = keccakRead(hasher, out[:]) + return +} + +func hash(data [64]byte) (out [32]byte) { + keccakReset(hasher) + _, _ = keccakWrite(hasher, data[:]) + _, _ = keccakRead(hasher, out[:]) + return +} diff --git a/rvgo/fast/memory.go b/rvgo/fast/memory.go index 3e41ae1c..00c487d4 100644 --- a/rvgo/fast/memory.go +++ b/rvgo/fast/memory.go @@ -6,8 +6,7 @@ import ( "fmt" "io" "sort" - - "github.com/ethereum/go-ethereum/crypto" + _ "unsafe" ) // Note: 2**12 = 4 KiB, the minimum page-size in Unicorn for mmap @@ -22,17 +21,11 @@ const ( ProofLen = 64 - 4 ) -func HashPair(left, right [32]byte) [32]byte { - out := crypto.Keccak256Hash(left[:], right[:]) - //fmt.Printf("0x%x 0x%x -> 0x%x\n", left, right, out) - return out -} - var zeroHashes = func() [256][32]byte { // empty parts of the tree are all zero. Precompute the hash of each full-zero range sub-tree level. var out [256][32]byte for i := 1; i < 256; i++ { - out[i] = HashPair(out[i-1], out[i-1]) + out[i] = hashPair(out[i-1], out[i-1]) } return out }() diff --git a/rvgo/fast/memory_test.go b/rvgo/fast/memory_test.go index 8653a6fc..1a245a55 100644 --- a/rvgo/fast/memory_test.go +++ b/rvgo/fast/memory_test.go @@ -36,9 +36,9 @@ func TestMemoryMerkleProof(t *testing.T) { for i := 32; i < len(proof); i += 32 { sib := *(*[32]byte)(proof[i : i+32]) if path&1 != 0 { - node = HashPair(sib, node) + node = hashPair(sib, node) } else { - node = HashPair(node, sib) + node = hashPair(node, sib) } path >>= 1 } @@ -253,9 +253,9 @@ func verifyProof(t *testing.T, expectedRoot [32]byte, proof [ProofLen * 32]byte, for i := 32; i < len(proof); i += 32 { sib := *(*[32]byte)(proof[i : i+32]) if path&1 != 0 { - node = HashPair(sib, node) + node = hashPair(sib, node) } else { - node = HashPair(node, sib) + node = hashPair(node, sib) } path >>= 1 } @@ -310,14 +310,14 @@ func TestMemoryMerkleRoot(t *testing.T) { p6 := m.radix.MerkleizeNode(0, 14) p7 := m.radix.MerkleizeNode(0, 15) - r1 := HashPair( - HashPair( - HashPair(p0, p1), // 0,1 - HashPair(p2, p3), // 2,3 + r1 := hashPair( + hashPair( + hashPair(p0, p1), // 0,1 + hashPair(p2, p3), // 2,3 ), - HashPair( - HashPair(p4, p5), // 4,5 - HashPair(p6, p7), // 6,7 + hashPair( + hashPair(p4, p5), // 4,5 + hashPair(p6, p7), // 6,7 ), ) r2 := m.MerkleRoot() diff --git a/rvgo/fast/page.go b/rvgo/fast/page.go index 24933b40..9b143e99 100644 --- a/rvgo/fast/page.go +++ b/rvgo/fast/page.go @@ -3,8 +3,6 @@ package fast import ( "encoding/hex" "fmt" - - "github.com/ethereum/go-ethereum/crypto" ) type Page [PageSize]byte @@ -55,7 +53,7 @@ func (p *CachedPage) MerkleRoot() [32]byte { if p.Ok[j] { continue } - p.Cache[j] = crypto.Keccak256Hash(p.Data[i : i+64]) + p.Cache[j] = hash([64]byte(p.Data[i : i+64])) //fmt.Printf("0x%x 0x%x -> 0x%x\n", p.Data[i:i+32], p.Data[i+32:i+64], p.Cache[j]) p.Ok[j] = true } @@ -66,7 +64,7 @@ func (p *CachedPage) MerkleRoot() [32]byte { if p.Ok[j] { continue } - p.Cache[j] = HashPair(p.Cache[i], p.Cache[i+1]) + p.Cache[j] = hashPair(p.Cache[i], p.Cache[i+1]) p.Ok[j] = true } @@ -100,11 +98,10 @@ func (p *CachedPage) MerkleizeNode(addr, gindex uint64) [32]byte { return p.Cache[gindex] } -func (p *CachedPage) GenerateProof(addr uint64) [][32]byte { +func (p *CachedPage) GenerateProof(addr uint64, proofs *[8][32]byte) { // Page-level proof pageGindex := PageSize>>5 + (addr&PageAddrMask)>>5 - proofs := make([][32]byte, 8) proofIndex := 0 proofs[proofIndex] = p.MerkleizeSubtree(pageGindex) @@ -114,6 +111,4 @@ func (p *CachedPage) GenerateProof(addr uint64) [][32]byte { proofIndex++ proofs[proofIndex] = p.MerkleizeSubtree(uint64(sibling)) } - - return proofs } diff --git a/rvgo/fast/page_test.go b/rvgo/fast/page_test.go index 71462876..3ac6e666 100644 --- a/rvgo/fast/page_test.go +++ b/rvgo/fast/page_test.go @@ -17,11 +17,11 @@ func TestCachedPage(t *testing.T) { require.Equal(t, expectedLeaf, node, "leaf nodes should not be hashed") node = p.MerkleizeSubtree(gindex >> 1) - expectedParent := common.Hash(HashPair(zeroHashes[0], expectedLeaf)) + expectedParent := common.Hash(hashPair(zeroHashes[0], expectedLeaf)) require.Equal(t, expectedParent, node, "can get the parent node") node = p.MerkleizeSubtree(gindex >> 2) - expectedParentParent := common.Hash(HashPair(expectedParent, zeroHashes[1])) + expectedParentParent := common.Hash(hashPair(expectedParent, zeroHashes[1])) require.Equal(t, expectedParentParent, node, "and the parent of the parent") pre := p.MerkleRoot() diff --git a/rvgo/fast/radix.go b/rvgo/fast/radix.go index 586cc6b6..0aea1ce2 100644 --- a/rvgo/fast/radix.go +++ b/rvgo/fast/radix.go @@ -160,8 +160,7 @@ func (m *Memory) GenerateProof(addr uint64, proofs [][32]byte) { // number of proof for a page is 8 // 0: leaf page data, 7: page's root if p, ok := m.pages[pageIndex]; ok { - pageProofs := p.GenerateProof(addr) // Generate proof from the page. - copy(proofs[:8], pageProofs) + p.GenerateProof(addr, (*[8][32]byte)(proofs[:8])) // Generate proof from the page. } else { fillZeroHashRange(proofs, 0, 8) // Return zero hashes if the page does not exist. } @@ -203,8 +202,8 @@ func (n *SmallRadixNode[C]) MerkleizeNode(addr, gindex uint64) [32]byte { left := n.MerkleizeNode(addr, gindex<<1) right := n.MerkleizeNode(addr, (gindex<<1)|1) - // Hash the pair and cache the result. - r := HashPair(left, right) + // hash the pair and cache the result. + r := hashPair(left, right) n.Hashes[gindex] = r n.HashValid |= 1 << hashBit return r @@ -249,8 +248,8 @@ func (n *MediumRadixNode[C]) MerkleizeNode(addr, gindex uint64) [32]byte { left := n.MerkleizeNode(addr, gindex<<1) right := n.MerkleizeNode(addr, (gindex<<1)|1) - // Hash the pair and cache the result. - r := HashPair(left, right) + // hash the pair and cache the result. + r := hashPair(left, right) n.Hashes[gindex] = r n.HashValid |= 1 << hashBit return r @@ -290,7 +289,7 @@ func (n *LargeRadixNode[C]) MerkleizeNode(addr, gindex uint64) [32]byte { left := n.MerkleizeNode(addr, gindex<<1) right := n.MerkleizeNode(addr, (gindex<<1)|1) - r := HashPair(left, right) + r := hashPair(left, right) n.Hashes[gindex] = r n.HashValid[hashIndex] |= 1 << hashBit return r