fix(wire): reject unsupported transaction variants
Add explicit errors for unknown input/output variants in the wire encoder and tighten transparent output validation in consensus. Cover the new failure paths with unit tests. Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
d2caf68d94
commit
be99c5e93a
4 changed files with 109 additions and 1 deletions
|
|
@ -144,15 +144,22 @@ func checkOutputs(tx *types.Transaction, hf1Active, hf4Active bool) error {
|
|||
if o.Amount == 0 {
|
||||
return coreerr.E("checkOutputs", fmt.Sprintf("output %d has zero amount", i), ErrInvalidOutput)
|
||||
}
|
||||
// HTLC and Multisig output targets require at least HF1.
|
||||
// Only known transparent output targets are accepted.
|
||||
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)
|
||||
}
|
||||
case nil:
|
||||
return coreerr.E("checkOutputs", fmt.Sprintf("output %d: missing target", i), ErrInvalidOutput)
|
||||
default:
|
||||
return coreerr.E("checkOutputs", fmt.Sprintf("output %d: unsupported target %T", i, o.Target), ErrInvalidOutput)
|
||||
}
|
||||
case types.TxOutputZarcanum:
|
||||
// Validated by proof verification.
|
||||
default:
|
||||
return coreerr.E("checkOutputs", fmt.Sprintf("output %d: unsupported output type %T", i, vout), ErrInvalidOutput)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ func validV1Tx() *types.Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
type unsupportedTxOutTarget struct{}
|
||||
|
||||
func (unsupportedTxOutTarget) TargetType() uint8 { return 250 }
|
||||
|
||||
func TestValidateTransaction_Good(t *testing.T) {
|
||||
tx := validV1Tx()
|
||||
blob := make([]byte, 100) // small blob
|
||||
|
|
@ -238,6 +242,36 @@ func TestCheckOutputs_MultisigTargetPostHF1_Good(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCheckOutputs_MissingTarget_Bad(t *testing.T) {
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{
|
||||
types.TxInputToKey{Amount: 100, KeyImage: types.KeyImage{1}},
|
||||
},
|
||||
Vout: []types.TxOutput{
|
||||
types.TxOutputBare{Amount: 90, Target: nil},
|
||||
},
|
||||
}
|
||||
blob := make([]byte, 100)
|
||||
err := ValidateTransaction(tx, blob, config.MainnetForks, 20000)
|
||||
assert.ErrorIs(t, err, ErrInvalidOutput)
|
||||
}
|
||||
|
||||
func TestCheckOutputs_UnsupportedTarget_Bad(t *testing.T) {
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{
|
||||
types.TxInputToKey{Amount: 100, KeyImage: types.KeyImage{1}},
|
||||
},
|
||||
Vout: []types.TxOutput{
|
||||
types.TxOutputBare{Amount: 90, Target: unsupportedTxOutTarget{}},
|
||||
},
|
||||
}
|
||||
blob := make([]byte, 100)
|
||||
err := ValidateTransaction(tx, blob, config.MainnetForks, 20000)
|
||||
assert.ErrorIs(t, err, ErrInvalidOutput)
|
||||
}
|
||||
|
||||
// --- Key image tests for HTLC (Task 8) ---
|
||||
|
||||
func TestCheckKeyImages_HTLCDuplicate_Bad(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -176,6 +176,9 @@ func encodeInputs(enc *Encoder, vin []types.TxInput) {
|
|||
encodeKeyOffsets(enc, v.KeyOffsets)
|
||||
enc.WriteBlob32((*[32]byte)(&v.KeyImage))
|
||||
enc.WriteBytes(v.EtcDetails)
|
||||
default:
|
||||
enc.err = coreerr.E("encodeInputs", fmt.Sprintf("wire: unsupported input type %T", in), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -298,6 +301,9 @@ func encodeOutputsV1(enc *Encoder, vout []types.TxOutput) {
|
|||
enc.WriteVarint(t.Expiration)
|
||||
enc.WriteBlob32((*[32]byte)(&t.PKRedeem))
|
||||
enc.WriteBlob32((*[32]byte)(&t.PKRefund))
|
||||
default:
|
||||
enc.err = coreerr.E("encodeOutputsV1", fmt.Sprintf("wire: unsupported output target type %T", v.Target), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -375,6 +381,9 @@ func encodeOutputsV2(enc *Encoder, vout []types.TxOutput) {
|
|||
enc.WriteVarint(t.Expiration)
|
||||
enc.WriteBlob32((*[32]byte)(&t.PKRedeem))
|
||||
enc.WriteBlob32((*[32]byte)(&t.PKRefund))
|
||||
default:
|
||||
enc.err = coreerr.E("encodeOutputsV2", fmt.Sprintf("wire: unsupported output target type %T", v.Target), nil)
|
||||
return
|
||||
}
|
||||
case types.TxOutputZarcanum:
|
||||
enc.WriteBlob32((*[32]byte)(&v.StealthAddress))
|
||||
|
|
|
|||
|
|
@ -987,3 +987,61 @@ func TestHTLCTargetV2RoundTrip_Good(t *testing.T) {
|
|||
t.Errorf("round-trip mismatch:\n got: %x\n want: %x", rtBuf.Bytes(), buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
type unsupportedTxInput struct{}
|
||||
|
||||
func (unsupportedTxInput) InputType() uint8 { return 250 }
|
||||
|
||||
type unsupportedTxOutTarget struct{}
|
||||
|
||||
func (unsupportedTxOutTarget) TargetType() uint8 { return 250 }
|
||||
|
||||
func TestEncodeTransaction_UnsupportedInput_Bad(t *testing.T) {
|
||||
tx := types.Transaction{
|
||||
Version: 1,
|
||||
Vin: []types.TxInput{unsupportedTxInput{}},
|
||||
Vout: []types.TxOutput{types.TxOutputBare{
|
||||
Amount: 1,
|
||||
Target: types.TxOutToKey{Key: types.PublicKey{1}},
|
||||
}},
|
||||
Extra: EncodeVarint(0),
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
EncodeTransactionPrefix(enc, &tx)
|
||||
if enc.Err() == nil {
|
||||
t.Fatal("expected encode error for unsupported input type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeTransaction_UnsupportedOutputTarget_Bad(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version uint64
|
||||
}{
|
||||
{name: "v1", version: types.VersionPreHF4},
|
||||
{name: "v2", version: types.VersionPostHF4},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tx := types.Transaction{
|
||||
Version: tt.version,
|
||||
Vin: []types.TxInput{types.TxInputGenesis{Height: 1}},
|
||||
Vout: []types.TxOutput{types.TxOutputBare{
|
||||
Amount: 1,
|
||||
Target: unsupportedTxOutTarget{},
|
||||
}},
|
||||
Extra: EncodeVarint(0),
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
enc := NewEncoder(&buf)
|
||||
EncodeTransactionPrefix(enc, &tx)
|
||||
if enc.Err() == nil {
|
||||
t.Fatal("expected encode error for unsupported output target type")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue