本日は、その3で考察したCTransactionSignatureSerializerとCTransactionが関連する、CTransactionのシリアライズ(UnserializeTransactionとSerializeTransaction)を観察しましょう。
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 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を整理すると以下のようになります。
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)の最後に解決策をお教えします。
