// 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[:])) }