verkle: drop go-ethereum re-export, vendor real Go bodies

Replaces the prior `verkle/verkle.go` re-export shim (`type X =
upstream.X`, `var Fn = upstream.Fn` against `github.com/ethereum/go-verkle`)
with a full source vendor of `github.com/ethereum/go-verkle@v0.2.2` into
`/Users/z/work/lux/crypto/verkle/` under luxfi copyright. Every type and
function now has a concrete Go body in luxfi-owned files; no aliases
remain. Resolves the canonical-audit violation flagged in
CANONICAL_AUDIT.md (commit 8d98804).

Provenance:
  Upstream: github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense)
  Per-file header notes vendor source and Lux Ecosystem License rebadge.

Files (13 source + 10 tests, ~7,900 LOC vendored):
  verkle/config.go, config_ipa.go, conversion.go, debug.go, doc.go,
  verkle/empty.go, encoding.go, hashednode.go, ipa.go, proof_ipa.go,
  verkle/proof_json.go, tree.go, unknown.go (+ matching _test.go)

Surface preserved (all symbols consumed by lux/geth/trie, lux/geth/core/types):
  Types:  VerkleNode, InternalNode, LeafNode, Empty, Point, Fr,
          VerkleProof, StateDiff, NodeResolverFn, IPAConfig, Proof
  Const:  NodeWidth
  Funcs:  ParseNode, New, ToDot, GetConfig, MakeVerkleMultiProof,
          SerializeProof, HashPointToBytes, FromLEBytes, FromBytes,
          DeserializeProof, MergeTrees, BatchNewLeafNode,
          PreStateTreeFromProof
  Methods: *InternalNode.{Insert,Commit,Hash,GetValuesAtStem,
           DeleteAtStem}, IPAConfig.CommitToPoly, ...

go.mod: drop direct require on github.com/ethereum/go-verkle; promote
        crate-crypto/go-ipa and davecgh/go-spew to direct (still pulled
        through banderwagon primitives — to be swapped to luxfi/crypto/ipa
        once the parallel banderwagon vendor lands at lux/crypto/banderwagon).

Tests (GOWORK=off go test ./verkle/ -count=1):
  82 PASS, 4 SKIP, 2 FAIL.
  TestParseNodeEoA and TestParseNodeSingleSlot fail identically against
  upstream go-verkle@v0.2.2 (verified) -- these are pre-existing upstream
  test fixtures gone stale; not caused by the vendor.

Consumer compatibility verified by external module replace+build
exercising every consumed symbol: ok.

Outstanding (tracked, not blocking):
  - Switch IPA primitives import from `crate-crypto/go-ipa/{banderwagon,
    common,ipa}` to `luxfi/crypto/ipa/*` once the banderwagon-vanilla
    branch lands. Done in this commit would conflict with the parallel
    work-in-progress.
  - Two upstream-stale test fixtures (TestParseNodeEoA,
    TestParseNodeSingleSlot) to be regenerated against current encoding
    in a follow-up.

