fix(consensus): reject unknown tx variants
Some checks are pending
Security Scan / security (push) Waiting to run
Test / Test (push) Waiting to run

Tighten semantic validation so unknown transaction inputs, outputs, and bare output targets are rejected explicitly instead of falling through the default case.\n\nCo-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
Virgil 2026-04-04 15:55:09 +00:00
parent 2729827f5b
commit 3a0d49401f
2 changed files with 48 additions and 2 deletions

View file

@ -119,11 +119,12 @@ func checkInputTypes(tx *types.Transaction, hf1Active, hf4Active bool) error {
if !hf1Active {
return coreerr.E("checkInputTypes", fmt.Sprintf("tag %d pre-HF1", vin.InputType()), ErrInvalidInputType)
}
default:
// Future types (ZC) — accept if HF4+.
case types.TxInputZC:
if !hf4Active {
return coreerr.E("checkInputTypes", fmt.Sprintf("tag %d pre-HF4", vin.InputType()), ErrInvalidInputType)
}
default:
return coreerr.E("checkInputTypes", fmt.Sprintf("unsupported input type %T", vin), ErrInvalidInputType)
}
}
return nil
@ -150,13 +151,18 @@ func checkOutputs(tx *types.Transaction, hf1Active, hf4Active bool) error {
}
// HTLC and Multisig output targets require at least HF1.
switch o.Target.(type) {
case types.TxOutToKey:
case types.TxOutHTLC, types.TxOutMultisig:
if !hf1Active {
return coreerr.E("checkOutputs", fmt.Sprintf("output %d: HTLC/multisig target pre-HF1", i), ErrInvalidOutput)
}
default:
return coreerr.E("checkOutputs", fmt.Sprintf("output %d: unsupported target type %T", i, o.Target), ErrInvalidOutput)
}
case types.TxOutputZarcanum:
// Validated by proof verification.
default:
return coreerr.E("checkOutputs", fmt.Sprintf("unsupported output type %T", vout), ErrInvalidOutput)
}
}

View file

@ -16,6 +16,18 @@ import (
"github.com/stretchr/testify/require"
)
type unknownTxInput struct{}
func (unknownTxInput) InputType() uint8 { return 99 }
type unknownTxOutput struct{}
func (unknownTxOutput) OutputType() uint8 { return 99 }
type unknownTxOutputTarget struct{}
func (unknownTxOutputTarget) TargetType() uint8 { return 99 }
// validV1Tx returns a minimal valid v1 transaction for testing.
func validV1Tx() *types.Transaction {
return &types.Transaction{
@ -73,6 +85,14 @@ func TestValidateTransaction_InvalidInputType(t *testing.T) {
assert.ErrorIs(t, err, ErrInvalidInputType)
}
func TestValidateTransaction_UnknownInputType(t *testing.T) {
tx := validV1Tx()
tx.Vin = []types.TxInput{unknownTxInput{}}
blob := make([]byte, 100)
err := ValidateTransaction(tx, blob, config.MainnetForks, 5000)
assert.ErrorIs(t, err, ErrInvalidInputType)
}
func TestValidateTransaction_NoOutputs(t *testing.T) {
tx := validV1Tx()
tx.Vout = nil
@ -102,6 +122,24 @@ func TestValidateTransaction_ZeroOutputAmount(t *testing.T) {
assert.ErrorIs(t, err, ErrInvalidOutput)
}
func TestValidateTransaction_UnsupportedOutputType(t *testing.T) {
tx := validV1Tx()
tx.Vout = []types.TxOutput{unknownTxOutput{}}
blob := make([]byte, 100)
err := ValidateTransaction(tx, blob, config.MainnetForks, 5000)
assert.ErrorIs(t, err, ErrInvalidOutput)
}
func TestValidateTransaction_UnsupportedBareTargetType(t *testing.T) {
tx := validV1Tx()
tx.Vout = []types.TxOutput{
types.TxOutputBare{Amount: 90, Target: unknownTxOutputTarget{}},
}
blob := make([]byte, 100)
err := ValidateTransaction(tx, blob, config.MainnetForks, 5000)
assert.ErrorIs(t, err, ErrInvalidOutput)
}
func TestValidateTransaction_DuplicateKeyImage(t *testing.T) {
ki := types.KeyImage{42}
tx := &types.Transaction{
@ -238,6 +276,8 @@ func TestCheckOutputs_MultisigTargetPostHF1_Good(t *testing.T) {
require.NoError(t, err)
}
type unknownTxOutputTarget struct{}
// --- Key image tests for HTLC (Task 8) ---
func TestCheckKeyImages_HTLCDuplicate_Bad(t *testing.T) {