From caa81a55ee29840b88150392ebc779607f7acfb5 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Thu, 6 Nov 2025 11:42:12 +0000 Subject: [PATCH 1/2] fix global mutation issue --- encoding.go | 9 ++++++--- encoding_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/encoding.go b/encoding.go index c0c8c6be..d1863146 100644 --- a/encoding.go +++ b/encoding.go @@ -145,7 +145,8 @@ func parseEoAccountNode(serialized []byte, depth byte) (VerkleNode, error) { 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.c2 = new(Point) + *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) @@ -171,13 +172,15 @@ func parseSingleSlotNode(serialized []byte, depth byte) (VerkleNode, error) { if err := ln.c1.SetBytesUncompressed(cnCommBytes, true); err != nil { return nil, fmt.Errorf("error setting leaf C1 commitment: %w", err) } - ln.c2 = &banderwagon.Identity + ln.c2 = new(Point) + *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.c1 = new(Point) + *ln.c1 = banderwagon.Identity } ln.commitment = new(Point) if err := ln.commitment.SetBytesUncompressed(rootCommBytes, true); err != nil { diff --git a/encoding_test.go b/encoding_test.go index ef7d4ea3..da0e519b 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -191,3 +191,43 @@ func TestParseNodeSingleSlot(t *testing.T) { t.Fatalf("invalid commitment, got %x, expected %x", lnd.commitment, ln.commitment) } } + +func TestLeaf_FirstC2Write_DoesNotMutate_GlobalIdentity(t *testing.T) { + // Build a leaf with exactly one value in c1 (suffix 7), so Serialize() picks single-slot encoding. + values := make([][]byte, NodeWidth) + values[7] = word32(0x11) + + ln, err := NewLeafNode(ffx32KeyTest[:StemSize], values) + if err != nil { + t.Fatalf("NewLeafNode: %v", err) + } + ser, err := ln.Serialize() + if err != nil { + t.Fatalf("Serialize: %v", err) + } + if got := ser[0]; got != singleSlotType { + t.Fatalf("expected single-slot encoding, got type=%d", got) + } + + node, err := ParseNode(ser, 0) + if err != nil { + t.Fatalf("ParseNode: %v", err) + } + leaf, ok := node.(*LeafNode) + if !ok { + t.Fatalf("expected *LeafNode, got %T", node) + } + + // Snapshot global identity (value copy) to compare later. + idBefore := banderwagon.Identity + + // First write into c2 (suffix 128). This must NOT mutate banderwagon.Identity. + val := word32(0x03) + if err := leaf.updateCn(128, val, leaf.c2); err != nil { + t.Fatalf("updateCn(c2): %v", err) + } + + if idBefore != banderwagon.Identity { + t.Fatalf("BUG: first c2 write mutated global banderwagon.Identity") + } +} From 05e9f38e7c103be44f947517819892cc9cce0807 Mon Sep 17 00:00:00 2001 From: Tudor Malene Date: Mon, 10 Nov 2025 09:23:43 +0000 Subject: [PATCH 2/2] add missing function --- encoding_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/encoding_test.go b/encoding_test.go index da0e519b..7f6ea263 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -231,3 +231,9 @@ func TestLeaf_FirstC2Write_DoesNotMutate_GlobalIdentity(t *testing.T) { t.Fatalf("BUG: first c2 write mutated global banderwagon.Identity") } } + +func word32(tag byte) []byte { + b := make([]byte, 32) + b[31] = tag + return b +}