暗号通貨系コインでは、取引用の紙片の出入りを要約(ハッシュ)する処理を以下のクラス(class CTransactionSignatureSerializer)になります。
※ 厳密には古い方という解釈になります。
※ ビットコインでは、従来からある1または3(非SegWit)から始まる古くからのアドレスが以下のクラスで処理されます。もともとは別の形だったのですが、このクラスに置き換えられていました。要約(ハッシュ)の論理は変わりません。もし変わってしまったら古い取引のハッシュが合わなくなって同期ができなくなります。
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);
}
};
それで……、初期の頃からある仕組みとなりますので、ハッカーはこの部分を研究し尽くした感じがあるのです。それで、忽然と残高が消えてしまう原因、例えば確実なセキュリティ対策を施していたにも関わらず残高が0になってしまう現象の原因が、ここに一つありました。
当然ながら、その発生確率は非常に低いですが、偶然にも凄惨な航空機事故に巻き込まれてしまう位の確率はあります。つまり、天文学的に小さな確率ゆえに無視できるという水準ではありません。その例としてハッシュの衝突は1/2^256なので気にする必要はありません。ここまで低い確率なら発生しないとみなすことができます。
よって特に高額の場合は、一つのアドレスを過信せずに残高を分散させておく必要があります。まず、これが一つ目の対策です。これは確率の問題であって、セキュリティの問題ではありません。万一の場合でも一つの少額なアドレスだけで被害が済むように対策する必要があります。
ところで、少額なら狙われない。その考えも捨ててください。ハッカーは狙えそうな高額な場所をくまなく24時間仕掛けている状況です。ちなみにハッカー同士でも激しい競争になっています。そして、CTransactionSignatureSerializerの副作用により、少額がターゲットに入るのです。それで、その少額がハッキング可能となった瞬間、それが例え1000円であろうとも、ハッカーに引き抜かれます。
また流出事故が起きてしまいました。ところが、それは本当にセキュリティの問題でしょうか?
実は確率の問題も絡んでおり、そのような場合からの流出(ハッキング)を確実に防ぐ効果的な方法があります。なぜその方法で防げるのか。理論的な側面を詳しく説明し、考察(その7)の最後に解決策をお教えします。
