LCOV - code coverage report
Current view: top level - src/test - script_P2CS_tests.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 199 210 94.8 %
Date: 2025-02-23 09:33:43 Functions: 17 18 94.4 %

          Line data    Source code
       1             : // Copyright (c) 2020-2021 The PIVX Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or https://www.opensource.org/licenses/mit-license.php.
       4             : #include "key.h"
       5             : #include "key_io.h"
       6             : #include "policy/policy.h"
       7             : #include "wallet/test/wallet_test_fixture.h"
       8             : #include "wallet/wallet.h"
       9             : 
      10             : #include <boost/test/unit_test.hpp>
      11             : 
      12             : BOOST_FIXTURE_TEST_SUITE(script_P2CS_tests, WalletTestingSetup)
      13             : 
      14           4 : void CheckValidKeyId(const CTxDestination& dest, const CKeyID& expectedKey)
      15             : {
      16           4 :     const CKeyID* keyid = boost::get<CKeyID>(&dest);
      17           4 :     if (keyid) {
      18           8 :         BOOST_CHECK(keyid);
      19           8 :         BOOST_CHECK(*keyid == expectedKey);
      20             :     } else {
      21           0 :         BOOST_ERROR("Destination is not a CKeyID");
      22             :     }
      23           4 : }
      24             : 
      25             : // Goal: check cold staking script keys extraction
      26           2 : BOOST_AUTO_TEST_CASE(extract_cold_staking_destination_keys)
      27             : {
      28           1 :     CKey ownerKey;
      29           1 :     ownerKey.MakeNewKey(true);
      30           1 :     CKeyID ownerId = ownerKey.GetPubKey().GetID();
      31           2 :     CKey stakerKey;
      32           1 :     stakerKey.MakeNewKey(true);
      33           1 :     CKeyID stakerId = stakerKey.GetPubKey().GetID();
      34           2 :     CScript script = GetScriptForStakeDelegation(stakerId, ownerId);
      35             : 
      36             :     // Check owner
      37           2 :     CTxDestination ownerDest;
      38           2 :     BOOST_CHECK(ExtractDestination(script, ownerDest, false));
      39           1 :     CheckValidKeyId(ownerDest, ownerId);
      40             : 
      41             :     // Check staker
      42           2 :     CTxDestination stakerDest;
      43           2 :     BOOST_CHECK(ExtractDestination(script, stakerDest, true));
      44           1 :     CheckValidKeyId(stakerDest, stakerId);
      45             : 
      46             :     // Now go with ExtractDestinations.
      47           1 :     txnouttype type;
      48           1 :     int nRequiredRet = -1;
      49           2 :     std::vector<CTxDestination> destVector;
      50           2 :     BOOST_CHECK(ExtractDestinations(script, type, destVector, nRequiredRet));
      51           2 :     BOOST_CHECK(type == TX_COLDSTAKE);
      52           2 :     BOOST_CHECK(nRequiredRet == 2);
      53           2 :     BOOST_CHECK(destVector.size() == 2);
      54           1 :     CheckValidKeyId(destVector[0], stakerId);
      55           1 :     CheckValidKeyId(destVector[1], ownerId);
      56           1 : }
      57             : 
      58           2 : static CScript GetNewP2CS(CKey& stakerKey, CKey& ownerKey, bool fLastOutFree)
      59             : {
      60           4 :     stakerKey = KeyIO::DecodeSecret("YNdsth3BsW53DYmCiR12SofWSAt2utXQUSGoin3PekVQCMbzfS7E");
      61           4 :     ownerKey = KeyIO::DecodeSecret("YUo8oW3y8cUQdQxQxCdnUJ4Ww5H7nHBEMwD2bNDpBbuLM59t4rvd");
      62           1 :     return fLastOutFree ? GetScriptForStakeDelegationLOF(stakerKey.GetPubKey().GetID(), ownerKey.GetPubKey().GetID())
      63           3 :                         : GetScriptForStakeDelegation(stakerKey.GetPubKey().GetID(), ownerKey.GetPubKey().GetID());
      64             : }
      65             : 
      66           2 : static CScript GetDummyP2CS(const CKeyID& dummyKeyID)
      67             : {
      68           2 :     return GetScriptForStakeDelegation(dummyKeyID, dummyKeyID);
      69             : }
      70             : 
      71           2 : static CScript GetDummyP2PKH(const CKeyID& dummyKeyID)
      72             : {
      73           4 :     return GetScriptForDestination(dummyKeyID);
      74             : }
      75             : 
      76             : static const CAmount amtIn = 200 * COIN;
      77             : static const unsigned int flags = STANDARD_SCRIPT_VERIFY_FLAGS;
      78             : 
      79           2 : static CMutableTransaction CreateNewColdStakeTx(CScript& scriptP2CS, CKey& stakerKey, CKey& ownerKey, bool fLastOutFree)
      80             : {
      81           2 :     scriptP2CS = GetNewP2CS(stakerKey, ownerKey, fLastOutFree);
      82             : 
      83             :     // Create prev transaction:
      84           2 :     CMutableTransaction txFrom;
      85           2 :     txFrom.vout.resize(1);
      86           2 :     txFrom.vout[0].nValue = amtIn;
      87           2 :     txFrom.vout[0].scriptPubKey = scriptP2CS;
      88             : 
      89             :     // Create coldstake
      90           2 :     CMutableTransaction tx;
      91           2 :     tx.vin.resize(1);
      92           2 :     tx.vout.resize(2);
      93           2 :     tx.vin[0].prevout.n = 0;
      94           2 :     tx.vin[0].prevout.hash = txFrom.GetHash();
      95           2 :     tx.vout[0].nValue = 0;
      96           2 :     tx.vout[0].scriptPubKey.clear();
      97           2 :     tx.vout[1].nValue = amtIn + 2 * COIN;
      98           2 :     tx.vout[1].scriptPubKey = scriptP2CS;
      99             : 
     100           2 :     return tx;
     101             : }
     102             : 
     103          22 : void SignColdStake(CMutableTransaction& tx, int nIn, const CScript& prevScript, const CKey& key, bool fStaker)
     104             : {
     105          22 :     assert(nIn < (int) tx.vin.size());
     106          22 :     tx.vin[nIn].scriptSig.clear();
     107          22 :     const CTransaction _tx(tx);
     108          22 :     SigVersion sv = _tx.GetRequiredSigVersion();
     109          22 :     const uint256& hash = SignatureHash(prevScript, _tx, nIn, SIGHASH_ALL, amtIn, sv);
     110          44 :     std::vector<unsigned char> vchSig;
     111          44 :     BOOST_CHECK(key.Sign(hash, vchSig));
     112          22 :     vchSig.push_back((unsigned char)SIGHASH_ALL);
     113          51 :     std::vector<unsigned char> selector(1, fStaker ? (int) OP_TRUE : OP_FALSE);
     114          44 :     tx.vin[nIn].scriptSig << vchSig << selector << ToByteVector(key.GetPubKey());
     115          22 : }
     116             : 
     117          22 : static bool CheckP2CSScript(const CScript& scriptSig, const CScript& scriptPubKey, const CMutableTransaction& tx, ScriptError& err)
     118             : {
     119          22 :     err = SCRIPT_ERR_OK;
     120          44 :     return VerifyScript(scriptSig, scriptPubKey, flags, MutableTransactionSignatureChecker(&tx, 0, amtIn), tx.GetRequiredSigVersion(), &err);
     121             : }
     122             : 
     123           2 : BOOST_AUTO_TEST_CASE(coldstake_lof_script)
     124             : {
     125           1 :     CScript scriptP2CS;
     126           3 :     CKey stakerKey, ownerKey;
     127             : 
     128             :     // create unsigned coinstake transaction
     129           2 :     CMutableTransaction good_tx = CreateNewColdStakeTx(scriptP2CS, stakerKey, ownerKey, true);
     130             : 
     131             :     // sign the input with the staker key
     132           1 :     SignColdStake(good_tx, 0, scriptP2CS, stakerKey, true);
     133             : 
     134             :     // check the signature and script
     135           1 :     ScriptError err = SCRIPT_ERR_OK;
     136           1 :     CMutableTransaction tx(good_tx);
     137           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     138             : 
     139             :     // pay less than expected
     140           1 :     tx.vout[1].nValue -= 3 * COIN;
     141           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     142           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     143           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     144             : 
     145             :     // Add another p2cs out
     146           1 :     tx.vout.emplace_back(3 * COIN, scriptP2CS);
     147           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     148           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     149             : 
     150           2 :     const CKey& dummyKey = KeyIO::DecodeSecret("YNdsth3BsW53DYmCiR12SofWSAt2utXQUSGoin3PekVQCMbzfS7E");
     151           1 :     const CKeyID& dummyKeyID = dummyKey.GetPubKey().GetID();
     152           2 :     const CScript& dummyP2PKH = GetDummyP2PKH(dummyKeyID);
     153             : 
     154             :     // Add a masternode out
     155           1 :     tx.vout.emplace_back(3 * COIN, dummyP2PKH);
     156           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     157           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     158             : 
     159             :     // Transfer more coins to the masternode
     160           1 :     tx.vout[2].nValue -= 3 * COIN;
     161           1 :     tx.vout[3].nValue += 3 * COIN;
     162           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     163           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     164           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     165             : 
     166             :     // Add two "free" outputs
     167           1 :     tx = good_tx;
     168           1 :     tx.vout[1].nValue -= 3 * COIN;
     169           1 :     tx.vout.emplace_back(3 * COIN, dummyP2PKH);
     170           1 :     tx.vout.emplace_back(3 * COIN, dummyP2PKH);
     171           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     172           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     173           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     174             :     // -- but the owner can
     175           1 :     SignColdStake(tx, 0, scriptP2CS, ownerKey, false);
     176           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     177             : 
     178             :     // Replace with new p2cs
     179           1 :     tx = good_tx;
     180           1 :     tx.vout[1].scriptPubKey = GetDummyP2CS(dummyKeyID);
     181           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     182           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     183           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     184             :     // -- but the owner can
     185           1 :     SignColdStake(tx, 0, scriptP2CS, ownerKey, false);
     186           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     187             : 
     188             :     // Replace with single dummy out
     189           1 :     tx = good_tx;
     190           2 :     tx.vout[1] = CTxOut(COIN, dummyP2PKH);
     191           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     192           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     193           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     194             :     // -- but the owner can
     195           1 :     SignColdStake(tx, 0, scriptP2CS, ownerKey, false);
     196           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     197           1 : }
     198             : 
     199           2 : BOOST_AUTO_TEST_CASE(coldstake_script)
     200             : {
     201           1 :     CScript scriptP2CS;
     202           3 :     CKey stakerKey, ownerKey;
     203             : 
     204             :     // create unsigned coinstake transaction
     205           2 :     CMutableTransaction good_tx = CreateNewColdStakeTx(scriptP2CS, stakerKey, ownerKey, false);
     206             : 
     207             :     // sign the input with the staker key
     208           1 :     SignColdStake(good_tx, 0, scriptP2CS, stakerKey, true);
     209             : 
     210             :     // check the signature and script
     211           1 :     ScriptError err = SCRIPT_ERR_OK;
     212           1 :     CMutableTransaction tx(good_tx);
     213           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     214             : 
     215             :     // pay less than expected
     216           1 :     tx.vout[1].nValue -= 3 * COIN;
     217           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     218           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     219           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     220             : 
     221             :     // Add another p2cs out
     222           1 :     tx.vout.emplace_back(3 * COIN, scriptP2CS);
     223           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     224           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     225             : 
     226           2 :     const CKey& dummyKey = KeyIO::DecodeSecret("YNdsth3BsW53DYmCiR12SofWSAt2utXQUSGoin3PekVQCMbzfS7E");
     227           1 :     const CKeyID& dummyKeyID = dummyKey.GetPubKey().GetID();
     228           2 :     const CScript& dummyP2PKH = GetDummyP2PKH(dummyKeyID);
     229             : 
     230             :     // Add a dummy P2PKH out at the end
     231           1 :     tx.vout.emplace_back(3 * COIN, dummyP2PKH);
     232           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     233           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     234           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     235             :     // -- but the owner can
     236           1 :     SignColdStake(tx, 0, scriptP2CS, ownerKey, false);
     237           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     238             : 
     239             :     // Add a dummy P2PKH out at the beginning
     240           1 :     tx = good_tx;
     241           2 :     tx.vout[1] = CTxOut(3 * COIN, dummyP2PKH);
     242           1 :     tx.vout.emplace_back(3 * COIN, scriptP2CS);
     243           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     244           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     245           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     246             :     // -- but the owner can
     247           1 :     SignColdStake(tx, 0, scriptP2CS, ownerKey, false);
     248           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     249             : 
     250             :     // Replace with new p2cs
     251           1 :     tx = good_tx;
     252           1 :     tx.vout[1].scriptPubKey = GetDummyP2CS(dummyKeyID);
     253           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     254           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     255           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     256             :     // -- but the owner can
     257           1 :     SignColdStake(tx, 0, scriptP2CS, ownerKey, false);
     258           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     259             : 
     260             :     // Replace with single dummy out
     261           1 :     tx = good_tx;
     262           2 :     tx.vout[1] = CTxOut(COIN, dummyP2PKH);
     263           1 :     SignColdStake(tx, 0, scriptP2CS, stakerKey, true);
     264           2 :     BOOST_CHECK(!CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     265           2 :     BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_CHECKCOLDSTAKEVERIFY, ScriptErrorString(err));
     266             :     // -- but the owner can
     267           1 :     SignColdStake(tx, 0, scriptP2CS, ownerKey, false);
     268           2 :     BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err));
     269           1 : }
     270             : 
     271             : // Check that it's not possible to "fake" a P2CS script for the owner by splitting the locking
     272             : // and unlocking parts. This particular script can be spent by any key, with a
     273             : // unlocking script composed like: <sig> <pk> <DUP> <HASH160> <pkh>
     274           1 : static CScript GetFakeLockingScript(const CKeyID staker, const CKeyID& owner)
     275             : {
     276           1 :     CScript script;
     277           1 :     script << opcodetype(0x2F) << opcodetype(0x01) << OP_ROT <<
     278           1 :             OP_IF << OP_CHECKCOLDSTAKEVERIFY << ToByteVector(staker) <<
     279           2 :             OP_ELSE << ToByteVector(owner) << OP_DROP <<
     280           1 :             OP_EQUALVERIFY << OP_CHECKSIG;
     281             : 
     282           1 :     return script;
     283             : }
     284             : 
     285           0 : void FakeUnlockColdStake(CMutableTransaction& tx, const CScript& prevScript, const CKey& key)
     286             : {
     287             :     // sign the first input
     288           0 :     tx.vin[0].scriptSig.clear();
     289           0 :     const CTransaction _tx(tx);
     290           0 :     SigVersion sv = _tx.GetRequiredSigVersion();
     291           0 :     const uint256& hash = SignatureHash(prevScript, _tx, 0, SIGHASH_ALL, amtIn, sv);
     292           0 :     std::vector<unsigned char> vchSig;
     293           0 :     BOOST_CHECK(key.Sign(hash, vchSig));
     294           0 :     vchSig.push_back((unsigned char)SIGHASH_ALL);
     295           0 :     tx.vin[0].scriptSig << vchSig << ToByteVector(key.GetPubKey()) << OP_DUP << OP_HASH160 << ToByteVector(key.GetPubKey().GetID());
     296           0 : }
     297             : 
     298           1 : static void setupWallet(CWallet& wallet)
     299             : {
     300           1 :     wallet.SetMinVersion(FEATURE_SAPLING);
     301           1 :     wallet.SetupSPKM(false);
     302           1 : }
     303             : 
     304           2 : BOOST_AUTO_TEST_CASE(fake_script_test)
     305             : {
     306           1 :     CWallet& wallet = m_wallet;
     307           1 :     LOCK(wallet.cs_wallet);
     308           1 :     setupWallet(wallet);
     309           2 :     CKey stakerKey;         // dummy staker key (not in the wallet)
     310           1 :     stakerKey.MakeNewKey(true);
     311           1 :     CKeyID stakerId = stakerKey.GetPubKey().GetID();
     312           1 :     CPubKey ownerPubKey;
     313           1 :     BOOST_ASSERT(wallet.GetKeyFromPool(ownerPubKey));
     314           1 :     const CKeyID& ownerId = ownerPubKey.GetID();
     315           2 :     CKey ownerKey;          // owner key (in the wallet)
     316           1 :     BOOST_ASSERT(wallet.GetKey(ownerId, ownerKey));
     317             : 
     318           2 :     const CScript& scriptP2CS = GetFakeLockingScript(stakerId, ownerId);
     319             : 
     320             :     // Create prev transaction
     321           2 :     CMutableTransaction txFrom;
     322           1 :     txFrom.vout.resize(1);
     323           1 :     txFrom.vout[0].nValue = amtIn;
     324           1 :     txFrom.vout[0].scriptPubKey = scriptP2CS;
     325             : 
     326             :     // it does NOT pass IsPayToColdStaking
     327           2 :     BOOST_CHECK_MESSAGE(!scriptP2CS.IsPayToColdStaking(), "Fake script passes as P2CS");
     328             : 
     329             :     // the output amount is NOT credited to the owner wallet
     330           2 :     wallet.AddToWallet({&wallet, MakeTransactionRef(CTransaction(txFrom))});
     331           1 :     BOOST_CHECK_EQUAL(wallet.GetWalletTx(txFrom.GetHash())->GetAvailableCredit(false, ISMINE_SPENDABLE_TRANSPARENT), 0);
     332           1 : }
     333             : 
     334             : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.14