本日は、その3で考察したCTransactionSignatureSerializerとCTransactionが関連する、CTransactionのシリアライズ(UnserializeTransactionとSerializeTransaction)を観察しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
template<typename Stream, typename TxType> inline void UnserializeTransaction(TxType& tx, Stream& s) { const bool fAllowWitness = !(s.GetVersion() & SERIALIZE_TRANSACTION_NO_WITNESS); const bool fAllowMWEB = !(s.GetVersion() & SERIALIZE_NO_MWEB); s >> tx.nVersion; unsigned char flags = 0; tx.vin.clear(); tx.vout.clear(); /* Try to read the vin. In case the dummy is there, this will be read as an empty vector. */ s >> tx.vin; if (tx.vin.size() == 0 && fAllowWitness) { /* We read a dummy or an empty vin. */ s >> flags; if (flags != 0) { s >> tx.vin; s >> tx.vout; } } else { /* We read a non-empty vin. Assume a normal vout follows. */ s >> tx.vout; } if ((flags & 1) && fAllowWitness) { /* The witness flag is present, and we support witnesses. */ flags ^= 1; for (size_t i = 0; i < tx.vin.size(); i++) { s >> tx.vin[i].scriptWitness.stack; } if (!tx.HasWitness()) { /* It's illegal to encode witnesses when all witness stacks are empty. */ throw std::ios_base::failure("Superfluous witness record"); } } if ((flags & 8) && fAllowMWEB) { /* The MWEB flag is present, and we support MWEB. */ flags ^= 8; s >> tx.mweb_tx; if (tx.mweb_tx.IsNull()) { if (tx.vout.empty()) { /* It's illegal to include a HogEx with no outputs. */ throw std::ios_base::failure("Missing HogEx output"); } /* If the MWEB flag is set, but there are no MWEB txs, assume HogEx txn. */ tx.m_hogEx = true; } } if (flags) { /* Unknown flag in the serialization */ throw std::ios_base::failure("Unknown transaction optional data"); } s >> tx.nLockTime; } template<typename Stream, typename TxType> inline void SerializeTransaction(const TxType& tx, Stream& s) { const bool fAllowWitness = !(s.GetVersion() & SERIALIZE_TRANSACTION_NO_WITNESS); const bool fAllowMWEB = !(s.GetVersion() & SERIALIZE_NO_MWEB); s << tx.nVersion; unsigned char flags = 0; // Consistency check if (fAllowWitness) { /* Check whether witnesses need to be serialized. */ if (tx.HasWitness()) { flags |= 1; } } if (fAllowMWEB) { if (tx.m_hogEx || !tx.mweb_tx.IsNull()) { flags |= 8; } } if (flags) { /* Use extended format in case witnesses are to be serialized. */ std::vector<CTxIn> vinDummy; s << vinDummy; s << flags; } s << tx.vin; s << tx.vout; if (flags & 1) { for (size_t i = 0; i < tx.vin.size(); i++) { s << tx.vin[i].scriptWitness.stack; } } if (flags & 8) { s << tx.mweb_tx; } s << tx.nLockTime; } |
ビットコインは、複数の機能を単一の関数にまとめる傾向があります。この傾向は、UnserializeTransactionやSerializeTransactionにおいても見られ、これらの関数には複数の機能が組み込まれています。そのため、これらのコードを理解するには、以前の古いコアに実装されていたSignatureHashのコードが頭にないと読めません。
そこで、CTransactionSignatureSerializerが作用する機能だけに絞ってUnserializeTransactionやSerializeTransactionを整理すると以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
template<typename Stream, typename TxType> inline void UnserializeTransaction(TxType& tx, Stream& s) { s >> tx.nVersion; s >> tx.vin; s >> tx.vout; s >> tx.nLockTime; } template<typename Stream, typename TxType> inline void SerializeTransaction(const TxType& tx, Stream& s) { s << tx.nVersion; s << tx.vin; s << tx.vout; s << tx.nLockTime; } |
あれっ、という感じですよね。ずいぶんとすっきりしました。CTransactionSignatureSerializerが作用する機能だけなら、これだけでもビットコインは問題なく動作します。もちろん、マルチシグでさえ動作します。
ここで注目すべきは、ストリーム演算子(<<および>>)の使用方法です。>>演算子はデータをストリームsから取り出す役割を果たします。具体的には、UnserializeTransaction関数は、コード中にある4つの要素を順番にsから取り出します。反対に、<<演算子はデータをストリームsに送り出す役割を持ちます。つまり、SerializeTransaction関数は、コード中の4つの要素を順番にsへ書き込む処理を行います。
次に、これら4つの要素のうち、取引情報を保持するのはvinとvoutです。これらは可変長配列(std::vector)として定義されています。vinは入力(所有する残高をまとめる部分)であり、voutは出力(受け取り先および余剰分をまとめる部分)を表します。コインを送る際に複数の送付先を指定できる理由は、このvoutに複数の送付先を設定できるからです。具体的には、vinにはトランザクションの入力情報が、voutにはトランザクションの出力情報が格納され、各々がそれぞれの目的に応じて使用されます。
これらを念頭に置き、CTxIn(vin)とCTxOut(vout)をみていきましょう。
また流出事故が起きてしまいました。ところが、それは本当にセキュリティの問題でしょうか?
実は確率の問題も絡んでおり、そのような場合からの流出(ハッキング)を確実に防ぐ効果的な方法があります。なぜその方法で防げるのか。理論的な側面を詳しく説明し、考察(その7)の最後に解決策をお教えします。