go-blockchain/wallet/extra_test.go
Claude ee257baa83
feat(wallet): TX extra parsing for wallet-critical tags
Parses tags 22 (tx public key), 14 (unlock time), and 11 (derivation
hint) from raw extra bytes. Unknown tags are skipped. Raw bytes preserved
for round-tripping.

Adds PublicKey.IsZero() to types package.

Co-Authored-By: Charon <charon@lethean.io>
2026-02-20 23:02:13 +00:00

183 lines
4.4 KiB
Go

// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2
package wallet
import (
"testing"
"forge.lthn.ai/core/go-blockchain/types"
"forge.lthn.ai/core/go-blockchain/wire"
)
func TestParseTxExtraPublicKey(t *testing.T) {
var key types.PublicKey
for i := range key {
key[i] = byte(i + 1)
}
raw := buildTestExtra(key, 0, 0)
extra, err := ParseTxExtra(raw)
if err != nil {
t.Fatal(err)
}
if extra.TxPublicKey != key {
t.Fatalf("tx public key mismatch: got %x", extra.TxPublicKey)
}
}
func TestParseTxExtraUnlockTime(t *testing.T) {
var key types.PublicKey
key[0] = 0xAA
raw := buildTestExtra(key, 500, 0)
extra, err := ParseTxExtra(raw)
if err != nil {
t.Fatal(err)
}
if extra.UnlockTime != 500 {
t.Fatalf("unlock_time = %d, want 500", extra.UnlockTime)
}
}
func TestParseTxExtraDerivationHint(t *testing.T) {
var key types.PublicKey
key[0] = 0xBB
raw := buildTestExtra(key, 0, 0x1234)
extra, err := ParseTxExtra(raw)
if err != nil {
t.Fatal(err)
}
if extra.DerivationHint != 0x1234 {
t.Fatalf("derivation_hint = %04x, want 1234", extra.DerivationHint)
}
}
func TestParseTxExtraAllFields(t *testing.T) {
var key types.PublicKey
for i := range key {
key[i] = byte(0xFF - i)
}
raw := buildTestExtra(key, 1000, 0xABCD)
extra, err := ParseTxExtra(raw)
if err != nil {
t.Fatal(err)
}
if extra.TxPublicKey != key {
t.Fatalf("tx public key mismatch: got %x", extra.TxPublicKey)
}
if extra.UnlockTime != 1000 {
t.Fatalf("unlock_time = %d, want 1000", extra.UnlockTime)
}
if extra.DerivationHint != 0xABCD {
t.Fatalf("derivation_hint = %04x, want ABCD", extra.DerivationHint)
}
}
func TestParseTxExtraEmpty(t *testing.T) {
raw := wire.EncodeVarint(0)
extra, err := ParseTxExtra(raw)
if err != nil {
t.Fatal(err)
}
if !extra.TxPublicKey.IsZero() {
t.Fatal("expected zero tx public key for empty extra")
}
if extra.UnlockTime != 0 {
t.Fatalf("unlock_time = %d, want 0", extra.UnlockTime)
}
if extra.DerivationHint != 0 {
t.Fatalf("derivation_hint = %d, want 0", extra.DerivationHint)
}
}
func TestParseTxExtraNilInput(t *testing.T) {
extra, err := ParseTxExtra(nil)
if err != nil {
t.Fatal(err)
}
if !extra.TxPublicKey.IsZero() {
t.Fatal("expected zero tx public key for nil input")
}
}
func TestParseTxExtraPreservesRaw(t *testing.T) {
var key types.PublicKey
key[0] = 0xCC
raw := buildTestExtra(key, 100, 0x5678)
extra, err := ParseTxExtra(raw)
if err != nil {
t.Fatal(err)
}
if len(extra.Raw) != len(raw) {
t.Fatalf("raw length = %d, want %d", len(extra.Raw), len(raw))
}
// Verify it is a copy, not a reference to the same backing array.
raw[0] = 0xFF
if extra.Raw[0] == 0xFF {
t.Fatal("Raw should be a defensive copy, not a reference")
}
}
func TestParseTxExtraTruncatedKey(t *testing.T) {
// Build a raw extra with a public key tag but only 16 bytes instead of 32.
raw := wire.EncodeVarint(1)
raw = append(raw, extraTagPublicKey)
raw = append(raw, make([]byte, 16)...) // only 16 bytes
_, err := ParseTxExtra(raw)
if err == nil {
t.Fatal("expected error for truncated public key")
}
}
func TestBuildTxExtraRoundTrip(t *testing.T) {
var key types.PublicKey
for i := range key {
key[i] = byte(i + 10)
}
raw := BuildTxExtra(key)
extra, err := ParseTxExtra(raw)
if err != nil {
t.Fatal(err)
}
if extra.TxPublicKey != key {
t.Fatal("round-trip key mismatch")
}
}
func TestBuildTxExtraLength(t *testing.T) {
var key types.PublicKey
key[0] = 0x42
raw := BuildTxExtra(key)
// Expected: varint(1) + tag(1) + key(32) = 34 bytes.
if len(raw) != 34 {
t.Fatalf("BuildTxExtra length = %d, want 34", len(raw))
}
}
// buildTestExtra constructs a raw extra with the given fields.
func buildTestExtra(txPubKey types.PublicKey, unlockTime uint64, hint uint16) []byte {
var count uint64
var elements []byte
if !txPubKey.IsZero() {
count++
elements = append(elements, extraTagPublicKey)
elements = append(elements, txPubKey[:]...)
}
if unlockTime > 0 {
count++
elements = append(elements, extraTagUnlockTime)
elements = append(elements, wire.EncodeVarint(unlockTime)...)
}
if hint > 0 {
count++
elements = append(elements, extraTagDerivationHint)
elements = append(elements, wire.EncodeVarint(2)...)
elements = append(elements, byte(hint&0xFF), byte(hint>>8))
}
raw := wire.EncodeVarint(count)
raw = append(raw, elements...)
return raw
}