FULLFEATURE: Separate serialization tx witness and the rest

This commit is contained in:
Pieter Wuille 2015-05-11 15:52:17 -07:00 committed by Matt Corallo
parent a04ea8a812
commit 663e9bd329
10 changed files with 164 additions and 51 deletions

View file

@ -585,19 +585,19 @@ static void MutateTxWithdrawSign(CMutableTransaction& tx, const string& flagStr)
merkleBlock.header.SetBitcoinBlock();
ssProof >> merkleBlock;
CDataStream ssTx(txData, SER_NETWORK, PROTOCOL_VERSION);
CDataStream ssTx(txData, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_VERSION_MASK_BITCOIN_TX);
CTransaction txBTC;
ssTx >> txBTC;
CDataStream ssCoinbaseTx(coinbaseTxData, SER_NETWORK, PROTOCOL_VERSION);
CDataStream ssCoinbaseTx(coinbaseTxData, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_VERSION_MASK_BITCOIN_TX);
CTransaction coinbaseTxBTC;
ssCoinbaseTx >> coinbaseTxBTC;
vector<uint256> transactionHashes;
if (merkleBlock.txn.ExtractMatches(transactionHashes) != merkleBlock.header.hashMerkleRoot ||
transactionHashes.size() != 2 ||
transactionHashes[0] != coinbaseTxBTC.GetHash() ||
transactionHashes[1] != txBTC.GetHash())
transactionHashes[0] != coinbaseTxBTC.GetBitcoinHash() ||
transactionHashes[1] != txBTC.GetBitcoinHash())
throw runtime_error("txoutproof is invalid or did not match tx");
if (nOut < 0 || (unsigned int) nOut >= txBTC.vout.size())

View file

@ -1971,6 +1971,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
dsMerkleBlockDS << (CMerkleBlock(block, txSet));
scriptSig.PushWithdraw(std::vector<unsigned char>(dsMerkleBlockDS.begin(), dsMerkleBlockDS.end()));
uint256 witnessHash(dsTx.GetWitnessHash());
scriptSig << std::vector<unsigned char>(witnessHash.begin(), witnessHash.end());
scriptSig << OP_1 << OP_1;
proofTx.vout.push_back(CTxOut(0, CScript()));

View file

@ -23,7 +23,7 @@ CMerkleBlock::CMerkleBlock(const CBlock& block, CBloomFilter& filter)
for (unsigned int i = 0; i < block.vtx.size(); i++)
{
const uint256& hash = block.vtx[i].GetHash();
const uint256& hash = block.vtx[i].GetFullHash();
if (filter.IsRelevantAndUpdate(block.vtx[i]))
{
vMatch.push_back(true);
@ -54,7 +54,7 @@ CMerkleBlock::CMerkleBlock(const CBlock& block, const std::set<uint256>& txids)
vMatch.push_back(true);
else
vMatch.push_back(false);
vHashes.push_back(hash);
vHashes.push_back(block.vtx[i].GetFullHash());
}
txn = CPartialMerkleTree(vHashes, vMatch);

View file

@ -65,8 +65,12 @@ uint256 CBlock::BuildMerkleTree(bool* fMutated) const
*/
vMerkleTree.clear();
vMerkleTree.reserve(vtx.size() * 2 + 16); // Safe upper bound for the number of total nodes.
for (std::vector<CTransaction>::const_iterator it(vtx.begin()); it != vtx.end(); ++it)
vMerkleTree.push_back(it->GetHash());
for (std::vector<CTransaction>::const_iterator it(vtx.begin()); it != vtx.end(); ++it) {
if (IsBitcoinBlock())
vMerkleTree.push_back(it->GetBitcoinHash());
else
vMerkleTree.push_back(it->GetFullHash());
}
int j = 0;
bool mutated = false;
for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2)

View file

@ -185,6 +185,8 @@ public:
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(*(CBlockHeader*)this);
if (IsBitcoinBlock())
nVersion |= SERIALIZE_VERSION_MASK_BITCOIN_TX;
READWRITE(vtx);
}

View file

