ct_tests.cpp raw

   1  // Copyright (c) 2025 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <test/fork_util.h>
   6  
   7  #include <coins.h>
   8  #include <consensus/ct.h>
   9  #include <consensus/tx_verify.h>
  10  #include <consensus/validation.h>
  11  #include <crypto/bignum.h>
  12  #include <crypto/bulletproofs.h>
  13  #include <kernel/cs_main.h>
  14  #include <primitives/transaction.h>
  15  #include <random.h>
  16  #include <script/script.h>
  17  #include <uint256.h>
  18  #include <validation.h>
  19  
  20  #include <boost/test/unit_test.hpp>
  21  
  22  BOOST_FIXTURE_TEST_SUITE(ct_tests, ForkTestingSetup)
  23  
  24  static std::vector<uint8_t> RandScalar(FastRandomContext& rng)
  25  {
  26      auto b = rng.randbytes(BP_SCALAR_SIZE);
  27      return {b.begin(), b.end()};
  28  }
  29  
  30  static CScript P2BPCTScript(const BPCommitment& c)
  31  {
  32      return CScript() << OP_4 << c;
  33  }
  34  
  35  // Build a kernel output script: OP_RETURN <"BK"> <fee:16 LE> <sig:64>.
  36  static CScript KernelScript(CAmount fee, const std::vector<uint8_t>& sig)
  37  {
  38      std::vector<uint8_t> magic = {CT_KERNEL_MAGIC_BYTE0, CT_KERNEL_MAGIC_BYTE1};
  39      std::vector<uint8_t> fb(CT_FEE_SIZE);
  40      for (size_t i = 0; i < CT_FEE_SIZE; i++) {
  41          fb[i] = uint8_t(static_cast<uint64_t>(fee >> (8 * i)));
  42      }
  43      return CScript() << OP_RETURN << magic << fb << sig;
  44  }
  45  
  46  BOOST_AUTO_TEST_CASE(ct_balance_valid)
  47  {
  48      const auto& cp = ForkConsensus();
  49      FastRandomContext rng{uint256{7}};
  50      // Attosat convention: satoshis * 10^18 + sub-satoshi fraction.
  51      const __int128 SAT = 1000000000000000000LL;
  52      const CAmount v_in = 5005 * SAT + 400, fee = 5000 * SAT + 250, v_out = v_in - fee;
  53  
  54      BPScalar r_in = RandScalar(rng);
  55      BPScalar zero(BP_SCALAR_SIZE, 0);
  56      BPCommitment cin, cout;
  57      BOOST_REQUIRE(CommitAmount(v_in, r_in, cin));
  58      BOOST_REQUIRE(CommitAmount(v_out, zero, cout));
  59  
  60      const COutPoint outpoint(Txid::FromUint256(uint256{0x11}), 0);
  61  
  62      CMutableTransaction tx;
  63      tx.vin.resize(1);
  64      tx.vin[0].prevout = outpoint;
  65      tx.vout.resize(2);
  66      tx.vout[0].nValue = 0;
  67      tx.vout[0].scriptPubKey = P2BPCTScript(cout);
  68  
  69      const int kernel_index = 1;
  70      CTKernelData kernel_msg_data;
  71      kernel_msg_data.fee = fee;
  72      const uint256 msg = ComputeCTKernelMessage(CTransaction(tx), kernel_index, kernel_msg_data);
  73      std::vector<uint8_t> sig;
  74      BOOST_REQUIRE(CreateCTKernelSig(r_in, {msg.begin(), msg.end()}, sig));
  75      tx.vout[1].nValue = 0;
  76      tx.vout[1].scriptPubKey = KernelScript(fee, sig);
  77  
  78      LOCK(cs_main);
  79      auto& view = m_node.chainman->ActiveChainstate().CoinsTip();
  80      view.AddCoin(outpoint, Coin(CTxOut(0, P2BPCTScript(cin)), 1, false), false);
  81  
  82      TxValidationState state;
  83      CAmount txfee = 0;
  84      BOOST_CHECK(Consensus::CheckTxInputs(CTransaction(tx), state, view, 966501, txfee,
  85                                            CheckTxInputsRules::None, cp, /*fork_active=*/true));
  86      // The explicit kernel fee (attosats) must be floored into the
  87      // satoshi-domain txfee so miners collect it; the 250-attosat
  88      // remainder stays burned and is never minted.
  89      BOOST_CHECK_EQUAL(txfee, fee / ATTOSATS_PER_SATOSHI);
  90      BOOST_CHECK_EQUAL(txfee, CAmount(5000));
  91  }
  92  
  93  BOOST_AUTO_TEST_CASE(ct_balance_inflation_rejected)
  94  {
  95      const auto& cp = ForkConsensus();
  96      FastRandomContext rng{uint256{8}};
  97      const CAmount v_in = 100000, fee = 5000;
  98      const CAmount v_out = v_in + 1000; // output exceeds input -> inflation
  99  
 100      BPScalar r_in = RandScalar(rng);
 101      BPScalar zero(BP_SCALAR_SIZE, 0);
 102      BPCommitment cin, cout;
 103      BOOST_REQUIRE(CommitAmount(v_in, r_in, cin));
 104      BOOST_REQUIRE(CommitAmount(v_out, zero, cout));
 105  
 106      const COutPoint outpoint(Txid::FromUint256(uint256{0x12}), 0);
 107  
 108      CMutableTransaction tx;
 109      tx.vin.resize(1);
 110      tx.vin[0].prevout = outpoint;
 111      tx.vout.resize(2);
 112      tx.vout[0].nValue = 0;
 113      tx.vout[0].scriptPubKey = P2BPCTScript(cout);
 114  
 115      const int kernel_index = 1;
 116      CTKernelData kernel_msg_data;
 117      kernel_msg_data.fee = fee;
 118      const uint256 msg = ComputeCTKernelMessage(CTransaction(tx), kernel_index, kernel_msg_data);
 119      std::vector<uint8_t> sig;
 120      BOOST_REQUIRE(CreateCTKernelSig(r_in, {msg.begin(), msg.end()}, sig));
 121      tx.vout[1].nValue = 0;
 122      tx.vout[1].scriptPubKey = KernelScript(fee, sig);
 123  
 124      LOCK(cs_main);
 125      auto& view = m_node.chainman->ActiveChainstate().CoinsTip();
 126      view.AddCoin(outpoint, Coin(CTxOut(0, P2BPCTScript(cin)), 1, false), false);
 127  
 128      TxValidationState state;
 129      CAmount txfee = 0;
 130      BOOST_CHECK(!Consensus::CheckTxInputs(CTransaction(tx), state, view, 966501, txfee,
 131                                             CheckTxInputsRules::None, cp, /*fork_active=*/true));
 132      BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-ct-balance");
 133  }
 134  
 135  BOOST_AUTO_TEST_CASE(ct_missing_kernel_rejected)
 136  {
 137      const auto& cp = ForkConsensus();
 138      FastRandomContext rng{uint256{9}};
 139  
 140      BPScalar r_in = RandScalar(rng);
 141      BPScalar zero(BP_SCALAR_SIZE, 0);
 142      BPCommitment cin, cout;
 143      BOOST_REQUIRE(CommitAmount(1000, r_in, cin));
 144      BOOST_REQUIRE(CommitAmount(900, zero, cout));
 145  
 146      const COutPoint outpoint(Txid::FromUint256(uint256{0x13}), 0);
 147  
 148      CMutableTransaction tx;
 149      tx.vin.resize(1);
 150      tx.vin[0].prevout = outpoint;
 151      tx.vout.resize(1);
 152      tx.vout[0].nValue = 0;
 153      tx.vout[0].scriptPubKey = P2BPCTScript(cout); // no kernel output
 154  
 155      LOCK(cs_main);
 156      auto& view = m_node.chainman->ActiveChainstate().CoinsTip();
 157      view.AddCoin(outpoint, Coin(CTxOut(0, P2BPCTScript(cin)), 1, false), false);
 158  
 159      TxValidationState state;
 160      CAmount txfee = 0;
 161      BOOST_CHECK(!Consensus::CheckTxInputs(CTransaction(tx), state, view, 966501, txfee,
 162                                             CheckTxInputsRules::None, cp, /*fork_active=*/true));
 163      BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-ct-no-kernel");
 164  }
 165  
 166  BOOST_AUTO_TEST_CASE(ct_nonzero_value_rejected)
 167  {
 168      const auto& cp = ForkConsensus();
 169      FastRandomContext rng{uint256{10}};
 170  
 171      BPScalar r_in = RandScalar(rng);
 172      BPScalar zero(BP_SCALAR_SIZE, 0);
 173      BPCommitment cin, cout;
 174      BOOST_REQUIRE(CommitAmount(1000, r_in, cin));
 175      BOOST_REQUIRE(CommitAmount(900, zero, cout));
 176  
 177      const COutPoint outpoint(Txid::FromUint256(uint256{0x14}), 0);
 178      const CAmount fee = 100;
 179  
 180      CMutableTransaction tx;
 181      tx.vin.resize(1);
 182      tx.vin[0].prevout = outpoint;
 183      tx.vout.resize(2);
 184      tx.vout[0].nValue = 50; // P2BPCT output must have nValue = 0
 185      tx.vout[0].scriptPubKey = P2BPCTScript(cout);
 186  
 187      const int kernel_index = 1;
 188      CTKernelData kernel_msg_data;
 189      kernel_msg_data.fee = fee;
 190      const uint256 msg = ComputeCTKernelMessage(CTransaction(tx), kernel_index, kernel_msg_data);
 191      std::vector<uint8_t> sig;
 192      BOOST_REQUIRE(CreateCTKernelSig(r_in, {msg.begin(), msg.end()}, sig));
 193      tx.vout[1].nValue = 0;
 194      tx.vout[1].scriptPubKey = KernelScript(fee, sig);
 195  
 196      LOCK(cs_main);
 197      auto& view = m_node.chainman->ActiveChainstate().CoinsTip();
 198      view.AddCoin(outpoint, Coin(CTxOut(0, P2BPCTScript(cin)), 1, false), false);
 199  
 200      TxValidationState state;
 201      CAmount txfee = 0;
 202      BOOST_CHECK(!Consensus::CheckTxInputs(CTransaction(tx), state, view, 966501, txfee,
 203                                             CheckTxInputsRules::None, cp, /*fork_active=*/true));
 204      // The standard value check (nValueIn=0 < value_out=50) rejects first.
 205      BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-in-belowout");
 206  }
 207  
 208  BOOST_AUTO_TEST_CASE(ct_mint_unbound_value_rejected)
 209  {
 210      // A mint whose kernel ignores the transparent input value fails: the
 211      // balance equation leaves the visible value unaccounted for.
 212      const auto& cp = ForkConsensus();
 213      FastRandomContext rng{uint256{11}};
 214  
 215      BPScalar r = RandScalar(rng);
 216      BPCommitment cout;
 217      BOOST_REQUIRE(CommitAmount(900, r, cout));
 218  
 219      const COutPoint outpoint(Txid::FromUint256(uint256{0x15}), 0);
 220      const CAmount fee = 100;
 221  
 222      CMutableTransaction tx;
 223      tx.vin.resize(1);
 224      tx.vin[0].prevout = outpoint;
 225      tx.vout.resize(2);
 226      tx.vout[0].nValue = 0;
 227      tx.vout[0].scriptPubKey = P2BPCTScript(cout);
 228  
 229      const int kernel_index = 1;
 230      CTKernelData kernel_msg_data;
 231      kernel_msg_data.fee = fee;
 232      const uint256 msg = ComputeCTKernelMessage(CTransaction(tx), kernel_index, kernel_msg_data);
 233      std::vector<uint8_t> sig;
 234      BOOST_REQUIRE(CreateCTKernelSig(r, {msg.begin(), msg.end()}, sig));
 235      tx.vout[1].nValue = 0;
 236      tx.vout[1].scriptPubKey = KernelScript(fee, sig);
 237  
 238      LOCK(cs_main);
 239      auto& view = m_node.chainman->ActiveChainstate().CoinsTip();
 240      // Transparent mint input worth 1000 satoshis.
 241      view.AddCoin(outpoint, Coin(CTxOut(1000, CScript() << OP_TRUE), 1, false), false);
 242  
 243      TxValidationState state;
 244      CAmount txfee = 0;
 245      BOOST_CHECK(!Consensus::CheckTxInputs(CTransaction(tx), state, view, 966501, txfee,
 246                                             CheckTxInputsRules::None, cp, /*fork_active=*/true));
 247      BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-ct-balance");
 248  }
 249  
 250  
 251  BOOST_AUTO_TEST_CASE(ct_mint_valid)
 252  {
 253      // Transparent value mints into CT: transparent input (OP_TRUE) spends
 254      // into a confidential output; the kernel balances the visible input
 255      // value and the miner collects only the kernel fee.
 256      const auto& cp = ForkConsensus();
 257      FastRandomContext rng{uint256{20}};
 258  
 259      const CAmount v_in_sats = 1000;
 260      const CAmount v_in = v_in_sats * ATTOSATS_PER_SATOSHI;
 261      const CAmount fee_attosats = ATTOSATS_PER_SATOSHI / 2; // 0.5 sat
 262      const CAmount v_out = v_in - fee_attosats;
 263  
 264      const COutPoint outpoint(Txid::FromUint256(uint256{0x51}), 0);
 265  
 266      BPScalar zero(BP_SCALAR_SIZE, 0);
 267      BPCommitment cout;
 268      BOOST_REQUIRE(CommitAmount(v_out, zero, cout));
 269  
 270      CMutableTransaction tx;
 271      tx.version = 2;
 272      tx.vin.resize(1);
 273      tx.vin[0].prevout = outpoint;
 274      tx.vout.resize(2);
 275      tx.vout[0].nValue = 0;
 276      tx.vout[0].scriptPubKey = P2BPCTScript(cout);
 277  
 278      // Kernel: excess = -sum(out blindings) = 0 here (blinding zero) - use a
 279      // non-zero output blinding to exercise the general path.
 280      BPScalar r_out = RandScalar(rng);
 281      BPCommitment cout2;
 282      BOOST_REQUIRE(CommitAmount(v_out, r_out, cout2));
 283      tx.vout[0].scriptPubKey = P2BPCTScript(cout2);
 284  
 285      const int kernel_index = 1;
 286      // excess = -r_out (replicate the wallet scalar negation with BigNum).
 287      std::vector<unsigned char> msg_bytes;
 288      {
 289          BigNum n;
 290          {
 291              static const uint8_t n_bytes[32] = {
 292                  0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,
 293                  0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,0x3B,0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x41};
 294              n = BigNum(std::vector<uint8_t>(n_bytes, n_bytes + 32), true);
 295          }
 296          BigNum r_bn(std::vector<uint8_t>(r_out.begin(), r_out.end()), true);
 297          BigNum neg = n - r_bn;
 298          auto le = neg.to_bytes(BP_SCALAR_SIZE);
 299          BPScalar excess_neg(BP_SCALAR_SIZE, 0);
 300          for (size_t i = 0; i < BP_SCALAR_SIZE; i++) excess_neg[i] = le[BP_SCALAR_SIZE - 1 - i];
 301  
 302          std::vector<CAmount> transparent_values{v_in};
 303          CTKernelData kd;
 304          kd.fee = fee_attosats;
 305          const uint256 msg = ComputeCTKernelMessage(CTransaction(tx), kernel_index, kd,
 306                                                     transparent_values);
 307          msg_bytes.assign(msg.begin(), msg.end());
 308          std::vector<uint8_t> sig;
 309          BOOST_REQUIRE(CreateCTKernelSig(excess_neg, msg_bytes, sig));
 310          kd.sig = std::move(sig);
 311          // Rebuild the kernel script from the data (fee + sig).
 312          tx.vout[1].nValue = 0;
 313          tx.vout[1].scriptPubKey = KernelScript(kd.fee, kd.sig);
 314      }
 315  
 316      LOCK(cs_main);
 317      auto& view = m_node.chainman->ActiveChainstate().CoinsTip();
 318      view.AddCoin(outpoint, Coin(CTxOut(v_in_sats, CScript() << OP_TRUE), 1, false), false);
 319  
 320      TxValidationState state;
 321      CAmount txfee = 0;
 322      BOOST_CHECK(Consensus::CheckTxInputs(CTransaction(tx), state, view, 966501, txfee,
 323                                           CheckTxInputsRules::None, cp, /*fork_active=*/true));
 324      // The miner collects ONLY the kernel fee, not the full input value.
 325      BOOST_CHECK_EQUAL(txfee, fee_attosats / ATTOSATS_PER_SATOSHI);
 326      BOOST_CHECK_EQUAL(txfee, CAmount(0)); // 0.5 sat floors to zero; no double pay
 327  }
 328  
 329  BOOST_AUTO_TEST_CASE(ct_mint_imbalance_rejected)
 330  {
 331      // Mint where outputs + fee do not equal the transparent input value:
 332      // the kernel key has an H component nobody can sign for.
 333      const auto& cp = ForkConsensus();
 334      FastRandomContext rng{uint256{21}};
 335  
 336      const CAmount v_in_sats = 1000;
 337      const CAmount v_in = v_in_sats * ATTOSATS_PER_SATOSHI;
 338      const CAmount fee_attosats = ATTOSATS_PER_SATOSHI / 2;
 339      const CAmount v_out = v_in - fee_attosats - 1; // 1 attosat short
 340  
 341      const COutPoint outpoint(Txid::FromUint256(uint256{0x52}), 0);
 342  
 343      BPScalar r_out = RandScalar(rng);
 344      BPCommitment cout;
 345      BOOST_REQUIRE(CommitAmount(v_out, r_out, cout));
 346  
 347      CMutableTransaction tx;
 348      tx.version = 2;
 349      tx.vin.resize(1);
 350      tx.vin[0].prevout = outpoint;
 351      tx.vout.resize(2);
 352      tx.vout[0].nValue = 0;
 353      tx.vout[0].scriptPubKey = P2BPCTScript(cout);
 354  
 355      const int kernel_index = 1;
 356      {
 357          BigNum n;
 358          {
 359              static const uint8_t n_bytes[32] = {
 360                  0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,
 361                  0xBA,0xAE,0xDC,0xE6,0xAF,0x48,0xA0,0x3B,0xBF,0xD2,0x5E,0x8C,0xD0,0x36,0x41,0x41};
 362              n = BigNum(std::vector<uint8_t>(n_bytes, n_bytes + 32), true);
 363          }
 364          BigNum r_bn(std::vector<uint8_t>(r_out.begin(), r_out.end()), true);
 365          BigNum neg = n - r_bn;
 366          auto le = neg.to_bytes(BP_SCALAR_SIZE);
 367          BPScalar excess_neg(BP_SCALAR_SIZE, 0);
 368          for (size_t i = 0; i < BP_SCALAR_SIZE; i++) excess_neg[i] = le[BP_SCALAR_SIZE - 1 - i];
 369  
 370          std::vector<CAmount> transparent_values{v_in};
 371          CTKernelData kd;
 372          kd.fee = fee_attosats;
 373          const uint256 msg = ComputeCTKernelMessage(CTransaction(tx), kernel_index, kd,
 374                                                     transparent_values);
 375          std::vector<uint8_t> msg_bytes(msg.begin(), msg.end());
 376          std::vector<uint8_t> sig;
 377          BOOST_REQUIRE(CreateCTKernelSig(excess_neg, msg_bytes, sig));
 378          kd.sig = std::move(sig);
 379          tx.vout[1].nValue = 0;
 380          tx.vout[1].scriptPubKey = KernelScript(kd.fee, kd.sig);
 381      }
 382  
 383      LOCK(cs_main);
 384      auto& view = m_node.chainman->ActiveChainstate().CoinsTip();
 385      view.AddCoin(outpoint, Coin(CTxOut(v_in_sats, CScript() << OP_TRUE), 1, false), false);
 386  
 387      TxValidationState state;
 388      CAmount txfee = 0;
 389      BOOST_CHECK(!Consensus::CheckTxInputs(CTransaction(tx), state, view, 966501, txfee,
 390                                             CheckTxInputsRules::None, cp, /*fork_active=*/true));
 391      BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-ct-balance");
 392  }
 393  
 394  BOOST_AUTO_TEST_CASE(ct_decoy_kernel_fee_preserved)
 395  {
 396      // A transparent transaction carrying a kernel-shaped OP_RETURN "BK"
 397      // output is NOT a confidential transaction (no CT inputs/outputs), so
 398      // its fee must not be zeroed by the kernel-fee path.
 399      const auto& cp = ForkConsensus();
 400  
 401      const COutPoint outpoint(Txid::FromUint256(uint256{0x61}), 0);
 402      const CAmount v_in = 1000, v_out = 900;
 403      const CScript spendable = CScript() << OP_0 << std::vector<uint8_t>(20, 0x42); // P2WPKH-shaped, not CT
 404  
 405      CMutableTransaction mtx;
 406      mtx.vin.resize(1);
 407      mtx.vin[0].prevout = outpoint;
 408      mtx.vout.resize(2);
 409      mtx.vout[0].nValue = v_out;
 410      mtx.vout[0].scriptPubKey = spendable;
 411      // Decoy kernel output: OP_RETURN <0x02> "BK" (matches the kernel
 412      // prefix but carries no fee/signature).
 413      mtx.vout[1].nValue = 0;
 414      mtx.vout[1].scriptPubKey = CScript() << OP_RETURN
 415          << std::vector<uint8_t>{CT_KERNEL_MAGIC_BYTE0, CT_KERNEL_MAGIC_BYTE1};
 416  
 417      {
 418          LOCK(cs_main);
 419          auto& view = m_node.chainman->ActiveChainstate().CoinsTip();
 420          view.AddCoin(outpoint, Coin(CTxOut(v_in, spendable), 1, false), false);
 421      }
 422  
 423      TxValidationState state;
 424      CAmount txfee = 0;
 425      BOOST_CHECK(Consensus::CheckTxInputs(CTransaction(mtx), state, m_node.chainman->ActiveChainstate().CoinsTip(), 966501, txfee,
 426                                           CheckTxInputsRules::None, cp, /*fork_active=*/true));
 427      // The real transparent fee (v_in - v_out) is preserved: no zeroing.
 428      BOOST_CHECK_EQUAL(txfee, v_in - v_out);
 429  }
 430  
 431  BOOST_AUTO_TEST_CASE(ct_offset_inflation_rejected)
 432  {
 433      // A1 regression: the balance equation is a plain Pedersen sum with no
 434      // offset terms.  A kernel carrying an extra 33-byte point push (the old
 435      // "blinding offset" field, which allowed minting arbitrary value via
 436      // b*H terms) must be rejected at parse time.
 437      const auto& cp = ForkConsensus();
 438      FastRandomContext rng{uint256{62}};
 439      const __int128 SAT = 1000000000000000000LL;
 440      const CAmount v_in = 5000 * SAT, fee = 1000, v_out = v_in - fee;
 441  
 442      BPScalar r_in = RandScalar(rng);
 443      BPScalar zero(BP_SCALAR_SIZE, 0);
 444      BPCommitment cin, cout;
 445      BOOST_REQUIRE(CommitAmount(v_in, r_in, cin));
 446      BOOST_REQUIRE(CommitAmount(v_out, zero, cout));
 447  
 448      const COutPoint outpoint(Txid::FromUint256(uint256{0x71}), 0);
 449  
 450      CMutableTransaction mtx;
 451      mtx.vin.resize(1);
 452      mtx.vin[0].prevout = outpoint;
 453      mtx.vout.resize(2);
 454      mtx.vout[0].nValue = 0;
 455      mtx.vout[0].scriptPubKey = P2BPCTScript(cout);
 456  
 457      const int kernel_index = 1;
 458      CTKernelData kernel_msg_data;
 459      kernel_msg_data.fee = fee;
 460      const uint256 msg = ComputeCTKernelMessage(CTransaction(mtx), kernel_index, kernel_msg_data);
 461      std::vector<uint8_t> sig;
 462      BOOST_REQUIRE(CreateCTKernelSig(r_in, {msg.begin(), msg.end()}, sig));
 463      // Valid kernel plus a smuggled 33-byte "offset" point.
 464      mtx.vout[1].nValue = 0;
 465      mtx.vout[1].scriptPubKey = KernelScript(fee, sig);
 466      mtx.vout[1].scriptPubKey << std::vector<uint8_t>(33, 0x42);
 467  
 468      {
 469          LOCK(cs_main);
 470          auto& view = m_node.chainman->ActiveChainstate().CoinsTip();
 471          view.AddCoin(outpoint, Coin(CTxOut(0, P2BPCTScript(cin)), 1, false), false);
 472      }
 473  
 474      TxValidationState state;
 475      CAmount txfee = 0;
 476      BOOST_CHECK(!Consensus::CheckTxInputs(CTransaction(mtx), state, m_node.chainman->ActiveChainstate().CoinsTip(), 966501, txfee,
 477                                            CheckTxInputsRules::None, cp, /*fork_active=*/true));
 478      BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-ct-kernel-format");
 479  }
 480  
 481  BOOST_AUTO_TEST_SUITE_END()
 482