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..7f6ea263 100644 --- a/encoding_test.go +++ b/encoding_test.go @@ -191,3 +191,49 @@ 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") + } +} + +func word32(tag byte) []byte { + b := make([]byte, 32) + b[31] = tag + return b +}