Line data Source code
1 : // Copyright (c) 2025 The Dash Core 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 <consensus/consensus.h> 6 : #include <hash.h> 7 : #include <instantsend/lock.h> 8 : #include <llmq/signhash.h> 9 : #include <primitives/transaction.h> 10 : #include <streams.h> 11 : #include <uint256.h> 12 : #include <util/strencodings.h> 13 : 14 : #include <string_view> 15 : 16 : #include <boost/test/unit_test.hpp> 17 : 18 146 : BOOST_AUTO_TEST_SUITE(evo_islock_tests) 19 : 20 3 : uint256 CalculateRequestId(const std::vector<COutPoint>& inputs) 21 : { 22 3 : CHashWriter hw(SER_GETHASH, 0); 23 3 : hw << std::string_view("islock"); 24 3 : hw << inputs; 25 3 : return hw.GetHash(); 26 : } 27 : 28 148 : BOOST_AUTO_TEST_CASE(getrequestid) 29 : { 30 : // Create an empty InstantSendLock 31 1 : instantsend::InstantSendLock islock; 32 : 33 : // Compute expected hash for an empty inputs vector. 34 : // Note: InstantSendLock::GetRequestId() serializes the prefix "islock" 35 : // followed by the 'inputs' vector. 36 : { 37 1 : const uint256 expected = CalculateRequestId(islock.inputs); 38 : 39 1 : BOOST_CHECK(islock.GetRequestId() == expected); 40 : } 41 : 42 : // Now add two dummy inputs to the lock 43 1 : islock.inputs.clear(); 44 : // Construct two dummy outpoints (using uint256S for a dummy hash) 45 1 : COutPoint op1(uint256::ONE, 0); 46 1 : COutPoint op2(uint256::TWO, 1); 47 1 : islock.inputs.push_back(op1); 48 1 : islock.inputs.push_back(op2); 49 : 50 1 : const uint256 expected = CalculateRequestId(islock.inputs); 51 : 52 1 : BOOST_CHECK(islock.GetRequestId() == expected); 53 1 : } 54 : 55 148 : BOOST_AUTO_TEST_CASE(deserialize_instantlock_from_realdata2) 56 : { 57 : // Expected values from the provided getislocks output: 58 1 : const std::string_view expectedTxidStr = "7b33968effa613e8ea9c1b5734c9bbbe467ff4650f8060caf8a5c213c6059d5b"; 59 1 : const std::string_view expectedCycleHashStr = "000000000000000bbd0b1bb95540351e7ee99c5b08efde076b3d712a57ea74d6"; 60 : const std::string_view expectedSignatureStr = 61 1 : "997d0b36738a9eef46ceeb4405998ff7235317708f277402799ffe05258015cae9b6bae" 62 : "43683f992b2f50f70f8f0cb9c0f26af340b00903e93995c1345d1b2c5b697ebecdbe581" 63 : "1dd112e11889101dcb4553b2bc206ab304026b96c07dec4f24"; 64 1 : const std::string quorumHashStr = "0000000000000019756ecc9c9c5f476d3f66876b1dcfa5dde1ea82f0d99334a2"; 65 1 : const std::string_view expectedSignHashStr = "6a3c37bc610c4efd5babd8941068a8eca9e7bec942fe175b8ca9cae31b67e838"; 66 : // The serialized InstantSend lock from the "hex" field of getislocks: 67 : const std::string_view islockHex = 68 1 : "0101497915895c30eebfad0c5fcfb9e0e72308c7e92cd3749be2fd49c8320c4c58b6010000005b9d05c613c2a5f8ca60800f65f47f46be" 69 : "bbc934571b9ceae813a6ff8e96337bd674ea572a713d6b07deef085b9ce97e1e354055b91b0bbd0b00000000000000997d0b36738a9eef" 70 : "46ceeb4405998ff7235317708f277402799ffe05258015cae9b6bae43683f992b2f50f70f8f0cb9c0f26af340b00903e93995c1345d1b2" 71 : "c5b697ebecdbe5811dd112e11889101dcb4553b2bc206ab304026b96c07dec4f24"; 72 : 73 : // This islock was created with non-legacy. Using legacy will result in the signature being all zeros. 74 1 : bls::bls_legacy_scheme.store(false); 75 : 76 : // Convert hex string to a byte vector and deserialize. 77 1 : std::vector<unsigned char> islockData = ParseHex(islockHex); 78 1 : CDataStream ss(islockData, SER_NETWORK, PROTOCOL_VERSION); 79 1 : instantsend::InstantSendLock islock; 80 1 : ss >> islock; 81 : 82 : // Verify the calculated signHash 83 : auto signHash = 84 1 : llmq::SignHash(Consensus::LLMQType::LLMQ_60_75, uint256S(quorumHashStr), islock.GetRequestId(), islock.txid).Get(); 85 1 : BOOST_CHECK_EQUAL(signHash.ToString(), expectedSignHashStr); 86 : 87 : // Verify the txid field. 88 1 : BOOST_CHECK_EQUAL(islock.txid.ToString(), expectedTxidStr); 89 : 90 : // Verify the cycleHash field. 91 1 : BOOST_CHECK_EQUAL(islock.cycleHash.ToString(), expectedCycleHashStr); 92 : 93 : // Verify the inputs vector has exactly one element. 94 1 : BOOST_REQUIRE_EQUAL(islock.inputs.size(), 1U); 95 1 : const COutPoint& input = islock.inputs.front(); 96 1 : const std::string expectedInputTxid = "b6584c0c32c849fde29b74d32ce9c70823e7e0b9cf5f0cadbfee305c89157949"; 97 1 : const unsigned int expectedInputN = 1; 98 1 : BOOST_CHECK_EQUAL(input.hash.ToString(), expectedInputTxid); 99 1 : BOOST_CHECK_EQUAL(input.n, expectedInputN); 100 : 101 : // Compute the expected request ID: it is the hash of the constant prefix "islock" followed by the inputs. 102 1 : uint256 expectedRequestId = CalculateRequestId(islock.inputs); 103 1 : BOOST_CHECK_EQUAL(islock.GetRequestId().ToString(), expectedRequestId.ToString()); 104 : 105 : // Verify the signature field. 106 1 : BOOST_CHECK_EQUAL(islock.sig.Get().ToString(), expectedSignatureStr); 107 1 : } 108 : 109 148 : BOOST_AUTO_TEST_CASE(geninputlockrequestid_basic) 110 : { 111 : // Test that GenInputLockRequestId generates consistent hashes for the same outpoint 112 1 : const uint256 txHash = uint256S("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); 113 1 : const uint32_t outputIndex = 5; 114 : 115 1 : COutPoint outpoint1(txHash, outputIndex); 116 1 : COutPoint outpoint2(txHash, outputIndex); 117 : 118 : // Same outpoints should produce identical request IDs 119 1 : const uint256 requestId1 = instantsend::GenInputLockRequestId(outpoint1); 120 1 : const uint256 requestId2 = instantsend::GenInputLockRequestId(outpoint2); 121 : 122 1 : BOOST_CHECK(requestId1 == requestId2); 123 1 : BOOST_CHECK(!requestId1.IsNull()); 124 1 : } 125 : 126 148 : BOOST_AUTO_TEST_CASE(geninputlockrequestid_different_outpoints) 127 : { 128 : // Test that different outpoints produce different request IDs 129 1 : const uint256 txHash1 = uint256S("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); 130 1 : const uint256 txHash2 = uint256S("0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"); 131 : 132 1 : COutPoint outpoint1(txHash1, 0); 133 1 : COutPoint outpoint2(txHash2, 0); 134 1 : COutPoint outpoint3(txHash1, 1); // Same hash, different index 135 : 136 1 : const uint256 requestId1 = instantsend::GenInputLockRequestId(outpoint1); 137 1 : const uint256 requestId2 = instantsend::GenInputLockRequestId(outpoint2); 138 1 : const uint256 requestId3 = instantsend::GenInputLockRequestId(outpoint3); 139 : 140 : // All should be different 141 1 : BOOST_CHECK(requestId1 != requestId2); 142 1 : BOOST_CHECK(requestId1 != requestId3); 143 1 : BOOST_CHECK(requestId2 != requestId3); 144 1 : } 145 : 146 148 : BOOST_AUTO_TEST_CASE(geninputlockrequestid_only_outpoint_matters) 147 : { 148 : // Critical test: Verify that only the COutPoint is hashed, not scriptSig or nSequence 149 : // This validates the fix where CTxIn was incorrectly used before 150 1 : const uint256 txHash = uint256S("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"); 151 1 : const uint32_t outputIndex = 3; 152 : 153 1 : COutPoint outpoint(txHash, outputIndex); 154 : 155 : // Create two CTxIn objects with the same prevout but different scriptSig and nSequence 156 1 : CTxIn txin1; 157 1 : txin1.prevout = outpoint; 158 1 : txin1.scriptSig = CScript() << OP_1 << OP_2; 159 1 : txin1.nSequence = 0xFFFFFFFF; 160 : 161 1 : CTxIn txin2; 162 1 : txin2.prevout = outpoint; 163 1 : txin2.scriptSig = CScript() << OP_3 << OP_4 << OP_5; // Different scriptSig 164 1 : txin2.nSequence = 0x12345678; // Different nSequence 165 : 166 : // The request IDs should be identical because they share the same prevout (COutPoint) 167 1 : const uint256 requestId1 = instantsend::GenInputLockRequestId(txin1.prevout); 168 1 : const uint256 requestId2 = instantsend::GenInputLockRequestId(txin2.prevout); 169 : 170 1 : BOOST_CHECK(requestId1 == requestId2); 171 : 172 : // Also verify against the direct outpoint 173 1 : const uint256 requestId3 = instantsend::GenInputLockRequestId(outpoint); 174 1 : BOOST_CHECK(requestId1 == requestId3); 175 1 : } 176 : 177 148 : BOOST_AUTO_TEST_CASE(geninputlockrequestid_serialization_format) 178 : { 179 : // Test that the serialization format is: SerializeHash(pair("inlock", outpoint)) 180 1 : const uint256 txHash = uint256S("0x0000000000000000000000000000000000000000000000000000000000000001"); 181 1 : const uint32_t outputIndex = 0; 182 : 183 1 : COutPoint outpoint(txHash, outputIndex); 184 : 185 : // Calculate the expected hash manually 186 1 : const uint256 expectedHash = ::SerializeHash(std::make_pair(std::string_view("inlock"), outpoint)); 187 : 188 : // Get the actual hash from the function 189 1 : const uint256 actualHash = instantsend::GenInputLockRequestId(outpoint); 190 : 191 1 : BOOST_CHECK(actualHash == expectedHash); 192 1 : } 193 : 194 148 : BOOST_AUTO_TEST_CASE(geninputlockrequestid_edge_cases) 195 : { 196 : // Test edge cases: null hash, max index 197 1 : COutPoint nullOutpoint(uint256(), 0); 198 1 : COutPoint maxIndexOutpoint(uint256::ONE, COutPoint::NULL_INDEX); 199 : 200 1 : const uint256 nullRequestId = instantsend::GenInputLockRequestId(nullOutpoint); 201 1 : const uint256 maxIndexRequestId = instantsend::GenInputLockRequestId(maxIndexOutpoint); 202 : 203 : // Both should produce valid (non-null) request IDs 204 1 : BOOST_CHECK(!nullRequestId.IsNull()); 205 1 : BOOST_CHECK(!maxIndexRequestId.IsNull()); 206 : 207 : // And they should be different from each other 208 1 : BOOST_CHECK(nullRequestId != maxIndexRequestId); 209 1 : } 210 : 211 146 : BOOST_AUTO_TEST_SUITE_END()