@ -18,10 +18,7 @@ CTxOutValue::CTxOutValue()
CTxOutValue::CTxOutValue(CAmount nAmountIn)
{
vchCommitment.resize(nCommitmentSize);
assert(vchCommitment.size() > sizeof(nAmountIn) + 1);
memset(&vchCommitment[0], 0, vchCommitment.size() - sizeof(nAmountIn));
for (size_t i = 0; i < sizeof(nAmountIn); ++i)
vchCommitment[vchCommitment.size() - (i + 1)] = ((nAmountIn >> (i * 8)) & 0xff);
SetToAmount(nAmountIn);
}
CTxOutValue::CTxOutValue(const std::vector<unsigned char>& vchValueCommitmentIn, const std::vector<unsigned char>& vchRangeproofIn)
@ -62,6 +59,14 @@ bool CTxOutValue::IsAmount() const
return !vchCommitment[0];
}
void CTxOutValue::SetToAmount(CAmount nAmount)
{
assert(vchCommitment.size() > sizeof(nAmount) + 1);
memset(&vchCommitment[0], 0, vchCommitment.size() - sizeof(nAmount));
for (size_t i = 0; i < sizeof(nAmount); ++i)
vchCommitment[vchCommitment.size() - (i + 1)] = ((nAmount >> (i * 8)) & 0xff);
}
CAmount CTxOutValue::GetAmount() const
{
assert(IsAmount());
@ -132,15 +137,36 @@ CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.n
uint256 CMutableTransaction::GetHash() const
{
return SerializeHash(*this);
if (IsCoinBase()) {
return SerializeHash(*this, SER_GETHASH, PROTOCOL_VERSION);
} else {
return SerializeHash(*this, SER_GETHASH, PROTOCOL_VERSION | SERIALIZE_VERSION_MASK_NO_WITNESS);
}
}
void CTransaction::UpdateHash() const
{
*const_cast<uint256*>(&hash) = SerializeHash(*this);
bool maybeBitcoinTx = true;
for (unsigned int i = 0; i < vout.size(); i++)
if (!vout[i].nValue.IsAmount())
maybeBitcoinTx = false;
if (maybeBitcoinTx)
*const_cast<uint256*>(&hashBitcoin) = SerializeHash(*this, SER_GETHASH, PROTOCOL_VERSION | SERIALIZE_VERSION_MASK_BITCOIN_TX);
if (IsCoinBase()) {
*const_cast<uint256*>(&hash) = SerializeHash(*this, SER_GETHASH, PROTOCOL_VERSION);
} else {
*const_cast<uint256*>(&hash) = SerializeHash(*this, SER_GETHASH, PROTOCOL_VERSION | SERIALIZE_VERSION_MASK_NO_WITNESS);
}
*const_cast<uint256*>(&hashWitness) = SerializeHash(*this, SER_GETHASH, PROTOCOL_VERSION | SERIALIZE_VERSION_MASK_ONLY_WITNESS);
// Update full hash combining the normalized txid with the hash of the witness
CHash256 hasher;
hasher.Write((unsigned char*)&hash, hash.size());
hasher.Write((unsigned char*)&hashWitness, hashWitness.size());
hasher.Finalize((unsigned char*)&hashFull);
}
CTransaction::CTransaction() : hash(0), nVersion(CTransaction::CURRENT_VERSION), vin(), nTxFee(0), vout(), nLockTime(0) { }
CTransaction::CTransaction() : hash(0), hashFull(0), nVersion(CTransaction::CURRENT_VERSION), vin(), nTxFee(0), vout(), nLockTime(0) { }
CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), nTxFee(tx.nTxFee), vout(tx.vout), nLockTime(tx.nLockTime) {
UpdateHash();
@ -153,6 +179,7 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) {
*const_cast<std::vector<CTxOut>*>(&vout) = tx.vout;
*const_cast<unsigned int*>(&nLockTime) = tx.nLockTime;
*const_cast<uint256*>(&hash) = tx.hash;
*const_cast<uint256*>(&hashFull) = tx.hashFull;
return *this;
}

View file