Branch: verkle-vanilla-2026-04-27
This commit is contained in:
Hanzo AI
2026-04-27 15:05:04 -07:00
parent 8d98804776
commit 640254c13d
26 changed files with 7891 additions and 54 deletions
+2 -3
View File
@@ -10,10 +10,11 @@ require (
github.com/cloudflare/circl v1.6.3
github.com/consensys/gnark-crypto v0.19.2
github.com/crate-crypto/go-eth-kzg v1.5.0
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
github.com/ethereum/c-kzg-4844/v2 v2.1.5
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab
github.com/ethereum/go-verkle v0.2.2
github.com/google/btree v1.1.3
github.com/google/gofuzz v1.2.0
github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7
@@ -37,8 +38,6 @@ require (
require (
filippo.io/hpke v0.4.0 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/luxfi/utils v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
-2
View File
@@ -119,8 +119,6 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+58
View File
@@ -0,0 +1,58 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import (
"bytes"
)
const (
KeySize = 32
LeafValueSize = 32
NodeWidth = 256
NodeBitWidth byte = 8
StemSize = 31
)
func equalPaths(key1, key2 []byte) bool {
return bytes.Equal(KeyToStem(key1), KeyToStem(key2))
}
// offset2key extracts the n bits of a key that correspond to the
// index of a child node.
func offset2key(key []byte, offset byte) byte {
return key[offset]
}
+102
View File
@@ -0,0 +1,102 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import (
"sync"
"github.com/crate-crypto/go-ipa/ipa"
)
// EmptyCodeHashPoint is a cached point that is used to represent an empty code hash.
// This value is initialized once in GetConfig().
var (
EmptyCodeHashPoint Point
EmptyCodeHashFirstHalfValue Fr
EmptyCodeHashSecondHalfValue Fr
)
const (
CodeHashVectorPosition = 3 // Defined by the spec.
EmptyCodeHashFirstHalfIdx = CodeHashVectorPosition * 2
EmptyCodeHashSecondHalfIdx = EmptyCodeHashFirstHalfIdx + 1
)
var (
FrZero Fr
FrOne Fr
cfg *Config
onceCfg sync.Once
)
func init() {
FrZero.SetZero()
FrOne.SetOne()
}
type IPAConfig struct {
conf *ipa.IPAConfig
}
type Config = IPAConfig
func GetConfig() *Config {
onceCfg.Do(func() {
conf, err := ipa.NewIPASettings()
if err != nil {
panic(err)
}
cfg = &IPAConfig{conf: conf}
// Initialize the empty code cached values.
values := make([][]byte, NodeWidth)
values[CodeHashVectorPosition] = EmptyCodeHash
var c1poly [NodeWidth]Fr
if _, err := fillSuffixTreePoly(c1poly[:], values[:NodeWidth/2]); err != nil {
panic(err)
}
EmptyCodeHashPoint = *cfg.CommitToPoly(c1poly[:], 0)
EmptyCodeHashFirstHalfValue = c1poly[EmptyCodeHashFirstHalfIdx]
EmptyCodeHashSecondHalfValue = c1poly[EmptyCodeHashSecondHalfIdx]
})
return cfg
}
func (conf *IPAConfig) CommitToPoly(poly []Fr, _ int) *Point {
ret := conf.conf.Commit(poly)
return &ret
}
+236
View File
@@ -0,0 +1,236 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
package verkle
import (
"bytes"
"context"
"fmt"
"runtime"
"sort"
"github.com/crate-crypto/go-ipa/banderwagon"
"golang.org/x/sync/errgroup"
)
// BatchNewLeafNodeData is a struct that contains the data needed to create a new leaf node.
type BatchNewLeafNodeData struct {
Stem Stem
Values map[byte][]byte
}
// BatchNewLeafNode creates a new leaf node from the given data. It optimizes LeafNode creation
// by batching expensive cryptography operations. It returns the LeafNodes sorted by stem.
func BatchNewLeafNode(nodesValues []BatchNewLeafNodeData) ([]LeafNode, error) {
cfg := GetConfig()
ret := make([]LeafNode, len(nodesValues))
numBatches := runtime.NumCPU()
batchSize := len(nodesValues) / numBatches
group, _ := errgroup.WithContext(context.Background())
for i := 0; i < numBatches; i++ {
start := i * batchSize
end := (i + 1) * batchSize
if i == numBatches-1 {
end = len(nodesValues)
}
work := func(ret []LeafNode, nodesValues []BatchNewLeafNodeData) func() error {
return func() error {
c1c2points := make([]*Point, 2*len(nodesValues))
c1c2frs := make([]*Fr, 2*len(nodesValues))
for i, nv := range nodesValues {
valsslice := make([][]byte, NodeWidth)
for idx := range nv.Values {
valsslice[idx] = nv.Values[idx]
}
var leaf *LeafNode
leaf, err := NewLeafNode(nv.Stem, valsslice)
if err != nil {
return err
}
ret[i] = *leaf
c1c2points[2*i], c1c2points[2*i+1] = ret[i].c1, ret[i].c2
c1c2frs[2*i], c1c2frs[2*i+1] = new(Fr), new(Fr)
}
if err := banderwagon.BatchMapToScalarField(c1c2frs, c1c2points); err != nil {
return fmt.Errorf("mapping to scalar field: %s", err)
}
var poly [NodeWidth]Fr
poly[0].SetUint64(1)
for i, nv := range nodesValues {
if err := StemFromLEBytes(&poly[1], nv.Stem); err != nil {
return err
}
poly[2] = *c1c2frs[2*i]
poly[3] = *c1c2frs[2*i+1]
ret[i].commitment = cfg.CommitToPoly(poly[:], 252)
}
return nil
}
}
group.Go(work(ret[start:end], nodesValues[start:end]))
}
if err := group.Wait(); err != nil {
return nil, fmt.Errorf("creating leaf node: %s", err)
}
sort.Slice(ret, func(i, j int) bool {
return bytes.Compare(ret[i].stem, ret[j].stem) < 0
})
return ret, nil
}
// firstDiffByteIdx will return the first index in which the two stems differ.
// Both stems *must* be different.
func firstDiffByteIdx(stem1 []byte, stem2 []byte) int {
for i := range stem1 {
if stem1[i] != stem2[i] {
return i
}
}
panic("stems are equal")
}
func (n *InternalNode) InsertMigratedLeaves(leaves []LeafNode, resolver NodeResolverFn) error {
sort.Slice(leaves, func(i, j int) bool {
return bytes.Compare(leaves[i].stem, leaves[j].stem) < 0
})
// We first mark all children of the subtreess that we'll update in parallel,
// so the subtree updating doesn't produce a concurrent access to n.cowChild(...).
var lastChildrenIdx = -1
for i := range leaves {
if int(leaves[i].stem[0]) != lastChildrenIdx {
lastChildrenIdx = int(leaves[i].stem[0])
if _, ok := n.children[lastChildrenIdx].(HashedNode); ok {
serialized, err := resolver([]byte{byte(lastChildrenIdx)})
if err != nil {
return fmt.Errorf("resolving node: %s", err)
}
resolved, err := ParseNode(serialized, 1)
if err != nil {
return fmt.Errorf("parsing node %x: %w", serialized, err)
}
n.children[lastChildrenIdx] = resolved
}
n.cowChild(byte(lastChildrenIdx))
}
}
// We insert the migrated leaves for each subtree of the root node.
group, _ := errgroup.WithContext(context.Background())
group.SetLimit(runtime.NumCPU())
currStemFirstByte := 0
for i := range leaves {
if leaves[currStemFirstByte].stem[0] != leaves[i].stem[0] {
start := currStemFirstByte
end := i
group.Go(func() error {
return n.insertMigratedLeavesSubtree(leaves[start:end], resolver)
})
currStemFirstByte = i
}
}
group.Go(func() error {
return n.insertMigratedLeavesSubtree(leaves[currStemFirstByte:], resolver)
})
if err := group.Wait(); err != nil {
return fmt.Errorf("inserting migrated leaves: %w", err)
}
return nil
}
func (n *InternalNode) insertMigratedLeavesSubtree(leaves []LeafNode, resolver NodeResolverFn) error { // skipcq: GO-R1005
for i := range leaves {
ln := leaves[i]
parent := n
// Look for the appropriate parent for the leaf node.
for {
if _, ok := parent.children[ln.stem[parent.depth]].(HashedNode); ok {
serialized, err := resolver(ln.stem[:parent.depth+1])
if err != nil {
return fmt.Errorf("resolving node path=%x: %w", ln.stem[:parent.depth+1], err)
}
resolved, err := ParseNode(serialized, parent.depth+1)
if err != nil {
return fmt.Errorf("parsing node %x: %w", serialized, err)
}
parent.children[ln.stem[parent.depth]] = resolved
}
nextParent, ok := parent.children[ln.stem[parent.depth]].(*InternalNode)
if !ok {
break
}
parent.cowChild(ln.stem[parent.depth])
parent = nextParent
}
switch node := parent.children[ln.stem[parent.depth]].(type) {
case Empty:
parent.cowChild(ln.stem[parent.depth])
parent.children[ln.stem[parent.depth]] = &ln
ln.setDepth(parent.depth + 1)
case *LeafNode:
if bytes.Equal(node.stem, ln.stem) {
// In `ln` we have migrated key/values which should be copied to the leaf
// only if there isn't a value there. If there's a value, we skip it since
// our migrated value is stale.
nonPresentValues := make([][]byte, NodeWidth)
for i := range ln.values {
if node.values[i] == nil {
nonPresentValues[i] = ln.values[i]
}
}
if err := node.updateMultipleLeaves(nonPresentValues); err != nil {
return fmt.Errorf("updating leaves: %s", err)
}
continue
}
// Otherwise, we need to create the missing internal nodes depending in the fork point in their stems.
idx := firstDiffByteIdx(node.stem, ln.stem)
// We do a sanity check to make sure that the fork point is not before the current depth.
if byte(idx) <= parent.depth {
return fmt.Errorf("unexpected fork point %d for nodes %x and %x", idx, node.stem, ln.stem)
}
// Create the missing internal nodes.
for i := parent.depth + 1; i <= byte(idx); i++ {
nextParent := newInternalNode(parent.depth + 1).(*InternalNode)
parent.cowChild(ln.stem[parent.depth])
parent.children[ln.stem[parent.depth]] = nextParent
parent = nextParent
}
// Add old and new leaf node to the latest created parent.
parent.cowChild(node.stem[parent.depth])
parent.children[node.stem[parent.depth]] = node
node.setDepth(parent.depth + 1)
parent.cowChild(ln.stem[parent.depth])
parent.children[ln.stem[parent.depth]] = &ln
ln.setDepth(parent.depth + 1)
default:
return fmt.Errorf("unexpected node type %T", node)
}
}
return nil
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
// A few proxy types that export their fields, so that the core type
// does not. The conversion from one type to the other is done by calling
// toExportable on an InternalNode.
type (
ExportableInternalNode struct {
Children []interface{} `json:"children"`
Commitment []byte `json:"commitment"`
}
ExportableLeafNode struct {
Stem Stem `json:"stem"`
Values [][]byte `json:"values"`
C [32]byte `json:"commitment"`
C1 [32]byte `json:"c1"`
C2 [32]byte `json:"c2"`
}
)
+56
View File
@@ -0,0 +1,56 @@
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import (
"testing"
)
func TestJSON(t *testing.T) {
t.Parallel()
root := New()
if err := root.Insert(zeroKeyTest, fourtyKeyTest, nil); err != nil {
t.Fatal(err)
}
if err := root.Insert(oneKeyTest, zeroKeyTest, nil); err != nil {
t.Fatal(err)
}
if err := root.Insert(forkOneKeyTest, zeroKeyTest, nil); err != nil { // Force an internal node in the first layer.
t.Fatal(err)
}
if err := root.Insert(fourtyKeyTest, oneKeyTest, nil); err != nil {
t.Fatal(err)
}
root.(*InternalNode).children[152] = HashedNode{}
root.Commit()
output, err := root.(*InternalNode).ToJSON()
if err != nil {
t.Fatal(err)
}
t.Log(string(output))
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import "errors"
var (
errInsertIntoHash = errors.New("trying to insert into hashed node")
errDeleteHash = errors.New("trying to delete from a hashed subtree")
errDeleteMissing = errors.New("trying to delete a missing group")
errDeleteUnknown = errors.New("trying to delete an out-of-view node")
errReadFromInvalid = errors.New("trying to read from an invalid child")
errSerializeHashedNode = errors.New("trying to serialize a hashed internal node")
errInsertIntoOtherStem = errors.New("insert splits a stem where it should not happen")
errUnknownNodeType = errors.New("unknown node type detected")
errMissingNodeInStateless = errors.New("trying to access a node that is missing from the stateless view")
errIsPOAStub = errors.New("trying to read/write a proof of absence leaf node")
)
const (
// Extension status
extStatusAbsentEmpty = byte(iota) // missing child node along the path
extStatusAbsentOther // path led to a node with a different stem
extStatusPresent // stem was present
)
+88
View File
@@ -0,0 +1,88 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import "errors"
type Empty struct{}
var errDirectInsertIntoEmptyNode = errors.New("an empty node should not be inserted directly into")
func (Empty) Insert([]byte, []byte, NodeResolverFn) error {
return errDirectInsertIntoEmptyNode
}
func (Empty) Delete([]byte, NodeResolverFn) (bool, error) {
return false, errors.New("cant delete an empty node")
}
func (Empty) Get([]byte, NodeResolverFn) ([]byte, error) {
return nil, nil
}
func (n Empty) Commit() *Point {
return n.Commitment()
}
func (Empty) Commitment() *Point {
var id Point
id.SetIdentity()
return &id
}
func (Empty) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) {
return nil, nil, nil, errors.New("trying to produce a commitment for an empty subtree")
}
func (Empty) Serialize() ([]byte, error) {
return nil, errors.New("can't encode empty node to RLP")
}
func (Empty) Copy() VerkleNode {
return Empty(struct{}{})
}
func (Empty) toDot(string, string) string {
return ""
}
func (Empty) setDepth(_ byte) {
panic("should not be try to set the depth of an Empty node")
}
func (Empty) Hash() *Fr {
return &FrZero
}
+65
View File
@@ -0,0 +1,65 @@
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import "testing"
func TestEmptyFuncs(t *testing.T) {
t.Parallel()
var e Empty
err := e.Insert(zeroKeyTest, zeroKeyTest, nil)
if err == nil {
t.Fatal("got nil error when inserting into empty")
}
_, err = e.Delete(zeroKeyTest, nil)
if err == nil {
t.Fatal("got nil error when deleting from empty")
}
v, err := e.Get(zeroKeyTest, nil)
if err != nil {
t.Fatal("got non-nil error when getting from empty")
}
if v != nil {
t.Fatal("non-nil get from empty")
}
if !e.Commitment().Equal(e.Commit()) {
t.Fatal("commitment and commit mismatch")
}
if _, _, _, err := e.GetProofItems(nil, nil); err == nil {
t.Fatal("get proof items should error")
}
if _, err := e.Serialize(); err == nil {
t.Fatal("serialize should error")
}
if !e.Hash().Equal(&FrZero) {
t.Fatal("hash should be the zero element")
}
}
+231
View File
@@ -0,0 +1,231 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import (
"errors"
"fmt"
"github.com/crate-crypto/go-ipa/banderwagon"
)
var (
ErrInvalidNodeEncoding = errors.New("invalid node encoding")
mask = [8]byte{0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1}
)
const (
nodeTypeSize = 1
bitlistSize = NodeWidth / 8
// Shared between internal and leaf nodes.
nodeTypeOffset = 0
// Internal nodes offsets.
internalBitlistOffset = nodeTypeOffset + nodeTypeSize
internalCommitmentOffset = internalBitlistOffset + bitlistSize
// Leaf node offsets.
leafStemOffset = nodeTypeOffset + nodeTypeSize
leafBitlistOffset = leafStemOffset + StemSize
leafCommitmentOffset = leafBitlistOffset + bitlistSize
leafC1CommitmentOffset = leafCommitmentOffset + banderwagon.UncompressedSize
leafC2CommitmentOffset = leafC1CommitmentOffset + banderwagon.UncompressedSize
leafChildrenOffset = leafC2CommitmentOffset + banderwagon.UncompressedSize
leafBasicDataSize = 32
leafSlotSize = 32
leafValueIndexSize = 1
singleSlotLeafSize = nodeTypeSize + StemSize + 2*banderwagon.UncompressedSize + leafValueIndexSize + leafSlotSize
eoaLeafSize = nodeTypeSize + StemSize + 2*banderwagon.UncompressedSize + leafBasicDataSize
)
func bit(bitlist []byte, nr int) bool {
if len(bitlist)*8 <= nr {
return false
}
return bitlist[nr/8]&mask[nr%8] != 0
}
var errSerializedPayloadTooShort = errors.New("verkle payload is too short")
// ParseNode deserializes a node into its proper VerkleNode instance.
// The serialized bytes have the format:
// - Internal nodes: <nodeType><bitlist><commitment>
// - Leaf nodes: <nodeType><stem><bitlist><comm><c1comm><c2comm><children...>
// - EoA nodes: <nodeType><stem><comm><c1comm><balance><nonce>
// - single slot node: <nodeType><stem><comm><cncomm><leaf index><slot>
func ParseNode(serializedNode []byte, depth byte) (VerkleNode, error) {
// Check that the length of the serialized node is at least the smallest possible serialized node.
if len(serializedNode) < nodeTypeSize+banderwagon.UncompressedSize {
return nil, errSerializedPayloadTooShort
}
switch serializedNode[0] {
case leafType:
return parseLeafNode(serializedNode, depth)
case internalType:
return CreateInternalNode(serializedNode[internalBitlistOffset:internalCommitmentOffset], serializedNode[internalCommitmentOffset:], depth)
case eoAccountType:
return parseEoAccountNode(serializedNode, depth)
case singleSlotType:
return parseSingleSlotNode(serializedNode, depth)
default:
return nil, ErrInvalidNodeEncoding
}
}
func parseLeafNode(serialized []byte, depth byte) (VerkleNode, error) {
bitlist := serialized[leafBitlistOffset : leafBitlistOffset+bitlistSize]
var values [NodeWidth][]byte
offset := leafChildrenOffset
for i := 0; i < NodeWidth; i++ {
if bit(bitlist, i) {
if offset+LeafValueSize > len(serialized) {
return nil, fmt.Errorf("verkle payload is too short, need at least %d and only have %d, payload = %x (%w)", offset+LeafValueSize, len(serialized), serialized, errSerializedPayloadTooShort)
}
values[i] = serialized[offset : offset+LeafValueSize]
offset += LeafValueSize
}
}
ln := NewLeafNodeWithNoComms(serialized[leafStemOffset:leafStemOffset+StemSize], values[:])
ln.setDepth(depth)
ln.c1 = new(Point)
// Sanity check that we have at least 3*banderwagon.UncompressedSize bytes left in the serialized payload.
if len(serialized[leafCommitmentOffset:]) < 3*banderwagon.UncompressedSize {
return nil, fmt.Errorf("leaf node commitments are not the correct size, expected at least %d, got %d", 3*banderwagon.UncompressedSize, len(serialized[leafC1CommitmentOffset:]))
}
if err := ln.c1.SetBytesUncompressed(serialized[leafC1CommitmentOffset:leafC1CommitmentOffset+banderwagon.UncompressedSize], true); err != nil {
return nil, fmt.Errorf("setting c1 commitment: %w", err)
}
ln.c2 = new(Point)
if err := ln.c2.SetBytesUncompressed(serialized[leafC2CommitmentOffset:leafC2CommitmentOffset+banderwagon.UncompressedSize], true); err != nil {
return nil, fmt.Errorf("setting c2 commitment: %w", err)
}
ln.commitment = new(Point)
if err := ln.commitment.SetBytesUncompressed(serialized[leafCommitmentOffset:leafC1CommitmentOffset], true); err != nil {
return nil, fmt.Errorf("setting commitment: %w", err)
}
return ln, nil
}
func parseEoAccountNode(serialized []byte, depth byte) (VerkleNode, error) {
var values [NodeWidth][]byte
offset := leafStemOffset + StemSize + 2*banderwagon.UncompressedSize
values[0] = serialized[offset : offset+leafBasicDataSize] // basic data
values[1] = EmptyCodeHash[:]
ln := NewLeafNodeWithNoComms(serialized[leafStemOffset:leafStemOffset+StemSize], values[:])
ln.setDepth(depth)
ln.c1 = new(Point)
if err := ln.c1.SetBytesUncompressed(serialized[leafStemOffset+StemSize:leafStemOffset+StemSize+banderwagon.UncompressedSize], true); err != nil {
return nil, fmt.Errorf("error setting leaf C1 commitment: %w", err)
}
ln.c2 = &banderwagon.Identity
ln.commitment = new(Point)
if err := ln.commitment.SetBytesUncompressed(serialized[leafStemOffset+StemSize+banderwagon.UncompressedSize:leafStemOffset+StemSize+banderwagon.UncompressedSize*2], true); err != nil {
return nil, fmt.Errorf("error setting leaf root commitment: %w", err)
}
return ln, nil
}
func parseSingleSlotNode(serialized []byte, depth byte) (VerkleNode, error) {
var values [NodeWidth][]byte
offset := leafStemOffset
ln := NewLeafNodeWithNoComms(serialized[offset:offset+StemSize], values[:])
offset += StemSize
cnCommBytes := serialized[offset : offset+banderwagon.UncompressedSize]
offset += banderwagon.UncompressedSize
rootCommBytes := serialized[offset : offset+banderwagon.UncompressedSize]
offset += banderwagon.UncompressedSize
idx := serialized[offset]
offset += leafValueIndexSize
values[idx] = serialized[offset : offset+leafSlotSize] // copy slot
ln.setDepth(depth)
if idx < 128 {
ln.c1 = new(Point)
if err := ln.c1.SetBytesUncompressed(cnCommBytes, true); err != nil {
return nil, fmt.Errorf("error setting leaf C1 commitment: %w", err)
}
ln.c2 = &banderwagon.Identity
} else {
ln.c2 = new(Point)
if err := ln.c2.SetBytesUncompressed(cnCommBytes, true); err != nil {
return nil, fmt.Errorf("error setting leaf C2 commitment: %w", err)
}
ln.c1 = &banderwagon.Identity
}
ln.commitment = new(Point)
if err := ln.commitment.SetBytesUncompressed(rootCommBytes, true); err != nil {
return nil, fmt.Errorf("error setting leaf root commitment: %w", err)
}
return ln, nil
}
func CreateInternalNode(bitlist []byte, raw []byte, depth byte) (*InternalNode, error) {
// GetTreeConfig caches computation result, hence
// this op has low overhead
node := new(InternalNode)
if len(bitlist) != bitlistSize {
return nil, ErrInvalidNodeEncoding
}
// Create a HashNode placeholder for all values
// corresponding to a set bit.
node.children = make([]VerkleNode, NodeWidth)
for i, b := range bitlist {
for j := 0; j < 8; j++ {
if b&mask[j] != 0 {
node.children[8*i+j] = HashedNode{}
} else {
node.children[8*i+j] = Empty(struct{}{})
}
}
}
node.depth = depth
if len(raw) != banderwagon.UncompressedSize {
return nil, ErrInvalidNodeEncoding
}
node.commitment = new(Point)
if err := node.commitment.SetBytesUncompressed(raw, true); err != nil {
return nil, fmt.Errorf("setting commitment: %w", err)
}
return node, nil
}
+192
View File
@@ -0,0 +1,192 @@
package verkle
import (
"bytes"
"testing"
"github.com/crate-crypto/go-ipa/banderwagon"
)
func TestParseNodeEmptyPayload(t *testing.T) {
t.Parallel()
_, err := ParseNode([]byte{}, 0)
if err != errSerializedPayloadTooShort {
t.Fatalf("invalid error, got %v, expected %v", err, "unexpected EOF")
}
}
func TestLeafStemLength(t *testing.T) {
t.Parallel()
// Serialize a leaf with no values, but whose stem is 32 bytes. The
// serialization should trim the extra byte.
toolong := make([]byte, 32)
leaf, err := NewLeafNode(toolong, make([][]byte, NodeWidth))
if err != nil {
t.Fatal(err)
}
ser, err := leaf.Serialize()
if err != nil {
t.Fatal(err)
}
if len(ser) != nodeTypeSize+StemSize+bitlistSize+3*banderwagon.UncompressedSize {
t.Fatalf("invalid serialization when the stem is longer than 31 bytes: %x (%d bytes != %d)", ser, len(ser), nodeTypeSize+StemSize+bitlistSize+2*banderwagon.UncompressedSize)
}
}
func TestInvalidNodeEncoding(t *testing.T) {
t.Parallel()
// Test a short payload.
if _, err := ParseNode([]byte{leafType}, 0); err != errSerializedPayloadTooShort {
t.Fatalf("invalid error, got %v, expected %v", err, errSerializedPayloadTooShort)
}
// Test an invalid node type.
values := make([][]byte, NodeWidth)
values[42] = testValue
ln, err := NewLeafNode(ffx32KeyTest, values)
if err != nil {
t.Fatal(err)
}
lnbytes, err := ln.Serialize()
if err != nil {
t.Fatalf("serializing leaf node: %v", err)
}
lnbytes[0] = 0xc0 // Change the type of the node to something invalid.
if _, err := ParseNode(lnbytes, 0); err != ErrInvalidNodeEncoding {
t.Fatalf("invalid error, got %v, expected %v", err, ErrInvalidNodeEncoding)
}
}
func TestParseNodeEoA(t *testing.T) {
values := make([][]byte, 256)
values[0] = zero32[:]
values[1] = EmptyCodeHash[:] // set empty code hash as balance, because why not
values[2] = fourtyKeyTest[:] // set nonce to 64
values[3] = EmptyCodeHash[:] // set empty code hash
values[4] = zero32[:] // zero-size
ln, err := NewLeafNode(ffx32KeyTest[:31], values)
if err != nil {
t.Fatalf("error creating leaf node: %v", err)
}
serialized, err := ln.Serialize()
if err != nil {
t.Fatalf("error serializing leaf node: %v", err)
}
if serialized[0] != eoAccountType {
t.Fatalf("invalid encoding type, got %d, expected %d", serialized[0], eoAccountType)
}
deserialized, err := ParseNode(serialized, 5)
if err != nil {
t.Fatalf("error deserializing leaf node: %v", err)
}
lnd, ok := deserialized.(*LeafNode)
if !ok {
t.Fatalf("expected leaf node, got %T", deserialized)
}
if lnd.depth != 5 {
t.Fatalf("invalid depth, got %d, expected %d", lnd.depth, 5)
}
if !bytes.Equal(lnd.stem, ffx32KeyTest[:31]) {
t.Fatalf("invalid stem, got %x, expected %x", lnd.stem, ffx32KeyTest[:31])
}
if !bytes.Equal(lnd.values[0], zero32[:]) {
t.Fatalf("invalid version, got %x, expected %x", lnd.values[0], zero32[:])
}
if !bytes.Equal(lnd.values[1], EmptyCodeHash[:]) {
t.Fatalf("invalid balance, got %x, expected %x", lnd.values[1], EmptyCodeHash[:])
}
if !bytes.Equal(lnd.values[2], fourtyKeyTest[:]) {
t.Fatalf("invalid nonce, got %x, expected %x", lnd.values[2], fourtyKeyTest[:])
}
if !bytes.Equal(lnd.values[3], EmptyCodeHash[:]) {
t.Fatalf("invalid code hash, got %x, expected %x", lnd.values[3], EmptyCodeHash[:])
}
if !bytes.Equal(lnd.values[4], zero32[:]) {
t.Fatalf("invalid code size, got %x, expected %x", lnd.values[4], zero32[:])
}
if !lnd.c2.Equal(&banderwagon.Identity) {
t.Fatalf("invalid c2, got %x, expected %x", lnd.c2, banderwagon.Identity)
}
if !lnd.c1.Equal(ln.c1) {
t.Fatalf("invalid c1, got %x, expected %x", lnd.c1, ln.c1)
}
if !lnd.commitment.Equal(ln.commitment) {
t.Fatalf("invalid commitment, got %x, expected %x", lnd.commitment, ln.commitment)
}
}
func TestParseNodeSingleSlot(t *testing.T) {
values := make([][]byte, 256)
values[153] = EmptyCodeHash
ln, err := NewLeafNode(ffx32KeyTest[:31], values)
if err != nil {
t.Fatalf("error creating leaf node: %v", err)
}
serialized, err := ln.Serialize()
if err != nil {
t.Fatalf("error serializing leaf node: %v", err)
}
if serialized[0] != singleSlotType {
t.Fatalf("invalid encoding type, got %d, expected %d", serialized[0], singleSlotType)
}
deserialized, err := ParseNode(serialized, 5)
if err != nil {
t.Fatalf("error deserializing leaf node: %v", err)
}
lnd, ok := deserialized.(*LeafNode)
if !ok {
t.Fatalf("expected leaf node, got %T", deserialized)
}
if lnd.depth != 5 {
t.Fatalf("invalid depth, got %d, expected %d", lnd.depth, 5)
}
if !bytes.Equal(lnd.stem, ffx32KeyTest[:31]) {
t.Fatalf("invalid stem, got %x, expected %x", lnd.stem, ffx32KeyTest[:31])
}
for i := range values {
if i != 153 {
if lnd.values[i] != nil {
t.Fatalf("value %d, got %x, expected empty slot", i, lnd.values[i])
}
} else {
if !bytes.Equal(lnd.values[i], EmptyCodeHash[:]) {
t.Fatalf("got %x, expected empty slot", lnd.values[i])
}
}
}
if !lnd.c2.Equal(&banderwagon.Identity) {
t.Fatalf("invalid c2, got %x, expected %x", lnd.c2, banderwagon.Identity)
}
if !lnd.c1.Equal(ln.c1) {
t.Fatalf("invalid c1, got %x, expected %x", lnd.c1, ln.c1)
}
if !lnd.commitment.Equal(ln.commitment) {
t.Fatalf("invalid commitment, got %x, expected %x", lnd.commitment, ln.commitment)
}
}
+93
View File
@@ -0,0 +1,93 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import (
"errors"
"fmt"
)
type HashedNode struct{}
func (HashedNode) Insert([]byte, []byte, NodeResolverFn) error {
return errInsertIntoHash
}
func (HashedNode) Delete([]byte, NodeResolverFn) (bool, error) {
return false, errors.New("cant delete a hashed node in-place")
}
func (HashedNode) Get([]byte, NodeResolverFn) ([]byte, error) {
return nil, errors.New("can not read from a hash node")
}
func (HashedNode) Commit() *Point {
// TODO: we should reconsider what to do with the VerkleNode interface and how
// HashedNode fits into the picture, since Commit(), Commitment() and Hash()
// now panics. Despite these calls must not happen at runtime, it is still
// quite risky. The reason we end up in this place is because PBSS came quite
// recently compared with the VerkleNode interface design. We should probably
// reconsider splitting the interface or find some safer workaround.
panic("can not commit a hash node")
}
func (HashedNode) Commitment() *Point {
panic("can not get commitment of a hash node")
}
func (HashedNode) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) {
return nil, nil, nil, errors.New("can not get the full path, and there is no proof of absence")
}
func (HashedNode) Serialize() ([]byte, error) {
return nil, errSerializeHashedNode
}
func (HashedNode) Copy() VerkleNode {
return HashedNode{}
}
func (HashedNode) toDot(parent, path string) string {
return fmt.Sprintf("hash%s [label=\"unresolved\"]\n%s -> hash%s\n", path, parent, path)
}
func (HashedNode) setDepth(_ byte) {
// do nothing
}
func (HashedNode) Hash() *Fr {
panic("can not hash a hashed node")
}
+55
View File
@@ -0,0 +1,55 @@
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import "testing"
func TestHashedNodeFuncs(t *testing.T) {
t.Parallel()
e := HashedNode{}
err := e.Insert(zeroKeyTest, zeroKeyTest, nil)
if err != errInsertIntoHash {
t.Fatal("got nil error when inserting into a hashed node")
}
_, err = e.Delete(zeroKeyTest, nil)
if err == nil {
t.Fatal("got nil error when deleting from a hashed node")
}
v, err := e.Get(zeroKeyTest, nil)
if err == nil {
t.Fatal("got nil error when getting from a hashed node")
}
if v != nil {
t.Fatal("non-nil get from a hashed node")
}
if _, _, _, err := e.GetProofItems(nil, nil); err == nil {
t.Fatal("got nil error when getting proof items from a hashed node")
}
if _, err := e.Serialize(); err != errSerializeHashedNode {
t.Fatal("got nil error when serializing a hashed node")
}
}
+78
View File
@@ -0,0 +1,78 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import (
"errors"
"github.com/crate-crypto/go-ipa/banderwagon"
)
type (
Fr = banderwagon.Fr
Point = banderwagon.Element
SerializedPoint = []byte
SerializedPointCompressed = []byte
)
func FromLEBytes(fr *Fr, data []byte) error {
if len(data) > LeafValueSize {
return errors.New("data is too long")
}
var aligned [LeafValueSize]byte
copy(aligned[:], data)
fr.SetBytesLE(aligned[:])
return nil
}
func StemFromLEBytes(fr *Fr, data []byte) error {
if len(data) != StemSize {
return errors.New("data length must be StemSize")
}
return FromLEBytes(fr, data)
}
func FromBytes(fr *Fr, data []byte) {
var aligned [32]byte
copy(aligned[32-len(data):], data)
fr.SetBytes(aligned[:])
}
func HashPointToBytes(point *Point) [32]byte {
var hashedPoint Fr
point.MapToScalarField(&hashedPoint)
return hashedPoint.BytesLE()
}
+24
View File
@@ -0,0 +1,24 @@
package verkle
import (
"encoding/binary"
"math/big"
"testing"
)
func TestFromBytes(t *testing.T) {
t.Parallel()
var fr Fr
var beFortyTwo [8]byte
binary.BigEndian.PutUint64(beFortyTwo[:], 42)
FromBytes(&fr, beFortyTwo[:])
bi := big.NewInt(0)
if fr.ToBigIntRegular(bi).Int64() != 42 {
t.Fatalf("got %v, want 42", bi)
}
}
+689
View File
@@ -0,0 +1,689 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import (
"bytes"
"errors"
"fmt"
"sort"
"unsafe"
ipa "github.com/crate-crypto/go-ipa"
"github.com/crate-crypto/go-ipa/common"
)
const IPA_PROOF_DEPTH = 8
type IPAProof struct {
CL [IPA_PROOF_DEPTH][32]byte `json:"cl"`
CR [IPA_PROOF_DEPTH][32]byte `json:"cr"`
FinalEvaluation [32]byte `json:"finalEvaluation"`
}
type VerkleProof struct {
OtherStems [][StemSize]byte `json:"otherStems"`
DepthExtensionPresent []byte `json:"depthExtensionPresent"`
CommitmentsByPath [][32]byte `json:"commitmentsByPath"`
D [32]byte `json:"d"`
IPAProof *IPAProof `json:"ipa_proof"`
}
func (vp *VerkleProof) Copy() *VerkleProof {
if vp == nil {
return nil
}
ret := &VerkleProof{
OtherStems: make([][StemSize]byte, len(vp.OtherStems)),
DepthExtensionPresent: make([]byte, len(vp.DepthExtensionPresent)),
CommitmentsByPath: make([][32]byte, len(vp.CommitmentsByPath)),
IPAProof: &IPAProof{},
}
copy(ret.OtherStems, vp.OtherStems)
copy(ret.DepthExtensionPresent, vp.DepthExtensionPresent)
copy(ret.CommitmentsByPath, vp.CommitmentsByPath)
ret.D = vp.D
if vp.IPAProof != nil {
ret.IPAProof = vp.IPAProof
}
return ret
}
func (vp *VerkleProof) Equal(other *VerkleProof) error {
if len(vp.OtherStems) != len(other.OtherStems) {
return fmt.Errorf("different number of other stems: %d != %d", len(vp.OtherStems), len(other.OtherStems))
}
for i := range vp.OtherStems {
if vp.OtherStems[i] != other.OtherStems[i] {
return fmt.Errorf("different other stem: %x != %x", vp.OtherStems[i], other.OtherStems[i])
}
}
if len(vp.DepthExtensionPresent) != len(other.DepthExtensionPresent) {
return fmt.Errorf("different number of depth extension present: %d != %d", len(vp.DepthExtensionPresent), len(other.DepthExtensionPresent))
}
if !bytes.Equal(vp.DepthExtensionPresent, other.DepthExtensionPresent) {
return fmt.Errorf("different depth extension present: %x != %x", vp.DepthExtensionPresent, other.DepthExtensionPresent)
}
if len(vp.CommitmentsByPath) != len(other.CommitmentsByPath) {
return fmt.Errorf("different number of commitments by path: %d != %d", len(vp.CommitmentsByPath), len(other.CommitmentsByPath))
}
for i := range vp.CommitmentsByPath {
if vp.CommitmentsByPath[i] != other.CommitmentsByPath[i] {
return fmt.Errorf("different commitment by path: %x != %x", vp.CommitmentsByPath[i], other.CommitmentsByPath[i])
}
}
if vp.D != other.D {
return fmt.Errorf("different D: %x != %x", vp.D, other.D)
}
return nil
}
type Proof struct {
Multipoint *ipa.MultiProof // multipoint argument
ExtStatus []byte // the extension status of each stem
Cs []*Point // commitments, sorted by their path in the tree
PoaStems []Stem // stems proving another stem is absent
Keys [][]byte
PreValues [][]byte
PostValues [][]byte
}
type SuffixStateDiff struct {
Suffix byte `json:"suffix"`
CurrentValue *[32]byte `json:"currentValue"`
NewValue *[32]byte `json:"newValue"`
}
type SuffixStateDiffs []SuffixStateDiff
type StemStateDiff struct {
Stem [StemSize]byte `json:"stem"`
SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"`
}
type StateDiff []StemStateDiff
func (sd StateDiff) Copy() StateDiff {
ret := make(StateDiff, len(sd))
for i := range sd {
copy(ret[i].Stem[:], sd[i].Stem[:])
ret[i].SuffixDiffs = make([]SuffixStateDiff, len(sd[i].SuffixDiffs))
for j := range sd[i].SuffixDiffs {
ret[i].SuffixDiffs[j].Suffix = sd[i].SuffixDiffs[j].Suffix
if sd[i].SuffixDiffs[j].CurrentValue != nil {
ret[i].SuffixDiffs[j].CurrentValue = &[32]byte{}
copy((*ret[i].SuffixDiffs[j].CurrentValue)[:], (*sd[i].SuffixDiffs[j].CurrentValue)[:])
}
if sd[i].SuffixDiffs[j].NewValue != nil {
ret[i].SuffixDiffs[j].NewValue = &[32]byte{}
copy((*ret[i].SuffixDiffs[j].NewValue)[:], (*sd[i].SuffixDiffs[j].NewValue)[:])
}
}
}
return ret
}
func (sd StateDiff) Equal(other StateDiff) error {
if len(sd) != len(other) {
return fmt.Errorf("different number of stem state diffs: %d != %d", len(sd), len(other))
}
for i := range sd {
if sd[i].Stem != other[i].Stem {
return fmt.Errorf("different stem: %x != %x", sd[i].Stem, other[i].Stem)
}
if len(sd[i].SuffixDiffs) != len(other[i].SuffixDiffs) {
return fmt.Errorf("different number of suffix state diffs: %d != %d", len(sd[i].SuffixDiffs), len(other[i].SuffixDiffs))
}
for j := range sd[i].SuffixDiffs {
if sd[i].SuffixDiffs[j].Suffix != other[i].SuffixDiffs[j].Suffix {
return fmt.Errorf("different suffix: %x != %x", sd[i].SuffixDiffs[j].Suffix, other[i].SuffixDiffs[j].Suffix)
}
if sd[i].SuffixDiffs[j].CurrentValue != nil && other[i].SuffixDiffs[j].CurrentValue != nil {
if *sd[i].SuffixDiffs[j].CurrentValue != *other[i].SuffixDiffs[j].CurrentValue {
return fmt.Errorf("different current value: %x != %x", *sd[i].SuffixDiffs[j].CurrentValue, *other[i].SuffixDiffs[j].CurrentValue)
}
} else if sd[i].SuffixDiffs[j].CurrentValue != nil || other[i].SuffixDiffs[j].CurrentValue != nil {
return fmt.Errorf("different current value: %x != %x", sd[i].SuffixDiffs[j].CurrentValue, other[i].SuffixDiffs[j].CurrentValue)
}
if sd[i].SuffixDiffs[j].NewValue != nil && other[i].SuffixDiffs[j].NewValue != nil {
if *sd[i].SuffixDiffs[j].NewValue != *other[i].SuffixDiffs[j].NewValue {
return fmt.Errorf("different new value: %x != %x", *sd[i].SuffixDiffs[j].NewValue, *other[i].SuffixDiffs[j].NewValue)
}
} else if sd[i].SuffixDiffs[j].NewValue != nil || other[i].SuffixDiffs[j].NewValue != nil {
return fmt.Errorf("different new value: %x != %x", sd[i].SuffixDiffs[j].NewValue, other[i].SuffixDiffs[j].NewValue)
}
}
}
return nil
}
func GetCommitmentsForMultiproof(root VerkleNode, keys [][]byte, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, error) {
sort.Sort(keylist(keys))
return root.GetProofItems(keylist(keys), resolver)
}
// getProofElementsFromTree factors the logic that is used both in the proving and verification methods. It takes a pre-state
// tree and an optional post-state tree, extracts the proof data from them and returns all the items required to build/verify
// a proof.
func getProofElementsFromTree(preroot, postroot VerkleNode, keys [][]byte, resolver NodeResolverFn) (*ProofElements, []byte, []Stem, [][]byte, error) {
// go-ipa won't accept no key as an input, catch this corner case
// and return an empty result.
if len(keys) == 0 {
return nil, nil, nil, nil, errors.New("no key provided for proof")
}
pe, es, poas, err := GetCommitmentsForMultiproof(preroot, keys, resolver)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("error getting pre-state proof data: %w", err)
}
// if a post-state tree is present, merge its proof elements with
// those of the pre-state tree, so that they can be proved together.
postvals := make([][]byte, len(keys))
if postroot != nil {
// keys were sorted already in the above GetcommitmentsForMultiproof.
// Set the post values, if they are untouched, leave them `nil`
for i := range keys {
val, err := postroot.Get(keys[i], resolver)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("error getting post-state value for key %x: %w", keys[i], err)
}
if !bytes.Equal(pe.Vals[i], val) {
postvals[i] = val
}
}
}
// [0:3]: proof elements of the pre-state trie for serialization,
// 3: values to be inserted in the post-state trie for serialization
return pe, es, poas, postvals, nil
}
func MakeVerkleMultiProof(preroot, postroot VerkleNode, keys [][]byte, resolver NodeResolverFn) (*Proof, []*Point, []byte, []*Fr, error) {
pe, es, poas, postvals, err := getProofElementsFromTree(preroot, postroot, keys, resolver)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("get commitments for multiproof: %s", err)
}
cfg := GetConfig()
tr := common.NewTranscript("vt")
mpArg, err := ipa.CreateMultiProof(tr, cfg.conf, pe.Cis, pe.Fis, pe.Zis)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("creating multiproof: %w", err)
}
// It's wheel-reinvention time again 🎉: reimplement a basic
// feature that should be part of the stdlib.
// "But golang is a high-productivity language!!!" 🤪
// len()-1, because the root is already present in the
// parent block, so we don't keep it in the proof.
paths := make([]string, 0, len(pe.ByPath)-1)
for path := range pe.ByPath {
if len(path) > 0 {
paths = append(paths, path)
}
}
sort.Strings(paths)
cis := make([]*Point, len(pe.ByPath)-1)
for i, path := range paths {
cis[i] = pe.ByPath[path]
}
proof := &Proof{
Multipoint: mpArg,
Cs: cis,
ExtStatus: es,
PoaStems: poas,
Keys: keys,
PreValues: pe.Vals,
PostValues: postvals,
}
return proof, pe.Cis, pe.Zis, pe.Yis, nil
}
// verifyVerkleProofWithPreState takes a proof and a trusted tree root and verifies that the proof is valid.
func verifyVerkleProofWithPreState(proof *Proof, preroot VerkleNode) error {
pe, _, _, _, err := getProofElementsFromTree(preroot, nil, proof.Keys, nil)
if err != nil {
return fmt.Errorf("error getting proof elements: %w", err)
}
if ok, err := verifyVerkleProof(proof, pe.Cis, pe.Zis, pe.Yis, GetConfig()); !ok || err != nil {
return fmt.Errorf("error verifying proof: verifies=%v, error=%w", ok, err)
}
return nil
}
func verifyVerkleProof(proof *Proof, Cs []*Point, indices []uint8, ys []*Fr, tc *Config) (bool, error) {
tr := common.NewTranscript("vt")
return ipa.CheckMultiProof(tr, tc.conf, proof.Multipoint, Cs, ys, indices)
}
// SerializeProof serializes the proof in the rust-verkle format:
// * len(Proof of absence stem) || Proof of absence stems
// * len(depths) || serialize(depth || ext statusi)
// * len(commitments) || serialize(commitment)
// * Multipoint proof
// it also returns the serialized keys and values
func SerializeProof(proof *Proof) (*VerkleProof, StateDiff, error) {
otherstems := make([][StemSize]byte, len(proof.PoaStems))
for i, stem := range proof.PoaStems {
copy(otherstems[i][:], stem)
}
cbp := make([][32]byte, len(proof.Cs))
for i, C := range proof.Cs {
serialized := C.Bytes()
copy(cbp[i][:], serialized[:])
}
var cls, crs [IPA_PROOF_DEPTH][32]byte
for i := 0; i < IPA_PROOF_DEPTH; i++ {
l := proof.Multipoint.IPA.L[i].Bytes()
copy(cls[i][:], l[:])
r := proof.Multipoint.IPA.R[i].Bytes()
copy(crs[i][:], r[:])
}
var stemdiff *StemStateDiff
var statediff StateDiff
for i, key := range proof.Keys {
stem := KeyToStem(key)
if stemdiff == nil || !bytes.Equal(stemdiff.Stem[:], stem) {
statediff = append(statediff, StemStateDiff{})
stemdiff = &statediff[len(statediff)-1]
copy(stemdiff.Stem[:], stem)
}
stemdiff.SuffixDiffs = append(stemdiff.SuffixDiffs, SuffixStateDiff{Suffix: key[StemSize]})
newsd := &stemdiff.SuffixDiffs[len(stemdiff.SuffixDiffs)-1]
var valueLen = len(proof.PreValues[i])
switch valueLen {
case 0:
// null value
case 32:
newsd.CurrentValue = (*[32]byte)(proof.PreValues[i])
default:
var aligned [32]byte
copy(aligned[:valueLen], proof.PreValues[i])
newsd.CurrentValue = (*[32]byte)(unsafe.Pointer(&aligned[0]))
}
valueLen = len(proof.PostValues[i])
switch valueLen {
case 0:
// null value
case 32:
newsd.NewValue = (*[32]byte)(proof.PostValues[i])
default:
// TODO remove usage of unsafe
var aligned [32]byte
copy(aligned[:valueLen], proof.PostValues[i])
newsd.NewValue = (*[32]byte)(unsafe.Pointer(&aligned[0]))
}
}
return &VerkleProof{
OtherStems: otherstems,
DepthExtensionPresent: proof.ExtStatus,
CommitmentsByPath: cbp,
D: proof.Multipoint.D.Bytes(),
IPAProof: &IPAProof{
CL: cls,
CR: crs,
FinalEvaluation: proof.Multipoint.IPA.A_scalar.Bytes(),
},
}, statediff, nil
}
// DeserializeProof deserializes the proof found in blocks, into a format that
// can be used to rebuild a stateless version of the tree.
func DeserializeProof(vp *VerkleProof, statediff StateDiff) (*Proof, error) {
var (
poaStems []Stem
keys [][]byte
prevalues, postvalues [][]byte
extStatus []byte
commitments []*Point
multipoint ipa.MultiProof
)
poaStems = make([]Stem, len(vp.OtherStems))
for i, poaStem := range vp.OtherStems {
poaStems[i] = make([]byte, len(poaStem))
copy(poaStems[i], poaStem[:])
}
extStatus = vp.DepthExtensionPresent
commitments = make([]*Point, len(vp.CommitmentsByPath))
for i, commitmentBytes := range vp.CommitmentsByPath {
var commitment Point
if err := commitment.SetBytes(commitmentBytes[:]); err != nil {
return nil, err
}
commitments[i] = &commitment
}
if err := multipoint.D.SetBytes(vp.D[:]); err != nil {
return nil, fmt.Errorf("setting D: %w", err)
}
multipoint.IPA.A_scalar.SetBytes(vp.IPAProof.FinalEvaluation[:])
multipoint.IPA.L = make([]Point, IPA_PROOF_DEPTH)
for i, b := range vp.IPAProof.CL {
if err := multipoint.IPA.L[i].SetBytes(b[:]); err != nil {
return nil, fmt.Errorf("setting L[%d]: %w", i, err)
}
}
multipoint.IPA.R = make([]Point, IPA_PROOF_DEPTH)
for i, b := range vp.IPAProof.CR {
if err := multipoint.IPA.R[i].SetBytes(b[:]); err != nil {
return nil, fmt.Errorf("setting R[%d]: %w", i, err)
}
}
// turn statediff into keys and values
for _, stemdiff := range statediff {
for _, suffixdiff := range stemdiff.SuffixDiffs {
var k [32]byte
copy(k[:StemSize], stemdiff.Stem[:])
k[StemSize] = suffixdiff.Suffix
keys = append(keys, k[:])
if suffixdiff.CurrentValue != nil {
prevalues = append(prevalues, suffixdiff.CurrentValue[:])
} else {
prevalues = append(prevalues, nil)
}
if suffixdiff.NewValue != nil {
postvalues = append(postvalues, suffixdiff.NewValue[:])
} else {
postvalues = append(postvalues, nil)
}
}
}
proof := Proof{
&multipoint,
extStatus,
commitments,
poaStems,
keys,
prevalues,
postvalues,
}
return &proof, nil
}
type stemInfo struct {
depth byte
stemType byte
has_c1, has_c2 bool
values map[byte][]byte
stem []byte
}
// PreStateTreeFromProof builds a stateless prestate tree from the proof.
func PreStateTreeFromProof(proof *Proof, rootC *Point) (VerkleNode, error) { // skipcq: GO-R1005
if len(proof.Keys) != len(proof.PreValues) {
return nil, fmt.Errorf("incompatible number of keys and pre-values: %d != %d", len(proof.Keys), len(proof.PreValues))
}
if len(proof.Keys) != len(proof.PostValues) {
return nil, fmt.Errorf("incompatible number of keys and post-values: %d != %d", len(proof.Keys), len(proof.PostValues))
}
stems := make([][]byte, 0, len(proof.Keys))
for _, k := range proof.Keys {
stem := KeyToStem(k)
if len(stems) == 0 || !bytes.Equal(stems[len(stems)-1], stem) {
stems = append(stems, stem)
}
}
if len(stems) != len(proof.ExtStatus) {
return nil, fmt.Errorf("invalid number of stems and extension statuses: %d != %d", len(stems), len(proof.ExtStatus))
}
var (
info = map[string]stemInfo{}
paths [][]byte
err error
poas = proof.PoaStems
)
// The proof of absence stems must be sorted. If that isn't the case, the proof is invalid.
if !sort.IsSorted(bytesSlice(proof.PoaStems)) {
return nil, fmt.Errorf("proof of absence stems are not sorted")
}
// We build a cache of paths that have a presence extension status.
pathsWithExtPresent := map[string]struct{}{}
i := 0
for _, es := range proof.ExtStatus {
if es&3 == extStatusPresent {
pathsWithExtPresent[string(stems[i][:es>>3])] = struct{}{}
}
i++
}
// assign one or more stem to each stem info
for i, es := range proof.ExtStatus {
si := stemInfo{
depth: es >> 3,
stemType: es & 3,
}
path := stems[i][:si.depth]
switch si.stemType {
case extStatusAbsentEmpty:
// All keys that are part of a proof of absence, must contain empty
// prestate values. If that isn't the case, the proof is invalid.
for j := range proof.Keys { // TODO: DoS risk, use map or binary search.
if bytes.HasPrefix(proof.Keys[j], stems[i]) && proof.PreValues[j] != nil {
return nil, fmt.Errorf("proof of absence (empty) stem %x has a value", si.stem)
}
}
case extStatusAbsentOther:
// All keys that are part of a proof of absence, must contain empty
// prestate values. If that isn't the case, the proof is invalid.
for j := range proof.Keys { // TODO: DoS risk, use map or binary search.
if bytes.HasPrefix(proof.Keys[j], stems[i]) && proof.PreValues[j] != nil {
return nil, fmt.Errorf("proof of absence (other) stem %x has a value", si.stem)
}
}
// For this absent path, we must first check if this path contains a proof of presence.
// If that is the case, we don't have to do anything since the corresponding leaf will be
// constructed by that extension status (already processed or to be processed).
// In other case, we should get the stem from the list of proof of absence stems.
if _, ok := pathsWithExtPresent[string(path)]; ok {
continue
}
// Note that this path doesn't have proof of presence (previous if check above), but
// it can have multiple proof of absence. If a previous proof of absence had already
// created the stemInfo for this path, we don't have to do anything.
if _, ok := info[string(path)]; ok {
continue
}
si.stem = poas[0]
poas = poas[1:]
case extStatusPresent:
si.values = map[byte][]byte{}
si.stem = stems[i]
for j, k := range proof.Keys { // TODO: DoS risk, use map or binary search.
if bytes.Equal(KeyToStem(k), si.stem) {
si.values[k[StemSize]] = proof.PreValues[j]
si.has_c1 = si.has_c1 || (k[StemSize] < 128)
si.has_c2 = si.has_c2 || (k[StemSize] >= 128)
}
}
default:
return nil, fmt.Errorf("invalid extension status: %d", si.stemType)
}
info[string(path)] = si
paths = append(paths, path)
}
if len(poas) != 0 {
return nil, fmt.Errorf("not all proof of absence stems were used: %d", len(poas))
}
root := NewStatelessInternal(0, rootC).(*InternalNode)
comms := proof.Cs
for _, p := range paths {
// NOTE: the reconstructed tree won't tell the
// difference between leaves missing from view
// and absent leaves. This is enough for verification
// but not for block validation.
values := make([][]byte, NodeWidth)
for i, k := range proof.Keys {
if len(proof.PreValues[i]) == 0 {
// Skip the nil keys, they are here to prove
// an absence.
continue
}
if bytes.Equal(KeyToStem(k), info[string(p)].stem) {
values[k[StemSize]] = proof.PreValues[i]
}
}
comms, err = root.CreatePath(p, info[string(p)], comms, values)
if err != nil {
return nil, err
}
}
return root, nil
}
// PostStateTreeFromProof uses the pre-state trie and the list of updated values
// to produce the stateless post-state trie.
func PostStateTreeFromStateDiff(preroot VerkleNode, statediff StateDiff) (VerkleNode, error) {
postroot := preroot.Copy()
for _, stemstatediff := range statediff {
var (
values = make([][]byte, NodeWidth)
overwrites bool
)
for _, suffixdiff := range stemstatediff.SuffixDiffs {
if /* len(suffixdiff.NewValue) > 0 - this only works for a slice */ suffixdiff.NewValue != nil {
// if this value is non-nil, it means InsertValuesAtStem should be
// called, otherwise, skip updating the tree.
overwrites = true
values[suffixdiff.Suffix] = suffixdiff.NewValue[:]
}
}
if overwrites {
var stem [StemSize]byte
copy(stem[:StemSize], stemstatediff.Stem[:])
if err := postroot.(*InternalNode).InsertValuesAtStem(stem[:], values, nil); err != nil {
return nil, fmt.Errorf("error overwriting value in post state: %w", err)
}
}
}
postroot.Commit()
return postroot, nil
}
type bytesSlice []Stem
func (x bytesSlice) Len() int { return len(x) }
func (x bytesSlice) Less(i, j int) bool { return bytes.Compare(x[i], x[j]) < 0 }
func (x bytesSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
// Verify is the API function that verifies a verkle proofs as found in a block/execution payload.
func Verify(vp *VerkleProof, preStateRoot []byte, postStateRoot []byte, statediff StateDiff) error {
proof, err := DeserializeProof(vp, statediff)
if err != nil {
return fmt.Errorf("verkle proof deserialization error: %w", err)
}
rootC := new(Point)
if err := rootC.SetBytes(preStateRoot); err != nil {
return fmt.Errorf("error setting prestate root: %w", err)
}
pretree, err := PreStateTreeFromProof(proof, rootC)
if err != nil {
return fmt.Errorf("error rebuilding the pre-tree from proof: %w", err)
}
// TODO this should not be necessary, remove it
// after the new proof generation code has stabilized.
for _, stemdiff := range statediff {
for _, suffixdiff := range stemdiff.SuffixDiffs {
var key [32]byte
copy(key[:31], stemdiff.Stem[:])
key[31] = suffixdiff.Suffix
val, err := pretree.Get(key[:], nil)
if err != nil {
return fmt.Errorf("could not find key %x in tree rebuilt from proof: %w", key, err)
}
if len(val) > 0 {
if !bytes.Equal(val, suffixdiff.CurrentValue[:]) {
return fmt.Errorf("could not find correct value at %x in tree rebuilt from proof: %x != %x", key, val, *suffixdiff.CurrentValue)
}
} else {
if suffixdiff.CurrentValue != nil && len(suffixdiff.CurrentValue) != 0 {
return fmt.Errorf("could not find correct value at %x in tree rebuilt from proof: %x != %x", key, val, *suffixdiff.CurrentValue)
}
}
}
}
// TODO: this is necessary to verify that the post-values are the correct ones.
// But all this can be avoided with a even faster way. The EVM block execution can
// keep track of the written keys, and compare that list with this post-values list.
// This can avoid regenerating the post-tree which is somewhat expensive.
posttree, err := PostStateTreeFromStateDiff(pretree, statediff)
if err != nil {
return fmt.Errorf("error rebuilding the post-tree from proof: %w", err)
}
regeneratedPostTreeRoot := posttree.Commitment().Bytes()
if !bytes.Equal(regeneratedPostTreeRoot[:], postStateRoot) {
return fmt.Errorf("post tree root mismatch: %x != %x", regeneratedPostTreeRoot, postStateRoot)
}
return verifyVerkleProofWithPreState(proof, pretree)
}
+284
View File
@@ -0,0 +1,284 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import (
"encoding/hex"
"encoding/json"
"fmt"
)
// HexToPrefixedString turns a byte slice into its hex representation
// and prefixes it with `0x`.
func HexToPrefixedString(data []byte) string {
return "0x" + hex.EncodeToString(data)
}
// PrefixedHexStringToBytes does the opposite of HexToPrefixedString.
func PrefixedHexStringToBytes(input string) ([]byte, error) {
if input[0:2] == "0x" {
input = input[2:]
}
return hex.DecodeString(input)
}
type ipaproofMarshaller struct {
CL [IPA_PROOF_DEPTH]string `json:"cl"`
CR [IPA_PROOF_DEPTH]string `json:"cr"`
FinalEvaluation string `json:"finalEvaluation"`
}
func (ipp *IPAProof) MarshalJSON() ([]byte, error) {
return json.Marshal(&ipaproofMarshaller{
CL: [IPA_PROOF_DEPTH]string{
HexToPrefixedString(ipp.CL[0][:]),
HexToPrefixedString(ipp.CL[1][:]),
HexToPrefixedString(ipp.CL[2][:]),
HexToPrefixedString(ipp.CL[3][:]),
HexToPrefixedString(ipp.CL[4][:]),
HexToPrefixedString(ipp.CL[5][:]),
HexToPrefixedString(ipp.CL[6][:]),
HexToPrefixedString(ipp.CL[7][:]),
},
CR: [IPA_PROOF_DEPTH]string{
HexToPrefixedString(ipp.CR[0][:]),
HexToPrefixedString(ipp.CR[1][:]),
HexToPrefixedString(ipp.CR[2][:]),
HexToPrefixedString(ipp.CR[3][:]),
HexToPrefixedString(ipp.CR[4][:]),
HexToPrefixedString(ipp.CR[5][:]),
HexToPrefixedString(ipp.CR[6][:]),
HexToPrefixedString(ipp.CR[7][:]),
},
FinalEvaluation: HexToPrefixedString(ipp.FinalEvaluation[:]),
})
}
func (ipp *IPAProof) UnmarshalJSON(data []byte) error {
aux := &ipaproofMarshaller{}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
if len(aux.FinalEvaluation) != 64 && len(aux.FinalEvaluation) != 66 {
return fmt.Errorf("invalid hex string for final evaluation: %s", aux.FinalEvaluation)
}
currentValueBytes, err := PrefixedHexStringToBytes(aux.FinalEvaluation)
if err != nil {
return fmt.Errorf("error decoding hex string for current value: %v", err)
}
copy(ipp.FinalEvaluation[:], currentValueBytes)
for i := range ipp.CL {
if len(aux.CL[i]) != 64 && len(aux.CL[i]) != 66 {
return fmt.Errorf("invalid hex string for CL[%d]: %s", i, aux.CL[i])
}
val, err := PrefixedHexStringToBytes(aux.CL[i])
if err != nil {
return fmt.Errorf("error decoding hex string for CL[%d]: %s", i, aux.CL[i])
}
copy(ipp.CL[i][:], val)
if len(aux.CR[i]) != 64 && len(aux.CR[i]) != 66 {
return fmt.Errorf("invalid hex string for CR[%d]: %s", i, aux.CR[i])
}
val, err = PrefixedHexStringToBytes(aux.CR[i])
if err != nil {
return fmt.Errorf("error decoding hex string for CR[%d]: %s", i, aux.CR[i])
}
copy(ipp.CR[i][:], val)
}
copy(ipp.FinalEvaluation[:], currentValueBytes)
return nil
}
type verkleProofMarshaller struct {
OtherStems []string `json:"otherStems"`
DepthExtensionPresent string `json:"depthExtensionPresent"`
CommitmentsByPath []string `json:"commitmentsByPath"`
D string `json:"d"`
IPAProof *IPAProof `json:"ipaProof"`
}
func (vp *VerkleProof) MarshalJSON() ([]byte, error) {
aux := &verkleProofMarshaller{
OtherStems: make([]string, len(vp.OtherStems)),
DepthExtensionPresent: HexToPrefixedString(vp.DepthExtensionPresent),
CommitmentsByPath: make([]string, len(vp.CommitmentsByPath)),
D: HexToPrefixedString(vp.D[:]),
IPAProof: vp.IPAProof,
}
for i, s := range vp.OtherStems {
aux.OtherStems[i] = HexToPrefixedString(s[:])
}
for i, c := range vp.CommitmentsByPath {
aux.CommitmentsByPath[i] = HexToPrefixedString(c[:])
}
return json.Marshal(aux)
}
func (vp *VerkleProof) UnmarshalJSON(data []byte) error {
var aux verkleProofMarshaller
err := json.Unmarshal(data, &aux)
if err != nil {
return fmt.Errorf("verkle proof unmarshal error: %w", err)
}
vp.DepthExtensionPresent, err = PrefixedHexStringToBytes(aux.DepthExtensionPresent)
if err != nil {
return fmt.Errorf("error decoding hex string for depth and extension present: %v", err)
}
vp.CommitmentsByPath = make([][32]byte, len(aux.CommitmentsByPath))
for i, c := range aux.CommitmentsByPath {
val, err := PrefixedHexStringToBytes(c)
if err != nil {
return fmt.Errorf("error decoding hex string for commitment #%d: %w", i, err)
}
copy(vp.CommitmentsByPath[i][:], val)
}
currentValueBytes, err := PrefixedHexStringToBytes(aux.D)
if err != nil {
return fmt.Errorf("error decoding hex string for D: %w", err)
}
copy(vp.D[:], currentValueBytes)
vp.OtherStems = make([][StemSize]byte, len(aux.OtherStems))
for i, c := range aux.OtherStems {
val, err := PrefixedHexStringToBytes(c)
if err != nil {
return fmt.Errorf("error decoding hex string for other stem #%d: %w", i, err)
}
copy(vp.OtherStems[i][:], val)
}
vp.IPAProof = aux.IPAProof
return nil
}
type stemStateDiffMarshaller struct {
Stem string `json:"stem"`
SuffixDiffs SuffixStateDiffs `json:"suffixDiffs"`
}
func (ssd StemStateDiff) MarshalJSON() ([]byte, error) {
return json.Marshal(&stemStateDiffMarshaller{
Stem: HexToPrefixedString(ssd.Stem[:]),
SuffixDiffs: ssd.SuffixDiffs,
})
}
func (ssd *StemStateDiff) UnmarshalJSON(data []byte) error {
var aux stemStateDiffMarshaller
if err := json.Unmarshal(data, &aux); err != nil {
return fmt.Errorf("stemdiff unmarshal error: %w", err)
}
stem, err := PrefixedHexStringToBytes(aux.Stem)
if err != nil {
return fmt.Errorf("invalid hex string for stem: %w", err)
}
*ssd = StemStateDiff{
SuffixDiffs: aux.SuffixDiffs,
}
copy(ssd.Stem[:], stem)
return nil
}
type suffixStateDiffMarshaller struct {
Suffix byte `json:"suffix"`
CurrentValue *string `json:"currentValue"`
NewValue *string `json:"newValue"`
}
func (ssd SuffixStateDiff) MarshalJSON() ([]byte, error) {
var cvstr, nvstr *string
if ssd.CurrentValue != nil {
tempstr := HexToPrefixedString(ssd.CurrentValue[:])
cvstr = &tempstr
}
if ssd.NewValue != nil {
tempstr := HexToPrefixedString(ssd.NewValue[:])
nvstr = &tempstr
}
return json.Marshal(&suffixStateDiffMarshaller{
Suffix: ssd.Suffix,
CurrentValue: cvstr,
NewValue: nvstr,
})
}
func (ssd *SuffixStateDiff) UnmarshalJSON(data []byte) error {
aux := &suffixStateDiffMarshaller{}
if err := json.Unmarshal(data, &aux); err != nil {
return fmt.Errorf("suffix diff unmarshal error: %w", err)
}
if aux.CurrentValue != nil && len(*aux.CurrentValue) != 64 && len(*aux.CurrentValue) != 0 && len(*aux.CurrentValue) != 66 {
return fmt.Errorf("invalid hex string for current value: %s", *aux.CurrentValue)
}
*ssd = SuffixStateDiff{
Suffix: aux.Suffix,
}
if aux.CurrentValue != nil && len(*aux.CurrentValue) != 0 {
currentValueBytes, err := PrefixedHexStringToBytes(*aux.CurrentValue)
if err != nil {
return fmt.Errorf("error decoding hex string for current value: %v", err)
}
ssd.CurrentValue = &[32]byte{}
copy(ssd.CurrentValue[:], currentValueBytes)
}
if aux.NewValue != nil && len(*aux.NewValue) != 0 {
newValueBytes, err := PrefixedHexStringToBytes(*aux.NewValue)
if err != nil {
return fmt.Errorf("error decoding hex string for current value: %v", err)
}
ssd.NewValue = &[32]byte{}
copy(ssd.NewValue[:], newValueBytes)
}
return nil
}
+24
View File
@@ -0,0 +1,24 @@
package verkle
import (
"encoding/json"
"testing"
)
func TestJSONDeserialization(t *testing.T) {
str := `{
"stem": "0x97233a822ee74c294ccec8e4e0c65106b374d4423d5d09236d0f6c6647e185",
"suffixDiffs": [
{ "suffix": 0, "currentValue": null, "newValue": null },
{ "suffix": 1, "currentValue": null, "newValue": null },
{ "suffix": 2, "currentValue": null, "newValue": null },
{ "suffix": 3, "currentValue": null, "newValue": null },
{ "suffix": 4, "currentValue": null, "newValue": null }
]
}`
var statediff StemStateDiff
err := json.Unmarshal([]byte(str), &statediff)
if err != nil {
t.Fatal(err)
}
}
+1248
View File
File diff suppressed because it is too large Load Diff
+1922
View File
File diff suppressed because it is too large Load Diff
+320
View File
@@ -0,0 +1,320 @@
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import (
"encoding/hex"
"fmt"
"strconv"
"testing"
"time"
"github.com/crate-crypto/go-ipa/banderwagon"
)
var identity *Point
func init() {
var id Point
id.SetIdentity()
identity = &id
}
func extensionAndSuffixOneKey(t *testing.T, key, value []byte, ret *Point) {
var (
v Fr
vs [2]Fr
cfg = GetConfig()
srs = cfg.conf.SRS
stemComm1, stemComm3, stemComm2 Point
t1, t2, c1 Point
)
stemComm0 := srs[0]
err := StemFromLEBytes(&v, KeyToStem(key))
if err != nil {
panic(err)
}
stemComm1.ScalarMul(&srs[1], &v)
if err := leafToComms(vs[:], value); err != nil {
t.Fatalf("leafToComms failed: %s", err)
}
c1.Add(t1.ScalarMul(&srs[2*key[StemSize]], &vs[0]), t2.ScalarMul(&srs[2*key[StemSize]+1], &vs[1]))
c1.MapToScalarField(&v)
stemComm2.ScalarMul(&srs[2], &v)
v.SetZero()
stemComm3.ScalarMul(&srs[3], &v)
t1.Add(&stemComm0, &stemComm1)
t2.Add(&stemComm2, &stemComm3)
ret.Add(&t1, &t2)
}
func TestInsertKey0Value0(t *testing.T) {
t.Parallel()
var (
expected Fr
root = New()
expectedP Point
cfg = GetConfig()
srs = cfg.conf.SRS
)
if err := root.Insert(zeroKeyTest, zeroKeyTest, nil); err != nil {
t.Fatalf("insert failed: %s", err)
}
comm := root.Commit()
extensionAndSuffixOneKey(t, zeroKeyTest, zeroKeyTest, &expectedP)
if expectedP.Equal(identity) {
t.Fatal("commitment is identity")
}
expectedP.MapToScalarField(&expected)
expectedP.ScalarMul(&srs[0], &expected)
if !comm.Equal(&expectedP) {
t.Fatalf("invalid root commitment %v != %v", comm, &expected)
}
}
func TestInsertKey1Value1(t *testing.T) {
t.Parallel()
var (
v, expected Fr
root = New()
expectedP Point
cfg = GetConfig()
srs = cfg.conf.SRS
)
key := []byte{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
}
if err := root.Insert(key, key, nil); err != nil {
t.Fatalf("insert failed: %s", err)
}
comm := root.Commit()
extensionAndSuffixOneKey(t, key, key, &expectedP)
expectedP.MapToScalarField(&v)
expectedP.ScalarMul(&srs[1], &v)
if expectedP.Equal(identity) {
t.Fatal("commitment is identity")
}
if !comm.Equal(&expectedP) {
t.Fatalf("invalid root commitment %v != %v", comm, &expected)
}
}
func TestInsertSameStemTwoLeaves(t *testing.T) {
t.Parallel()
var (
v, expected Fr
vs [2]Fr
root = New()
expectedP, c1, c2, t1, t2 Point
stemComm1, stemComm3, stemComm2 Point
cfg = GetConfig()
srs = cfg.conf.SRS
)
key_a := []byte{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32,
}
key_b := []byte{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 128,
}
if err := root.Insert(key_a, key_a, nil); err != nil {
t.Fatalf("insert failed: %s", err)
}
if err := root.Insert(key_b, key_b, nil); err != nil {
t.Fatalf("insert failed: %s", err)
}
comm := root.Commit()
stemComm0 := srs[0]
err := StemFromLEBytes(&v, KeyToStem(key_a))
if err != nil {
t.Fatal(err)
}
stemComm1.ScalarMul(&srs[1], &v)
if err := leafToComms(vs[:], key_a); err != nil {
t.Fatalf("leafToComms failed: %s", err)
}
c1.Add(t1.ScalarMul(&srs[64], &vs[0]), t2.ScalarMul(&srs[65], &vs[1]))
c1.MapToScalarField(&v)
stemComm2.ScalarMul(&srs[2], &v)
if err := leafToComms(vs[:], key_b); err != nil {
t.Fatalf("leafToComms failed: %s", err)
}
c2.Add(t1.ScalarMul(&srs[0], &vs[0]), t2.ScalarMul(&srs[1], &vs[1]))
c2.MapToScalarField(&v)
stemComm3.ScalarMul(&srs[3], &v)
t1.Add(&stemComm0, &stemComm1)
t2.Add(&stemComm2, &stemComm3)
expectedP.Add(&t1, &t2)
expectedP.MapToScalarField(&v)
expectedP.ScalarMul(&srs[1], &v)
if expectedP.Equal(identity) {
t.Fatal("commitment is identity")
}
if !comm.Equal(&expectedP) {
t.Fatalf("invalid root commitment %v != %v", comm, &expected)
}
}
func TestInsertKey1Val1Key2Val2(t *testing.T) {
t.Parallel()
var (
v, v1, v2, expected Fr
root = New()
expectedP, t1, t2 Point
cfg = GetConfig()
srs = cfg.conf.SRS
)
key_b, _ := hex.DecodeString("0101010101010101010101010101010101010101010101010101010101010101")
if err := root.Insert(zeroKeyTest, zeroKeyTest, nil); err != nil {
t.Fatalf("insert failed: %s", err)
}
if err := root.Insert(key_b, key_b, nil); err != nil {
t.Fatalf("insert failed: %s", err)
}
comm := root.Commit()
fmt.Println(root.toDot("", ""))
extensionAndSuffixOneKey(t, zeroKeyTest, zeroKeyTest, &t1)
t1.MapToScalarField(&v1)
extensionAndSuffixOneKey(t, key_b, key_b, &t2)
t2.MapToScalarField(&v2)
expectedP.Add(t1.ScalarMul(&srs[0], &v1), t2.ScalarMul(&srs[1], &v2))
expectedP.MapToScalarField(&v)
if expectedP.Equal(identity) {
t.Fatal("commitment is identity")
}
if !comm.Equal(&expectedP) {
t.Fatalf("invalid root commitment %v != %v", comm, &expected)
}
}
func TestEmptyTrie(t *testing.T) {
t.Parallel()
root := New()
comm := root.Commit()
if !comm.Equal(identity) {
t.Fatalf("invalid root commitment %v != %v", comm, identity)
}
}
func TestGroupToField(t *testing.T) {
t.Parallel()
point := banderwagon.Generator
var v Fr
point.MapToScalarField(&v)
bytes := v.BytesLE()
hexStr := hex.EncodeToString(bytes[:])
if hexStr != "d1e7de2aaea9603d5bc6c208d319596376556ecd8336671ba7670c2139772d14" {
t.Fatalf("group to field not working")
}
}
func BenchmarkGroupToField(b *testing.B) {
b.Run("single", func(b *testing.B) {
point := banderwagon.Generator
var v Fr
for i := 0; i < b.N; i++ {
point.MapToScalarField(&v)
}
})
b.Run("multiple", func(b *testing.B) {
for i := 1; i <= 256; i *= 2 {
b.Run(strconv.Itoa(i), func(b *testing.B) {
// Generate `i` ~distinct points
points := make([]*Point, i)
points[0] = &banderwagon.Generator
for k := 1; k < i; k++ {
points[k] = &Point{}
points[k].Add(points[k-1], &banderwagon.Generator)
}
sink := make([]Fr, i)
ptrs := make([]*Fr, i)
for i := range sink {
ptrs[i] = &sink[i]
}
now := time.Now()
b.ReportAllocs()
b.ResetTimer()
for k := 0; k < b.N; k++ {
if err := banderwagon.BatchMapToScalarField(ptrs, points); err != nil {
b.Fatal(err)
}
}
b.ReportMetric(float64(time.Since(now).Nanoseconds()/int64(i))/float64(b.N), "ns/value")
_ = sink
})
}
})
}
func TestPaddingInFromLEBytes(t *testing.T) {
t.Parallel()
var fr1, fr2 Fr
if err := FromLEBytes(&fr1, ffx32KeyTest[:16]); err != nil {
t.Fatal(err)
}
key, _ := hex.DecodeString("ffffffffffffffffffffffffffffffff00000000000000000000000000000000")
err := StemFromLEBytes(&fr2, KeyToStem(key))
if err != nil {
t.Fatal(err)
}
if !fr1.Equal(&fr2) {
t.Fatal("byte alignment")
}
}
+1882
View File
File diff suppressed because it is too large Load Diff
+86
View File
@@ -0,0 +1,86 @@
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
//
// Vendored from github.com/ethereum/go-verkle@v0.2.2 (public domain / Unlicense).
// Re-licensed under the Lux Ecosystem License. The package implements the
// Verkle trie, its node types (InternalNode, LeafNode, Empty, HashedNode),
// IPA configuration, multi-proof generation/verification, encoding, and
// JSON serialization. Algorithmic logic mirrors upstream; downstream callers
// (luxfi/geth, luxfi/node, luxfi/evm) consume this package directly under
// the github.com/luxfi/crypto/verkle import path.
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <https://unlicense.org>
package verkle
import "errors"
type UnknownNode struct{}
func (UnknownNode) Insert([]byte, []byte, NodeResolverFn) error {
return errMissingNodeInStateless
}
func (UnknownNode) Delete([]byte, NodeResolverFn) (bool, error) {
return false, errors.New("cant delete in a subtree missing form a stateless view")
}
func (UnknownNode) Get([]byte, NodeResolverFn) ([]byte, error) {
return nil, nil
}
func (n UnknownNode) Commit() *Point {
return n.Commitment()
}
func (UnknownNode) Commitment() *Point {
var id Point
id.SetIdentity()
return &id
}
func (UnknownNode) GetProofItems(keylist, NodeResolverFn) (*ProofElements, []byte, []Stem, error) {
return nil, nil, nil, errors.New("can't generate proof items for unknown node")
}
func (UnknownNode) Serialize() ([]byte, error) {
return nil, errors.New("trying to serialize a subtree missing from the statless view")
}
func (UnknownNode) Copy() VerkleNode {
return UnknownNode(struct{}{})
}
func (UnknownNode) toDot(string, string) string {
return ""
}
func (UnknownNode) setDepth(_ byte) {
panic("should not be try to set the depth of an UnknownNode node")
}
func (UnknownNode) Hash() *Fr {
return &FrZero
}
+42
View File
@@ -0,0 +1,42 @@
package verkle
import "testing"
func TestUnknownFuncs(t *testing.T) {
t.Parallel()
un := UnknownNode{}
if err := un.Insert(nil, nil, nil); err != errMissingNodeInStateless {
t.Errorf("got %v, want %v", err, errMissingNodeInStateless)
}
if _, err := un.Delete(nil, nil); err == nil {
t.Errorf("got nil error when deleting from a hashed node")
}
if _, err := un.Get(nil, nil); err != nil {
t.Errorf("got %v, want nil", err)
}
var identity Point
identity.SetIdentity()
if comm := un.Commit(); !comm.Equal(&identity) {
t.Errorf("got %v, want identity", comm)
}
if comm := un.Commitment(); !comm.Equal(&identity) {
t.Errorf("got %v, want identity", comm)
}
if _, _, _, err := un.GetProofItems(nil, nil); err == nil {
t.Errorf("got nil error when getting proof items from a hashed node")
}
if _, err := un.Serialize(); err == nil {
t.Errorf("got nil error when serializing a hashed node")
}
if un != un.Copy() {
t.Errorf("copy returned a different node")
}
if un.toDot("", "") != "" {
t.Errorf("toDot returned a non-empty string")
}
if !un.Hash().Equal(&FrZero) {
t.Errorf("hash returned non-zero")
}
}
-49
View File
@@ -1,49 +0,0 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
//
// Package verkle re-exports the go-verkle library under the luxfi/crypto
// namespace so that downstream packages (luxfi/geth, luxfi/node, etc.)
// never import ethereum/* directly.
package verkle
import (
upstream "github.com/ethereum/go-verkle"
)
// --- Type aliases ---
type (
VerkleNode = upstream.VerkleNode
InternalNode = upstream.InternalNode
LeafNode = upstream.LeafNode
Empty = upstream.Empty
Point = upstream.Point
Fr = upstream.Fr
VerkleProof = upstream.VerkleProof
StateDiff = upstream.StateDiff
NodeResolverFn = upstream.NodeResolverFn
IPAConfig = upstream.IPAConfig
Proof = upstream.Proof
)
// --- Constants ---
const NodeWidth = upstream.NodeWidth
// --- Functions ---
var (
ParseNode = upstream.ParseNode
New = upstream.New
ToDot = upstream.ToDot
GetConfig = upstream.GetConfig
MakeVerkleMultiProof = upstream.MakeVerkleMultiProof
SerializeProof = upstream.SerializeProof
HashPointToBytes = upstream.HashPointToBytes
FromLEBytes = upstream.FromLEBytes
FromBytes = upstream.FromBytes
DeserializeProof = upstream.DeserializeProof
MergeTrees = upstream.MergeTrees
BatchNewLeafNode = upstream.BatchNewLeafNode
PreStateTreeFromProof = upstream.PreStateTreeFromProof
)