4, Today, we continue with an in-depth analysis (Part 2). The relationship between cold wallets and hackers focuses on the mechanism of summarizing (hashing) the inflows and outflows of the transaction pieces of paper.
There has been another leakage incident. However, there is an effective method that can surely prevent leakage (hacking). But why does this method work? We will explain the theoretical aspects thoroughly and reveal the solution at the end of section 9.
In the case of cryptocurrency coins, the process of summarizing (hashing) the inflows and outflows of the transaction pieces of paper is handled by the following class (class CTransactionSignatureSerializer).
* This interpretation strictly refers to the older method.
* In Bitcoin, old addresses starting with 1 or 3 (non-SegWit) have been processed by this class. Originally, it was a different format, but it has since been replaced by this class. The logic of the summary (hash) remains unchanged. If it were to change, the hashes of old transactions would no longer match, and synchronization would fail.
template <class T> class CTransactionSignatureSerializer { private: const T& txTo; //!< reference to the spending transaction (the one being serialized) const CScript& scriptCode; //!< output script being consumed const unsigned int nIn; //!< input index of txTo being signed const bool fAnyoneCanPay; //!< whether the hashtype has the SIGHASH_ANYONECANPAY flag set const bool fHashSingle; //!< whether the hashtype is SIGHASH_SINGLE const bool fHashNone; //!< whether the hashtype is SIGHASH_NONE public: CTransactionSignatureSerializer(const T& txToIn, const CScript& scriptCodeIn, unsigned int nInIn, int nHashTypeIn) : txTo(txToIn), scriptCode(scriptCodeIn), nIn(nInIn), fAnyoneCanPay(!!(nHashTypeIn & SIGHASH_ANYONECANPAY)), fHashSingle((nHashTypeIn & 0x1f) == SIGHASH_SINGLE), fHashNone((nHashTypeIn & 0x1f) == SIGHASH_NONE) {} /** Serialize the passed scriptCode, skipping OP_CODESEPARATORs */ template<typename S> void SerializeScriptCode(S &s) const { CScript::const_iterator it = scriptCode.begin(); CScript::const_iterator itBegin = it; opcodetype opcode; unsigned int nCodeSeparators = 0; while (scriptCode.GetOp(it, opcode)) { if (opcode == OP_CODESEPARATOR) nCodeSeparators++; } ::WriteCompactSize(s, scriptCode.size() - nCodeSeparators); it = itBegin; while (scriptCode.GetOp(it, opcode)) { if (opcode == OP_CODESEPARATOR) { s.write((char*)&itBegin[0], it-itBegin-1); itBegin = it; } } if (itBegin != scriptCode.end()) s.write((char*)&itBegin[0], it-itBegin); } /** Serialize an input of txTo */ template<typename S> void SerializeInput(S &s, unsigned int nInput) const { // In case of SIGHASH_ANYONECANPAY, only the input being signed is serialized if (fAnyoneCanPay) nInput = nIn; // Serialize the prevout ::Serialize(s, txTo.vin[nInput].prevout); // Serialize the script if (nInput != nIn) // Blank out other inputs' signatures ::Serialize(s, CScript()); else SerializeScriptCode(s); // Serialize the nSequence if (nInput != nIn && (fHashSingle || fHashNone)) // let the others update at will ::Serialize(s, (int)0); else ::Serialize(s, txTo.vin[nInput].nSequence); } /** Serialize an output of txTo */ template<typename S> void SerializeOutput(S &s, unsigned int nOutput) const { if (fHashSingle && nOutput != nIn) // Do not lock-in the txout payee at other indices as txin ::Serialize(s, CTxOut()); else ::Serialize(s, txTo.vout[nOutput]); } /** Serialize txTo */ template<typename S> void Serialize(S &s) const { // Serialize nVersion ::Serialize(s, txTo.nVersion); // Serialize vin unsigned int nInputs = fAnyoneCanPay ? 1 : txTo.vin.size(); ::WriteCompactSize(s, nInputs); for (unsigned int nInput = 0; nInput < nInputs; nInput++) SerializeInput(s, nInput); // Serialize vout unsigned int nOutputs = fHashNone ? 0 : (fHashSingle ? nIn+1 : txTo.vout.size()); ::WriteCompactSize(s, nOutputs); for (unsigned int nOutput = 0; nOutput < nOutputs; nOutput++) SerializeOutput(s, nOutput); // Serialize nLockTime ::Serialize(s, txTo.nLockTime); } };
So... this mechanism has been around since the early days, and it seems hackers have thoroughly studied this part. This is one cause of the sudden disappearance of balances, for example, the phenomenon where the balance becomes zero despite having implemented reliable security measures.
Naturally, the probability of this occurring is very low, but it is comparable to the chance of being involved in a catastrophic airplane crash. In other words, it is not a level of probability that can be ignored due to its astronomical rarity. For instance, the collision of hashes has a probability of 1/2^256, which is low enough to be considered non-occurring.
Therefore, especially for large amounts, it is necessary not to rely on a single address but to distribute the balance. This is the first countermeasure. This is a matter of probability, not security. In case of an emergency, it is necessary to ensure that only a small address incurs damage.
By the way, the idea that small amounts will not be targeted should be discarded. Hackers are constantly targeting potentially lucrative locations 24 hours a day. There is also fierce competition among hackers. Due to the side effects of CTransactionSignatureSerializer, even small amounts can become targets. As soon as such a small amount becomes hackable, even if it is just 1000 yen, it will be siphoned off by hackers.