go-blockchain/consensus/v2sig_test.go
Virgil 3686a82b33
Some checks are pending
Security Scan / security (push) Waiting to run
Test / Test (push) Waiting to run
fix(consensus): validate HTLC tags in v2 signatures
Co-Authored-By: Charon <charon@lethean.io>
2026-04-04 20:32:39 +00:00

191 lines
6 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 consensus
import (
"bytes"
"encoding/hex"
"os"
"testing"
"dappco.re/go/core/blockchain/config"
"dappco.re/go/core/blockchain/types"
"dappco.re/go/core/blockchain/wire"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func buildSingleZCSigRaw() []byte {
var buf bytes.Buffer
enc := wire.NewEncoder(&buf)
enc.WriteVarint(1)
enc.WriteUint8(types.SigTypeZC)
enc.WriteBytes(make([]byte, 64))
enc.WriteVarint(0)
enc.WriteVarint(0)
enc.WriteBytes(make([]byte, 64))
return buf.Bytes()
}
// loadTestTx loads and decodes a hex-encoded transaction from testdata.
func loadTestTx(t *testing.T, filename string) *types.Transaction {
t.Helper()
hexData, err := os.ReadFile(filename)
require.NoError(t, err, "read %s", filename)
blob, err := hex.DecodeString(string(bytes.TrimSpace(hexData)))
require.NoError(t, err, "decode hex")
dec := wire.NewDecoder(bytes.NewReader(blob))
tx := wire.DecodeTransaction(dec)
require.NoError(t, dec.Err(), "decode transaction")
return &tx
}
func TestParseV2Signatures_Mixin0(t *testing.T) {
tx := loadTestTx(t, "../testdata/v2_spending_tx_mixin0.hex")
require.Equal(t, uint64(3), tx.Version, "expected v3 transaction")
// Should have 1 ZC input.
require.Len(t, tx.Vin, 1)
_, ok := tx.Vin[0].(types.TxInputZC)
require.True(t, ok, "expected TxInputZC")
// Parse signatures.
entries, err := parseV2Signatures(tx.SignaturesRaw)
require.NoError(t, err)
require.Len(t, entries, 1, "should have 1 signature entry")
// Should be a ZC_sig.
assert.Equal(t, types.SigTypeZC, entries[0].tag)
require.NotNil(t, entries[0].zcSig)
zc := entries[0].zcSig
// Ring size should be 16 (mixin 0 + ZC default = 16).
assert.Equal(t, 16, zc.ringSize, "expected ring size 16")
// Flat sig size: c(32) + r_g[16*32] + r_x[16*32] + K1(32) + K2(32) = 1120.
expectedSigSize := 32 + 16*32 + 16*32 + 64
assert.Equal(t, expectedSigSize, len(zc.clsagFlatSig))
// Pseudo-out commitment and asset ID should be non-zero.
assert.NotEqual(t, [32]byte{}, zc.pseudoOutCommitment)
assert.NotEqual(t, [32]byte{}, zc.pseudoOutAssetID)
}
func TestParseV2Signatures_Mixin10(t *testing.T) {
tx := loadTestTx(t, "../testdata/v2_spending_tx_mixin10.hex")
require.Equal(t, uint64(3), tx.Version)
// Mixin10 tx has 2 ZC inputs.
require.Len(t, tx.Vin, 2)
entries, err := parseV2Signatures(tx.SignaturesRaw)
require.NoError(t, err)
require.Len(t, entries, 2, "should have 2 signature entries (one per input)")
for i, entry := range entries {
assert.Equal(t, types.SigTypeZC, entry.tag, "entry %d", i)
require.NotNil(t, entry.zcSig, "entry %d", i)
zc := entry.zcSig
// Both inputs use ring size 16 (ZC default).
assert.Equal(t, 16, zc.ringSize, "entry %d: expected ring size 16", i)
// Flat sig size: c(32) + r_g[16*32] + r_x[16*32] + K1(32) + K2(32) = 1120.
expectedSigSize := 32 + 16*32 + 16*32 + 64
assert.Equal(t, expectedSigSize, len(zc.clsagFlatSig), "entry %d", i)
}
}
func TestParseV2Proofs_Mixin0(t *testing.T) {
tx := loadTestTx(t, "../testdata/v2_spending_tx_mixin0.hex")
proofs, err := parseV2Proofs(tx.Proofs)
require.NoError(t, err)
// Should have BGE proofs (one per output).
assert.Len(t, proofs.bgeProofs, 2, "expected 2 BGE proofs (one per output)")
for i, p := range proofs.bgeProofs {
assert.True(t, len(p) > 0, "BGE proof %d should be non-empty", i)
}
// Should have BPP range proof.
assert.True(t, len(proofs.bppProofBytes) > 0, "BPP proof should be non-empty")
// Should have BPP commitments (one per output).
assert.Len(t, proofs.bppCommitments, 2, "expected 2 BPP commitments")
for i, c := range proofs.bppCommitments {
assert.NotEqual(t, [32]byte{}, c, "BPP commitment %d should be non-zero", i)
}
// Should have balance proof (96 bytes).
assert.Len(t, proofs.balanceProof, 96, "balance proof should be 96 bytes")
}
func TestParseV2Proofs_Mixin10(t *testing.T) {
tx := loadTestTx(t, "../testdata/v2_spending_tx_mixin10.hex")
proofs, err := parseV2Proofs(tx.Proofs)
require.NoError(t, err)
assert.Len(t, proofs.bgeProofs, 2)
assert.True(t, len(proofs.bppProofBytes) > 0)
assert.Len(t, proofs.bppCommitments, 2)
assert.Len(t, proofs.balanceProof, 96)
}
func TestVerifyV2Signatures_StructuralOnly(t *testing.T) {
// Test structural validation (no ring output function).
tx := loadTestTx(t, "../testdata/v2_spending_tx_mixin0.hex")
// With nil getZCRingOutputs, should pass structural checks.
err := VerifyTransactionSignatures(tx, config.TestnetForks, 2823, nil, nil)
require.NoError(t, err)
}
func TestVerifyV2Signatures_BadSigCount(t *testing.T) {
tx := loadTestTx(t, "../testdata/v2_spending_tx_mixin0.hex")
// Corrupt SignaturesRaw to have wrong count.
tx.SignaturesRaw = wire.EncodeVarint(5) // 5 sigs but only 1 input
err := verifyV2Signatures(tx, nil)
assert.Error(t, err, "should fail with mismatched sig count")
}
func TestVerifyV2Signatures_HTLCWrongSigTag_Bad(t *testing.T) {
tx := &types.Transaction{
Version: types.VersionPostHF5,
Vin: []types.TxInput{
types.TxInputHTLC{Amount: 100, KeyImage: types.KeyImage{1}},
},
SignaturesRaw: buildSingleZCSigRaw(),
}
err := VerifyTransactionSignatures(tx, config.TestnetForks, 250, nil, nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "HTLC")
}
func TestVerifyV2Signatures_TxHash(t *testing.T) {
// Verify the known tx hash matches.
tx := loadTestTx(t, "../testdata/v2_spending_tx_mixin0.hex")
txHash := wire.TransactionHash(tx)
expectedHash := "89c8839e3c6be3bb3616a5c2e7028fd8f33992e4f9ff218f8224825702865b8b"
assert.Equal(t, expectedHash, hex.EncodeToString(txHash[:]))
}
func TestVerifyV2Signatures_TxHashMixin10(t *testing.T) {
tx := loadTestTx(t, "../testdata/v2_spending_tx_mixin10.hex")
txHash := wire.TransactionHash(tx)
expectedHash := "87fbc60cde013579e1ad6ab403dee81c4da7a6b4621bea44f6973568c37b0af6"
assert.Equal(t, expectedHash, hex.EncodeToString(txHash[:]))
}