@ -7,10 +7,16 @@
#define BITCOIN_PRIMITIVES_TRANSACTION_H
#include "amount.h"
#include "hash.h"
#include "script/script.h"
#include "serialize.h"
#include "uint256.h"
#define SERIALIZE_VERSION_MASK_NO_WITNESS 0x40000000
#define SERIALIZE_VERSION_MASK_ONLY_WITNESS 0x80000000
#define SERIALIZE_VERSION_MASK_BITCOIN_TX 0x20000000
#define SERIALIZE_VERSION_MASK_PREHASH 0x10000000
class CTxOutValue
{
public:
@ -29,9 +35,29 @@ public:
template<typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion)
{
READWRITE(REF(CFlatData(&vchCommitment[0], &vchCommitment[nCommitmentSize])));
READWRITE(vchRangeproof);
READWRITE(vchNonceCommitment);
bool fBitcoinTx = nVersion & SERIALIZE_VERSION_MASK_BITCOIN_TX;
bool fWitness = (nVersion & SERIALIZE_VERSION_MASK_NO_WITNESS) == 0;
bool fOnlyWitness = nVersion & SERIALIZE_VERSION_MASK_ONLY_WITNESS;
assert(!fBitcoinTx || IsAmount() || (IsNull() && ser_action.ForRead()));
if (fBitcoinTx) {
CAmount amount = 0;
if (!ser_action.ForRead())
amount = GetAmount();
READWRITE(amount);
if (ser_action.ForRead())
SetToAmount(amount);
} else {
if (!fOnlyWitness) READWRITE(REF(CFlatData(&vchCommitment[0], &vchCommitment[nCommitmentSize])));
if (fWitness) {
if (nVersion & SERIALIZE_VERSION_MASK_PREHASH) {
uint256 prehash = (CHashWriter(nType, nVersion) << vchRangeproof << vchNonceCommitment).GetHash();
READWRITE(prehash);
} else {
READWRITE(vchRangeproof);
READWRITE(vchNonceCommitment);
}
}
}
}
bool IsValid() const;
@ -39,6 +65,7 @@ public:
bool IsAmount() const;
CAmount GetAmount() const;
void SetToAmount(CAmount nAmount);
friend bool operator==(const CTxOutValue& a, const CTxOutValue& b);
friend bool operator!=(const CTxOutValue& a, const CTxOutValue& b);
@ -105,9 +132,12 @@ public:
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(prevout);
READWRITE(scriptSig);
READWRITE(nSequence);
bool fWitness = (nVersion & SERIALIZE_VERSION_MASK_NO_WITNESS) == 0;
bool fOnlyWitness = nVersion & SERIALIZE_VERSION_MASK_ONLY_WITNESS;
assert((nType != SER_GETHASH && !fOnlyWitness && fWitness) || nType == SER_GETHASH);
if (!fOnlyWitness) READWRITE(prevout);
if (fWitness) READWRITE(scriptSig);
if (!fOnlyWitness) READWRITE(nSequence);
}
friend bool operator==(const CTxIn& a, const CTxIn& b)
@ -201,6 +231,9 @@ class CTransaction
private:
/** Memory only. */
const uint256 hash;
const uint256 hashWitness; // Just witness
const uint256 hashFull; // Including witness
const uint256 hashBitcoin; // For Bitcoin Transactions
void UpdateHash() const;
public:
@ -229,12 +262,16 @@ public:
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(*const_cast<int32_t*>(&this->nVersion));
nVersion = this->nVersion;
READWRITE(*const_cast<std::vector<CTxIn>*>(&vin));
READWRITE(*const_cast<CAmount*>(&nTxFee));
READWRITE(*const_cast<std::vector<CTxOut>*>(&vout));
READWRITE(*const_cast<uint32_t*>(&nLockTime));
bool fWitness = (nVersion & SERIALIZE_VERSION_MASK_NO_WITNESS) == 0;
bool fOnlyWitness = nVersion & SERIALIZE_VERSION_MASK_ONLY_WITNESS;
bool fBitcoinTx = nVersion & SERIALIZE_VERSION_MASK_BITCOIN_TX;
assert(!fBitcoinTx || (fBitcoinTx && fWitness && !fOnlyWitness));
assert((nType != SER_GETHASH && !fOnlyWitness && fWitness) || nType == SER_GETHASH);
if (!fOnlyWitness) READWRITE(*const_cast<int32_t*>(&this->nVersion));
READWRITE(*const_cast<std::vector<CTxIn>*>(&vin));
if (!fBitcoinTx && !fOnlyWitness) READWRITE(*const_cast<CAmount*>(&nTxFee));
if (!fOnlyWitness) READWRITE(*const_cast<std::vector<CTxOut>*>(&vout));
if (!fOnlyWitness) READWRITE(*const_cast<uint32_t*>(&nLockTime));
if (ser_action.ForRead())
UpdateHash();
}
@ -243,10 +280,27 @@ public:
return vin.empty() && vout.empty();
}
/* Transaction hash without witness information */
const uint256& GetHash() const {
return hash;
}
/* Transaction hash including witness information */
const uint256& GetFullHash() const {
return hashFull;
}
/* Hash of just witness information */
const uint256& GetWitnessHash() const {
return hashWitness;
}
/* Transaction hash including witness information */
const uint256& GetBitcoinHash() const {
assert(hashBitcoin != 0);
return hashBitcoin;
}
// Compute priority, given priority of inputs and (optionally) tx size
double ComputePriority(double dPriorityInputs, unsigned int nTxSize=0) const;
@ -287,12 +341,21 @@ struct CMutableTransaction
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(this->nVersion);
nVersion = this->nVersion;
READWRITE(vin);
READWRITE(nTxFee);
READWRITE(vout);
READWRITE(nLockTime);
bool fWitness = (nVersion & SERIALIZE_VERSION_MASK_NO_WITNESS) == 0;
bool fOnlyWitness = nVersion & SERIALIZE_VERSION_MASK_ONLY_WITNESS;
bool fBitcoinTx = nVersion & SERIALIZE_VERSION_MASK_BITCOIN_TX;
assert(!fBitcoinTx);
assert((nType != SER_GETHASH && !fOnlyWitness && fWitness) || nType == SER_GETHASH);
if (!fOnlyWitness) READWRITE(this->nVersion);
READWRITE(vin);
if (!fOnlyWitness) READWRITE(nTxFee);
if (!fOnlyWitness) READWRITE(vout);
if (!fOnlyWitness) READWRITE(nLockTime);
}
bool IsCoinBase() const
{
return (vin.size() == 1 && vin[0].prevout.IsNull());
}
/** Compute the hash of this CMutableTransaction. This is computed on the

View file

@ -1416,7 +1416,7 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
#endif
CTransaction locktx;
CDataStream locktxStream(vlockTx, SER_NETWORK, PROTOCOL_VERSION);
CDataStream locktxStream(vlockTx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_VERSION_MASK_BITCOIN_TX);
locktxStream >> locktx;
if (!locktxStream.empty())
return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX);
@ -1425,16 +1425,16 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
if (nlocktxOut < 0 || (unsigned int)nlocktxOut >= locktx.vout.size())
return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX);
if (locktx.GetHash() != txHashes[1])
if (locktx.GetBitcoinHash() != txHashes[1])
return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_LOCKTX);
CTransaction coinbasetx;
CDataStream coinbasetxStream(vlockCoinbaseTx, SER_NETWORK, PROTOCOL_VERSION);
CDataStream coinbasetxStream(vlockCoinbaseTx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_VERSION_MASK_BITCOIN_TX);
coinbasetxStream >> coinbasetx;
if (!coinbasetxStream.empty())
return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLOCK);
if (coinbasetx.GetHash() != txHashes[0] || !coinbasetx.IsCoinBase())
if (coinbasetx.GetBitcoinHash() != txHashes[0] || !coinbasetx.IsCoinBase())
return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_BLOCK);
valtype vcoinbaseHeight;
CScript::const_iterator coinbasepc = coinbasetx.vin[0].scriptSig.begin();
@ -1501,7 +1501,7 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
if (withdrawOutput.nValue.GetAmount() < withdrawVal)
return set_error(serror, SCRIPT_ERR_WITHDRAW_VERIFY_OUTPUT);
uint256 locktxHash = locktx.GetHash();
uint256 locktxHash = locktx.GetBitcoinHash();
std::vector<unsigned char> vlocktxHash(locktxHash.begin(), locktxHash.end());
CScript expectedWithdrawScriptPubKeyStart = CScript() << OP_IF << nLockHeight
<< std::vector<unsigned char>(vlocktxHash.rbegin(), vlocktxHash.rend()) << nlocktxOut
@ -1621,11 +1621,18 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
if (proofType == 1) { // Double-spent withdraw
// We need to have complete proof that two transactions double-spent each other:
// So we read the following from the stack:
// 9. merkle block with tx we are proving against (ie our input tx)
// 10. the full transaction of the original withdraw
// 11. the input index in the transaction above which shows the double-spend
// 12. the transaction which is spent in the above input
// 13. merkle block containing the original withdraw tx, required only if they are not in the same block
// 9. witness hash of the tx we are proving against (ie our input tx)
// 10. merkle block with tx we are proving against (ie our input tx)
// 11. the full transaction of the original withdraw
// 12. the input index in the transaction above which shows the double-spend
// 13. the transaction which is spent in the above input
// 14. merkle block containing the original withdraw tx, required only if they are not in the same block
if (stack.size() < size_t(-(stackReadPos)))
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
valtype vInputTxWitnessHash = stacktop(stackReadPos--);
if (vInputTxWitnessHash.size() != 32)
return set_error(serror, SCRIPT_ERR_REORG_VERIFY_FORMAT);
valtype vmerkleBlockInputTx;
if (!WithdrawProofReadStackItem(stack, fRequireMinimal, &stackReadPos, vmerkleBlockInputTx))
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
@ -1655,7 +1662,14 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
if (inputTxHashes.size() != 1 && inputTxHashes.size() != 2)
return set_error(serror, SCRIPT_ERR_REORG_VERIFY_FRAUD_BLOCK);
if (inputTxHashes[0] != checker.GetPrevOut().hash)
uint256 inputTxWitnessHash(vInputTxWitnessHash);
uint256 inputTxFullHash(checker.GetPrevOut().hash);
CHash256 hasher;
hasher.Write(inputTxFullHash.begin(), inputTxFullHash.size());
hasher.Write(inputTxWitnessHash.begin(), inputTxWitnessHash.size());
hasher.Finalize(inputTxFullHash.begin());
if (inputTxHashes[0] != inputTxFullHash)
return set_error(serror, SCRIPT_ERR_REORG_VERIFY_FRAUD_BLOCK);
CTransaction originalWithdrawTx;
@ -1681,9 +1695,9 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
if (originalWithdrawHashes.size() != 1 || merkleBlockOriginalWithdrawTx.header.GetHash() == merkleBlockInputTx.header.GetHash())
return set_error(serror, SCRIPT_ERR_REORG_VERIFY_FRAUD_ORIG_BLOCK);
if (originalWithdrawHashes[0] != originalWithdrawTx.GetHash())
if (originalWithdrawHashes[0] != originalWithdrawTx.GetFullHash())
return set_error(serror, SCRIPT_ERR_REORG_VERIFY_FRAUD_ORIG_BLOCK);
} else if (inputTxHashes[1] != originalWithdrawTx.GetHash())
} else if (inputTxHashes[1] != originalWithdrawTx.GetFullHash())
return set_error(serror, SCRIPT_ERR_REORG_VERIFY_FRAUD_BLOCK);
int noriginalWithdrawTxIn = CScriptNum(voriginalWithdrawTxInIndex, fRequireMinimal).getint();
@ -1830,9 +1844,9 @@ public:
void SerializeOutput(S &s, unsigned int nOutput, int nType, int nVersion) const {
if (fHashSingle && nOutput != nIn)
// Do not lock-in the txout payee at other indices as txin
::Serialize(s, CTxOut(), nType, nVersion);
::Serialize(s, CTxOut(), nType, nVersion | SERIALIZE_VERSION_MASK_PREHASH);
else
::Serialize(s, txTo.vout[nOutput], nType, nVersion);
::Serialize(s, txTo.vout[nOutput], nType, nVersion | SERIALIZE_VERSION_MASK_PREHASH);
}
/** Serialize txTo */

View file

@ -473,12 +473,12 @@ COutPoint CScript::GetWithdrawSpent() const
if (!PopWithdrawPush(pushes, &vTx))
return COutPoint();
CTransaction tx;
CDataStream(vTx, SER_NETWORK, PROTOCOL_VERSION) >> tx;
CDataStream(vTx, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_VERSION_MASK_BITCOIN_TX) >> tx;
if (ntxOut < 0 || (unsigned int)ntxOut >= tx.vout.size())
return COutPoint();
return COutPoint(tx.GetHash(), ntxOut);
return COutPoint(tx.GetBitcoinHash(), ntxOut);
} catch (std::exception& e) {
return COutPoint();
}

View file

@ -2488,7 +2488,7 @@ int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
// Make sure the merkle branch connects to this block
if (!fMerkleVerified)
{
if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != pindex->hashMerkleRoot)
if (CBlock::CheckMerkleBranch(GetFullHash(), vMerkleBranch, nIndex) != pindex->hashMerkleRoot)
return 0;
fMerkleVerified = true;
}