From 4c8f7851854874452742773e408a70ec184da011 Mon Sep 17 00:00:00 2001 From: Mark Friedenbach Date: Sat, 11 Mar 2017 06:43:11 -0800 Subject: [PATCH] [Hard-Fork] CT/CA: Put the asset surjection proof, value range proof, and issuance rangeproofs under the witness commitment --- src/bench/verify_script.cpp | 4 +- src/bitcoin-tx.cpp | 2 +- src/blind.cpp | 18 +-- src/blind.h | 2 +- src/coins.h | 1 - src/consensus/merkle.cpp | 9 +- src/consensus/merkle.h | 2 +- src/core_memusage.h | 7 +- src/core_write.cpp | 4 +- src/policy/policy.cpp | 8 +- src/primitives/transaction.cpp | 82 ++++++++++++-- src/primitives/transaction.h | 188 ++++++++++++++++++++------------ src/rpc/mining.cpp | 1 + src/rpc/rawtransaction.cpp | 21 ++-- src/script/bitcoinconsensus.cpp | 4 +- src/script/sign.cpp | 8 +- src/test/blind_tests.cpp | 12 +- src/test/script_tests.cpp | 3 +- src/test/sigopcount_tests.cpp | 5 +- src/test/transaction_tests.cpp | 13 ++- src/validation.cpp | 57 ++++++---- src/wallet/wallet.cpp | 26 +++-- src/wallet/wallet.h | 2 +- 23 files changed, 314 insertions(+), 165 deletions(-) diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index d2cd7ce3f3..22c89dc2e0 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -67,7 +67,7 @@ static void VerifyScriptBench(benchmark::State& state) CScript witScriptPubkey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(pubkeyHash) << OP_EQUALVERIFY << OP_CHECKSIG; CTransaction txCredit = BuildCreditingTransaction(scriptPubKey); CMutableTransaction txSpend = BuildSpendingTransaction(scriptSig, txCredit); - CScriptWitness& witness = txSpend.vin[0].scriptWitness; + CScriptWitness& witness = txSpend.wit.vtxinwit[0].scriptWitness; witness.stack.emplace_back(); key.Sign(SignatureHash(witScriptPubkey, txSpend, 0, SIGHASH_ALL, txCredit.vout[0].nValue, SIGVERSION_WITNESS_V0), witness.stack.back(), 0); witness.stack.back().push_back(static_cast(SIGHASH_ALL)); @@ -79,7 +79,7 @@ static void VerifyScriptBench(benchmark::State& state) bool success = VerifyScript( txSpend.vin[0].scriptSig, txCredit.vout[0].scriptPubKey, - &txSpend.vin[0].scriptWitness, + &txSpend.wit.vtxinwit[0].scriptWitness, flags, MutableTransactionNoWithdrawsSignatureChecker(&txSpend, 0, txCredit.vout[0].nValue), &err); diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 1cf77af604..be65f63807 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -710,7 +710,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) sigdata = CombineSignatures(prevPubKey, MutableTransactionNoWithdrawsSignatureChecker(&mergedTx, i, amount), sigdata, DataFromTransaction(txv, i)); UpdateTransaction(mergedTx, i, sigdata); - if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionNoWithdrawsSignatureChecker(&mergedTx, i, amount))) + if (!VerifyScript(txin.scriptSig, prevPubKey, (mergedTx.wit.vtxinwit.size() > i) ? &mergedTx.wit.vtxinwit[i].scriptWitness : NULL, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionNoWithdrawsSignatureChecker(&mergedTx, i, amount))) fComplete = false; } diff --git a/src/blind.cpp b/src/blind.cpp index 8c65ef6d7b..7463274f6c 100644 --- a/src/blind.cpp +++ b/src/blind.cpp @@ -34,7 +34,7 @@ public: static Blind_ECC_Init ecc_init_on_load; -bool UnblindOutput(const CKey &key, const CTxOut& txout, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out) +bool UnblindOutput(const CKey &key, const CTxOut& txout, const CTxOutWitness& txoutwit, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out) { if (!key.IsValid()) { return false; @@ -57,7 +57,7 @@ bool UnblindOutput(const CKey &key, const CTxOut& txout, CAmount& amount_out, ui return false; if (secp256k1_pedersen_commitment_parse(secp256k1_blind_context, &commit, &txout.nValue.vchCommitment[0]) != 1) return false; - int res = secp256k1_rangeproof_rewind(secp256k1_blind_context, blinding_factor_out.begin(), &amount, msg, &msg_size, nonce.begin(), &min_value, &max_value, &commit, &txout.vchRangeproof[0], txout.vchRangeproof.size(), txout.scriptPubKey.size() ? &txout.scriptPubKey.front() : NULL, txout.scriptPubKey.size(), &gen); + int res = secp256k1_rangeproof_rewind(secp256k1_blind_context, blinding_factor_out.begin(), &amount, msg, &msg_size, nonce.begin(), &min_value, &max_value, &commit, &txoutwit.vchRangeproof[0], txoutwit.vchRangeproof.size(), txout.scriptPubKey.size()? &txout.scriptPubKey.front(): NULL, txout.scriptPubKey.size(), &gen); secp256k1_generator recoveredGen; if (!res || amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount) || msg_size != 64 || secp256k1_generator_generate_blinded(secp256k1_blind_context, &recoveredGen, msg+32, msg+64) != 1 || !memcmp(&gen, &recoveredGen, 33)) { @@ -202,6 +202,10 @@ int BlindOutputs(std::vector& input_blinding_factors, const std::vecto } } + if (tx.wit.vtxoutwit.size() <= nOut) { + tx.wit.vtxoutwit.resize(tx.vout.size()); + } + CTxOutWitness& txoutwit = tx.wit.vtxoutwit[nOut]; output_blinding_factors[nOut] = uint256(std::vector(blindptrs[blindptrs.size()-1], blindptrs[blindptrs.size()-1]+32)); output_asset_blinding_factors[nOut] = uint256(std::vector(assetblindptrs[assetblindptrs.size()-1], assetblindptrs[assetblindptrs.size()-1]+32)); @@ -233,7 +237,7 @@ int BlindOutputs(std::vector& input_blinding_factors, const std::vecto // Prep range proof size_t nRangeProofLen = 5134; // TODO: smarter min_value selection - out.vchRangeproof.resize(nRangeProofLen); + txoutwit.vchRangeproof.resize(nRangeProofLen); // Compose sidechannel message to convey asset info (ID and asset blinds) unsigned char assetsMessage[64]; @@ -241,8 +245,8 @@ int BlindOutputs(std::vector& input_blinding_factors, const std::vecto memcpy(assetsMessage+32, assetblindptrs[assetblindptrs.size()-1], 32); // Sign rangeproof - int res = secp256k1_rangeproof_sign(secp256k1_blind_context, &out.vchRangeproof[0], &nRangeProofLen, 0, &commit, blindptrs.back(), nonce.begin(), std::min(std::max((int)GetArg("-ct_exponent", 0), -1),18), std::min(std::max((int)GetArg("-ct_bits", 32), 1), 51), amount, assetsMessage, sizeof(assetsMessage), out.scriptPubKey.size() ? &out.scriptPubKey.front() : NULL, out.scriptPubKey.size(), &gen); - out.vchRangeproof.resize(nRangeProofLen); + int res = secp256k1_rangeproof_sign(secp256k1_blind_context, &txoutwit.vchRangeproof[0], &nRangeProofLen, 0, &commit, blindptrs.back(), nonce.begin(), std::min(std::max((int)GetArg("-ct_exponent", 0), -1),18), std::min(std::max((int)GetArg("-ct_bits", 32), 1), 51), amount, assetsMessage, sizeof(assetsMessage), out.scriptPubKey.size() ? &out.scriptPubKey.front() : NULL, out.scriptPubKey.size(), &gen); + txoutwit.vchRangeproof.resize(nRangeProofLen); // TODO: do something smarter here assert(res); @@ -263,8 +267,8 @@ int BlindOutputs(std::vector& input_blinding_factors, const std::vecto assert(ret != 0); size_t output_len = secp256k1_surjectionproof_serialized_size(secp256k1_blind_context, &proof); - out.vchSurjectionproof.resize(output_len); - secp256k1_surjectionproof_serialize(secp256k1_blind_context, &out.vchSurjectionproof[0], &output_len, &proof); + txoutwit.vchSurjectionproof.resize(output_len); + secp256k1_surjectionproof_serialize(secp256k1_blind_context, &txoutwit.vchSurjectionproof[0], &output_len, &proof); // Successfully blinded this output nSuccessfullyBlinded++; diff --git a/src/blind.h b/src/blind.h index 892de767d6..7b8e167775 100644 --- a/src/blind.h +++ b/src/blind.h @@ -5,7 +5,7 @@ #include "pubkey.h" #include "primitives/transaction.h" -bool UnblindOutput(const CKey& blinding_key, const CTxOut& txout, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out); +bool UnblindOutput(const CKey& blinding_key, const CTxOut& txout, const CTxOutWitness& txoutwit, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out); /* Returns the number of ouputs that were successfully blinded. * In many cases a `0` can be fixed by adding an additional output. diff --git a/src/coins.h b/src/coins.h index 0040b0fc9a..a05b0b892b 100644 --- a/src/coins.h +++ b/src/coins.h @@ -91,7 +91,6 @@ public: fCoinBase = tx.IsCoinBase(); vout = tx.vout; for (size_t i = 0; i < vout.size(); i++) { - CTxOutWitnessSerializer(vout[i]).SetNull(); vout[i].nNonce.SetNull(); } nHeight = nHeightIn; diff --git a/src/consensus/merkle.cpp b/src/consensus/merkle.cpp index 5f354cbf2c..3b2f6cd587 100644 --- a/src/consensus/merkle.cpp +++ b/src/consensus/merkle.cpp @@ -225,15 +225,14 @@ uint256 BlockMerkleRoot(const CBlock& block, bool* mutated) return ComputeMerkleRoot(leaves, mutated); } -uint256 BlockWitnessMerkleRoot(const CBlock& block, bool* mutated) +uint256 BlockWitnessMerkleRoot(const CBlock& block) { std::vector leaves; leaves.resize(block.vtx.size()); - leaves[0].SetNull(); // The witness hash of the coinbase is 0. - for (size_t s = 1; s < block.vtx.size(); s++) { - leaves[s] = block.vtx[s]->GetWitnessHash(); + for (size_t s = 0; s < block.vtx.size(); s++) { + leaves[s] = block.vtx[s]->ComputeWitnessHash(); } - return ComputeMerkleRoot(leaves, mutated); + return ComputeFastMerkleRoot(leaves); } std::vector BlockMerkleBranch(const CBlock& block, uint32_t position) diff --git a/src/consensus/merkle.h b/src/consensus/merkle.h index 576cba9b74..71eb27ad19 100644 --- a/src/consensus/merkle.h +++ b/src/consensus/merkle.h @@ -36,7 +36,7 @@ uint256 BlockMerkleRoot(const CBlock& block, bool* mutated = NULL); * Compute the Merkle root of the witness transactions in a block. * *mutated is set to true if a duplicated subtree was found. */ -uint256 BlockWitnessMerkleRoot(const CBlock& block, bool* mutated = NULL); +uint256 BlockWitnessMerkleRoot(const CBlock& block); /* * Compute the Merkle branch for the tree of transactions in a block, for a diff --git a/src/core_memusage.h b/src/core_memusage.h index 5e10182075..7cebdd480c 100644 --- a/src/core_memusage.h +++ b/src/core_memusage.h @@ -18,10 +18,7 @@ static inline size_t RecursiveDynamicUsage(const COutPoint& out) { } static inline size_t RecursiveDynamicUsage(const CTxIn& in) { - size_t mem = RecursiveDynamicUsage(in.scriptSig) + RecursiveDynamicUsage(in.prevout) + memusage::DynamicUsage(in.scriptWitness.stack); - for (std::vector >::const_iterator it = in.scriptWitness.stack.begin(); it != in.scriptWitness.stack.end(); it++) { - mem += memusage::DynamicUsage(*it); - } + size_t mem = RecursiveDynamicUsage(in.scriptSig) + RecursiveDynamicUsage(in.prevout); return mem; } @@ -29,6 +26,8 @@ static inline size_t RecursiveDynamicUsage(const CTxOut& out) { return RecursiveDynamicUsage(out.scriptPubKey); } +//TODO Account for CTxWitness. + static inline size_t RecursiveDynamicUsage(const CTransaction& tx) { size_t mem = memusage::DynamicUsage(tx.vin) + memusage::DynamicUsage(tx.vout); for (std::vector::const_iterator it = tx.vin.begin(); it != tx.vin.end(); it++) { diff --git a/src/core_write.cpp b/src/core_write.cpp index 3b242531c6..6591046fc8 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -173,9 +173,9 @@ void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry) o.pushKV("asm", ScriptToAsmStr(txin.scriptSig, true)); o.pushKV("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); in.pushKV("scriptSig", o); - if (!tx.vin[i].scriptWitness.IsNull()) { + if (tx.wit.vtxinwit.size() > i && !tx.wit.vtxinwit[i].scriptWitness.IsNull()) { UniValue txinwitness(UniValue::VARR); - for (const auto& item : tx.vin[i].scriptWitness.stack) { + for (const auto& item : tx.wit.vtxinwit[i].scriptWitness.stack) { txinwitness.push_back(HexStr(item.begin(), item.end())); } in.pushKV("txinwitness", txinwitness); diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 08a4547cf7..2adb8b419b 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -175,7 +175,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) { // We don't care if witness for this input is empty, since it must not be bloated. // If the script is invalid without witness, it would be caught sooner or later during validation. - if (tx.vin[i].scriptWitness.IsNull()) + if (tx.wit.vtxinwit.size() <= i || tx.wit.vtxinwit[i].scriptWitness.IsNull()) continue; const CTxOut &prev = mapInputs.GetOutputFor(tx.vin[i]); @@ -204,13 +204,13 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) // Check P2WSH standard limits if (witnessversion == 0 && witnessprogram.size() == 32) { - if (tx.vin[i].scriptWitness.stack.back().size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) + if (tx.wit.vtxinwit[i].scriptWitness.stack.back().size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return false; - size_t sizeWitnessStack = tx.vin[i].scriptWitness.stack.size() - 1; + size_t sizeWitnessStack = tx.wit.vtxinwit[i].scriptWitness.stack.size() - 1; if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS) return false; for (unsigned int j = 0; j < sizeWitnessStack; j++) { - if (tx.vin[i].scriptWitness.stack[j].size() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE) + if (tx.wit.vtxinwit[i].scriptWitness.stack[j].size() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE) return false; } } diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 64232ddd9e..4e60c64db4 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "consensus/merkle.h" #include "primitives/transaction.h" #include "hash.h" @@ -90,8 +91,55 @@ std::string CTxIn::ToString() const return str; } -CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0) {} -CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime) {} +CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), wit(), nLockTime(0) {} +CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), wit(tx.wit), nLockTime(tx.nLockTime) {} + +/** + * The input witness consists of three elements, two of which are + * optional. The optional elements have to do with asset issuance + * and are not normally present, most of the time. For this reason + * the optional elements are places in a lower branch of the Merkle + * tree. When not present, they take on constant values in the hash + * tree. + * + * S : script witness + * A : issuance amount rangeproof + * I : inflation keys rangeproof + * + * . + * / \ + * . S + * / \ + * A I + */ +uint256 CTxInWitness::GetHash() const +{ + std::vector leaves; + leaves.push_back(SerializeHash(vchIssuanceAmountRangeproof, SER_GETHASH, 0)); + leaves.push_back(SerializeHash(vchInflationKeysRangeproof, SER_GETHASH, 0)); + leaves.push_back(SerializeHash(scriptWitness.stack, SER_GETHASH, 0)); + return ComputeFastMerkleRoot(leaves); +} + +/** + * The output witness consists of two elements: the surjection proof and + * the range proof. + * + * S : asset surjection proof + * R : value range proof + * + * . + * / \ + * S R + */ +uint256 CTxOutWitness::GetHash() const +{ + std::vector leaves; + leaves.push_back(SerializeHash(vchSurjectionproof, SER_GETHASH, 0)); + leaves.push_back(SerializeHash(vchRangeproof, SER_GETHASH, 0)); + return ComputeFastMerkleRoot(leaves); +} + uint256 CMutableTransaction::GetHash() const { @@ -111,6 +159,26 @@ uint256 CTransaction::GetHashWithWitness() const return SerializeHash(*this, SER_GETHASH, 0); } +uint256 CTransaction::ComputeWitnessHash() const +{ + std::vector leaves; + leaves.reserve(std::max(vin.size(), vout.size())); + /* Inputs */ + for (size_t i = 0; i < vin.size(); ++i) + leaves.push_back(((wit.vtxinwit.size() <= i || vin[i].prevout.IsNull())? CTxInWitness(): wit.vtxinwit[i]).GetHash()); + uint256 hashIn = ComputeFastMerkleRoot(leaves); + leaves.clear(); + /* Outputs */ + for (size_t i = 0; i < vout.size(); ++i) + leaves.push_back((wit.vtxoutwit.size() <= i? CTxOutWitness(): wit.vtxoutwit[i]).GetHash()); + uint256 hashOut = ComputeFastMerkleRoot(leaves); + leaves.clear(); + /* Combined */ + leaves.push_back(hashIn); + leaves.push_back(hashOut); + return ComputeFastMerkleRoot(leaves); +} + bool CTransaction::HasValidFee() const { CAmountMap totalFee; @@ -137,9 +205,9 @@ CAmountMap CTransaction::GetFee() const } /* For backward compatibility, the hash is initialized to 0. TODO: remove the need for this default constructor entirely. */ -CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0), hash() {} -CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime), hash(ComputeHash()) {} -CTransaction::CTransaction(CMutableTransaction &&tx) : nVersion(tx.nVersion), vin(std::move(tx.vin)), vout(std::move(tx.vout)), nLockTime(tx.nLockTime), hash(ComputeHash()) {} +CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), wit(), nLockTime(0), hash() {} +CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), wit(tx.wit), nLockTime(tx.nLockTime), hash(ComputeHash()) {} +CTransaction::CTransaction(CMutableTransaction &&tx) : nVersion(tx.nVersion), vin(std::move(tx.vin)), vout(std::move(tx.vout)), wit(tx.wit), nLockTime(tx.nLockTime), hash(ComputeHash()) {} double CTransaction::ComputePriority(double dPriorityInputs, unsigned int nTxSize) const { @@ -189,8 +257,8 @@ std::string CTransaction::ToString() const nLockTime); for (unsigned int i = 0; i < vin.size(); i++) str += " " + vin[i].ToString() + "\n"; - for (unsigned int i = 0; i < vin.size(); i++) - str += " " + vin[i].scriptWitness.ToString() + "\n"; + for (unsigned int i = 0; i < wit.vtxinwit.size(); i++) + str += " " + wit.vtxinwit[i].scriptWitness.ToString() + "\n"; for (unsigned int i = 0; i < vout.size(); i++) str += " " + vout[i].ToString() + "\n"; return str; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 2d250a7c10..93ff2fcede 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -150,9 +150,7 @@ class CTxOut { public: CConfidentialAsset nAsset; - std::vector vchSurjectionproof; CConfidentialValue nValue; - std::vector vchRangeproof; CConfidentialNonce nNonce; CScript scriptPubKey; @@ -170,9 +168,7 @@ public: template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(nAsset); - // The surjection proof is serialized as part of the witness data READWRITE(nValue); - // The range proof is serialized as part of the witness data READWRITE(nNonce); READWRITE(*(CScriptBase*)(&scriptPubKey)); } @@ -180,16 +176,14 @@ public: void SetNull() { nAsset.SetNull(); - vchSurjectionproof.clear(); nValue.SetNull(); - vchRangeproof.clear(); nNonce.SetNull(); scriptPubKey.clear(); } bool IsNull() const { - return nAsset.IsNull() && vchSurjectionproof.empty() && nValue.IsNull() && vchRangeproof.empty() && nNonce.IsNull() && scriptPubKey.empty(); + return nAsset.IsNull() && nValue.IsNull() && nNonce.IsNull() && scriptPubKey.empty(); } CAmount GetDustThreshold(const CFeeRate &minRelayTxFee) const @@ -248,9 +242,7 @@ public: friend bool operator==(const CTxOut& a, const CTxOut& b) { return (a.nAsset == b.nAsset && - a.vchSurjectionproof == b.vchSurjectionproof && a.nValue == b.nValue && - a.vchRangeproof == b.vchRangeproof && a.nNonce == b.nNonce && a.scriptPubKey == b.scriptPubKey); } @@ -389,7 +381,6 @@ public: COutPoint prevout; CScript scriptSig; uint32_t nSequence; - CScriptWitness scriptWitness; //! Only serialized through CTransaction CAssetIssuance assetIssuance; /* Setting nSequence to this value for every input in a transaction @@ -504,29 +495,117 @@ public: std::string ToString() const; }; - -class CTxOutWitnessSerializer +class CTxInWitness { - CTxOut& ref; - public: - CTxOutWitnessSerializer(CTxOut& ref_) : ref(ref_) {} + std::vector vchIssuanceAmountRangeproof; + std::vector vchInflationKeysRangeproof; + CScriptWitness scriptWitness; ADD_SERIALIZE_METHODS; - bool IsNull() const { - return ref.vchSurjectionproof.empty() && ref.vchRangeproof.empty(); + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(vchIssuanceAmountRangeproof); + READWRITE(vchInflationKeysRangeproof); + READWRITE(scriptWitness.stack); } + CTxInWitness() { } + + bool IsNull() const + { + return vchIssuanceAmountRangeproof.empty() && vchInflationKeysRangeproof.empty() && scriptWitness.IsNull(); + } + void SetNull() + { + vchIssuanceAmountRangeproof.clear(); + vchInflationKeysRangeproof.clear(); + scriptWitness.stack.clear(); + } + + uint256 GetHash() const; +}; + +class CTxOutWitness +{ +public: + std::vector vchSurjectionproof; + std::vector vchRangeproof; + + ADD_SERIALIZE_METHODS; + template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(ref.vchSurjectionproof); - READWRITE(ref.vchRangeproof); + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(vchSurjectionproof); + READWRITE(vchRangeproof); } - void SetNull() { - std::vector().swap(ref.vchSurjectionproof); - std::vector().swap(ref.vchRangeproof); + CTxOutWitness() { } + + bool IsNull() const + { + return vchSurjectionproof.empty() && vchRangeproof.empty(); + } + void SetNull() + { + vchSurjectionproof.clear(); + vchRangeproof.clear(); + } + + uint256 GetHash() const; +}; + +class CTxWitness +{ +public: + /** In case vtxinwit is missing, all entries are treated as if they were empty CTxInWitnesses */ + std::vector vtxinwit; + std::vector vtxoutwit; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + for (size_t n = 0; n < vtxinwit.size(); n++) { + READWRITE(vtxinwit[n]); + } + for (size_t n = 0; n < vtxoutwit.size(); n++) { + READWRITE(vtxoutwit[n]); + } + if (IsNull()) { + /* It's illegal to encode a witness when all vtxinwit and vtxoutwit entries are empty. */ + throw std::ios_base::failure("Superfluous witness record"); + } + } + + bool IsEmpty() const + { + return vtxinwit.empty() && vtxoutwit.empty(); + } + + bool IsNull() const + { + for (size_t n = 0; n < vtxinwit.size(); n++) { + if (!vtxinwit[n].IsNull()) { + return false; + } + } + for (size_t n = 0; n < vtxoutwit.size(); n++) { + if (!vtxoutwit[n].IsNull()) { + return false; + } + } + return true; + } + + void SetNull() + { + vtxinwit.clear(); + vtxoutwit.clear(); } }; @@ -547,8 +626,6 @@ struct CMutableTransaction; * - std::vector vout * - if (flags & 1): * - CTxWitness wit; - * - if (flags & 2): - * - CTxOutWitness witout; * - uint32_t nLockTime */ template @@ -558,6 +635,7 @@ inline void UnserializeTransaction(TxType& tx, Stream& s) { unsigned char flags = 0; tx.vin.clear(); tx.vout.clear(); + tx.wit.SetNull(); /* 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) { @@ -574,24 +652,9 @@ inline void UnserializeTransaction(TxType& tx, Stream& s) { 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 ((flags & 2) && fAllowWitness) { - /* The witness output flag is present, and we support witnesses. */ - flags ^= 2; - bool fHadOutputWitness = false; - for (size_t i = 0; i < tx.vout.size(); i++) { - CTxOutWitnessSerializer witser(REF(tx.vout[i])); - s >> witser; - if (!witser.IsNull()) { - fHadOutputWitness = true; - } - } - if (!fHadOutputWitness) { - throw std::ios_base::failure("Superfluous output witness record"); - } + const_cast(&tx.wit)->vtxinwit.resize(tx.vin.size()); + const_cast(&tx.wit)->vtxoutwit.resize(tx.vout.size()); + s >> tx.wit; } if (flags) { /* Unknown flag in the serialization */ @@ -608,17 +671,12 @@ inline void SerializeTransaction(const TxType& tx, Stream& s) { unsigned char flags = 0; // Consistency check + assert(tx.wit.vtxoutwit.size() <= tx.vout.size()); if (fAllowWitness) { /* Check whether witnesses need to be serialized. */ if (tx.HasWitness()) { flags |= 1; } - for (size_t i = 0; i < tx.vout.size(); i++) { - if (!CTxOutWitnessSerializer(*const_cast(&tx.vout[i])).IsNull()) { - flags |= 2; - break; - } - } } if (flags) { /* Use extended format in case witnesses are to be serialized. */ @@ -629,15 +687,9 @@ inline void SerializeTransaction(const TxType& tx, Stream& s) { 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 & 2) { - for (size_t i = 0; i < tx.vout.size(); i++) { - CTxOutWitnessSerializer witser(*const_cast(&tx.vout[i])); - s << witser; - } - } + const_cast(&tx.wit)->vtxinwit.resize(tx.vin.size()); + const_cast(&tx.wit)->vtxoutwit.resize(tx.vout.size()); + s << tx.wit; } s << tx.nLockTime; } @@ -665,8 +717,8 @@ public: // structure, including the hash. const int32_t nVersion; const std::vector vin; - const std::vector vout; + const CTxWitness wit; const uint32_t nLockTime; private: @@ -704,6 +756,9 @@ public: // Compute a hash that includes both transaction and witness data uint256 GetHashWithWitness() const; + // Compute a hash of just the witness data + uint256 ComputeWitnessHash() const; + // Check if explicit TX fees overflow or are negative bool HasValidFee() const; @@ -742,12 +797,7 @@ public: bool HasWitness() const { - for (size_t i = 0; i < vin.size(); i++) { - if (!vin[i].scriptWitness.IsNull()) { - return true; - } - } - return false; + return !wit.IsNull(); } }; @@ -757,6 +807,7 @@ struct CMutableTransaction int32_t nVersion; std::vector vin; std::vector vout; + CTxWitness wit; uint32_t nLockTime; CMutableTransaction(); @@ -790,12 +841,7 @@ struct CMutableTransaction bool HasWitness() const { - for (size_t i = 0; i < vin.size(); i++) { - if (!vin[i].scriptWitness.IsNull()) { - return true; - } - } - return false; + return !wit.IsNull(); } }; diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 107d67a72f..6975c0de38 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -639,6 +639,7 @@ UniValue getblocktemplate(const JSONRPCRequest& request) entry.push_back(Pair("data", EncodeHexTx(tx))); entry.push_back(Pair("txid", txHash.GetHex())); entry.push_back(Pair("hash", tx.GetHashWithWitness().GetHex())); + entry.push_back(Pair("withash", tx.ComputeWitnessHash().GetHex())); UniValue deps(UniValue::VARR); BOOST_FOREACH (const CTxIn &in, tx.vin) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 58c8fc4b5d..5aa97e2f00 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -90,6 +90,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) { entry.push_back(Pair("txid", tx.GetHash().GetHex())); entry.push_back(Pair("hash", tx.GetHashWithWitness().GetHex())); + entry.push_back(Pair("withash", tx.ComputeWitnessHash().GetHex())); entry.push_back(Pair("size", (int)::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION))); entry.push_back(Pair("vsize", (int)::GetVirtualTransactionSize(tx))); entry.push_back(Pair("version", tx.nVersion)); @@ -110,12 +111,14 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) in.push_back(Pair("scriptSig", o)); } if (tx.HasWitness()) { - UniValue txinwitness(UniValue::VARR); - for (unsigned int j = 0; j < tx.vin[i].scriptWitness.stack.size(); j++) { - std::vector item = tx.vin[i].scriptWitness.stack[j]; - txinwitness.push_back(HexStr(item.begin(), item.end())); + UniValue scriptWitness(UniValue::VARR); + if (tx.wit.vtxinwit.size() > i) { + for (unsigned int j = 0; j < tx.wit.vtxinwit[i].scriptWitness.stack.size(); j++) { + std::vector item = tx.wit.vtxinwit[i].scriptWitness.stack[j]; + scriptWitness.push_back(HexStr(item.begin(), item.end())); + } } - in.push_back(Pair("txinwitness", txinwitness)); + in.push_back(Pair("scriptWitness", scriptWitness)); } in.push_back(Pair("sequence", (int64_t)txin.nSequence)); vin.push_back(in); @@ -132,7 +135,8 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) int mantissa; uint64_t minv; uint64_t maxv; - if (secp256k1_rangeproof_info(secp256k1_blind_context, &exp, &mantissa, &minv, &maxv, &txout.vchRangeproof[0], txout.vchRangeproof.size())) { + const CTxOutWitness* ptxoutwit = tx.wit.vtxoutwit.size() <= i? NULL: &tx.wit.vtxoutwit[i]; + if (ptxoutwit && secp256k1_rangeproof_info(secp256k1_blind_context, &exp, &mantissa, &minv, &maxv, &ptxoutwit->vchRangeproof[0], ptxoutwit->vchRangeproof.size())) { if (exp == -1) { out.push_back(Pair("value", ValueFromAmount((CAmount)minv))); } else { @@ -584,12 +588,13 @@ UniValue createrawtransaction(const JSONRPCRequest& request) void FillOutputBlinds(CMutableTransaction& tx, bool fUseWallet, std::vector& output_value_blinds, std::vector& output_asset_blinds, std::vector& output_pubkeys) { for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { if (!tx.vout[nOut].nValue.IsExplicit()) { + const CTxOutWitness* ptxoutwit = tx.wit.vtxoutwit.size() <= nOut? NULL: &tx.wit.vtxoutwit[nOut]; uint256 blinding_factor; uint256 asset_blinding_factor; CAsset asset; CAmount amount; #ifdef ENABLE_WALLET - if (fUseWallet && UnblindOutput(pwalletMain->GetBlindingKey(&tx.vout[nOut].scriptPubKey), tx.vout[nOut], amount, blinding_factor, asset, asset_blinding_factor) != 0) { + if (fUseWallet && ptxoutwit && UnblindOutput(pwalletMain->GetBlindingKey(&tx.vout[nOut].scriptPubKey), tx.vout[nOut], *ptxoutwit, amount, blinding_factor, asset, asset_blinding_factor) != 0) { // Wipe out confidential info from output except nonce CConfidentialNonce nNonce = tx.vout[nOut].nNonce; CScript scriptPubKey = tx.vout[nOut].scriptPubKey; @@ -1197,7 +1202,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request) UpdateTransaction(mergedTx, i, sigdata); ScriptError serror = SCRIPT_ERR_OK; - if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionNoWithdrawsSignatureChecker(&txConst, i, amount), &serror)) { + if (!VerifyScript(txin.scriptSig, prevPubKey, (mergedTx.wit.vtxinwit.size() > i) ? &mergedTx.wit.vtxinwit[i].scriptWitness : NULL, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionNoWithdrawsSignatureChecker(&txConst, i, amount), &serror)) { TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror)); } } diff --git a/src/script/bitcoinconsensus.cpp b/src/script/bitcoinconsensus.cpp index 8df6fdff48..bfb1ba9c21 100644 --- a/src/script/bitcoinconsensus.cpp +++ b/src/script/bitcoinconsensus.cpp @@ -97,9 +97,9 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP PrecomputedTransactionData txdata(tx); if (amountPreviousInput.IsExplicit() && (amountPreviousInput.GetAmount() < -1 || (nIn != 0 && !MoneyRange(amountPreviousInput.GetAmount())))) - return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionNoWithdrawsSignatureChecker(&tx, nIn, amount, txdata), NULL); + return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), (tx.wit.vtxinwit.size() > nIn) ? &tx.wit.vtxinwit[nIn].scriptWitness : NULL, flags, TransactionNoWithdrawsSignatureChecker(&tx, nIn, amount, txdata), NULL); else - return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, amountPreviousInput, txdata, CScript()), NULL); + return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), (tx.wit.vtxinwit.size() > nIn) ? &tx.wit.vtxinwit[nIn].scriptWitness : NULL, flags, TransactionSignatureChecker(&tx, nIn, amount, amountPreviousInput, txdata, CScript()), NULL); } catch (const std::exception&) { return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing } diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 1b49169534..d9e59bda4c 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -198,21 +198,22 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI SignatureData data; assert(tx.vin.size() > nIn); data.scriptSig = tx.vin[nIn].scriptSig; - data.scriptWitness = tx.vin[nIn].scriptWitness; + data.scriptWitness = (tx.wit.vtxinwit.size() > nIn) ? tx.wit.vtxinwit[nIn].scriptWitness : CScriptWitness(); return data; } void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data) { assert(tx.vin.size() > nIn); + tx.wit.vtxinwit.resize(tx.vin.size()); tx.vin[nIn].scriptSig = data.scriptSig; - tx.vin[nIn].scriptWitness = data.scriptWitness; + tx.wit.vtxinwit[nIn].scriptWitness = data.scriptWitness; } bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CConfidentialValue& amount, int nHashType) { assert(nIn < txTo.vin.size()); - + txTo.wit.vtxinwit.resize(txTo.vin.size()); CTransaction txToConst(txTo); TransactionSignatureCreator creator(&keystore, &txToConst, nIn, amount, nHashType); @@ -225,6 +226,7 @@ bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CMutabl bool SignSignature(const CKeyStore &keystore, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType) { assert(nIn < txTo.vin.size()); + txTo.wit.vtxinwit.resize(txTo.vin.size()); CTxIn& txin = txTo.vin[nIn]; assert(txin.prevout.n < txFrom.vout.size()); const CTxOut& txout = txFrom.vout[txin.prevout.n]; diff --git a/src/test/blind_tests.cpp b/src/test/blind_tests.cpp index 5537b4c103..7eeed70508 100644 --- a/src/test/blind_tests.cpp +++ b/src/test/blind_tests.cpp @@ -112,14 +112,14 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) BOOST_CHECK(VerifyAmounts(cache, tx3)); CAmount unblinded_amount; - BOOST_CHECK(UnblindOutput(key2, tx3.vout[0], unblinded_amount, blind3, unblinded_id, asset_blind) == 0); + BOOST_CHECK(UnblindOutput(key2, tx3.vout[0], tx3.wit.vtxoutwit[0], unblinded_amount, blind3, unblinded_id, asset_blind) == 0); // Saving unblinded_id and asset_blind for later since we need for input - BOOST_CHECK(UnblindOutput(key1, tx3.vout[0], unblinded_amount, blind3, unblinded_id, asset_blind) == 1); + BOOST_CHECK(UnblindOutput(key1, tx3.vout[0], tx3.wit.vtxoutwit[0], unblinded_amount, blind3, unblinded_id, asset_blind) == 1); BOOST_CHECK(unblinded_amount == 100); BOOST_CHECK(unblinded_id == bitcoinID); CAsset temp_asset; uint256 temp_asset_blinder; - BOOST_CHECK(UnblindOutput(keyDummy, tx3.vout[2], unblinded_amount, blindDummy, temp_asset, temp_asset_blinder) == 1); + BOOST_CHECK(UnblindOutput(keyDummy, tx3.vout[2], tx3.wit.vtxoutwit[2], unblinded_amount, blindDummy, temp_asset, temp_asset_blinder) == 1); BOOST_CHECK(unblinded_amount == 0); CCoinsModifier in3 = cache.ModifyCoins(ArithToUint256(3)); @@ -265,11 +265,11 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) CAmount unblinded_amount; CAsset asset_out; uint256 asset_blinder_out; - BOOST_CHECK(UnblindOutput(key1, tx4.vout[0], unblinded_amount, blind4, asset_out, asset_blinder_out) == 0); - BOOST_CHECK(UnblindOutput(key2, tx4.vout[0], unblinded_amount, blind4, asset_out, asset_blinder_out) == 1); + BOOST_CHECK(UnblindOutput(key1, tx4.vout[0], tx4.wit.vtxoutwit[0], unblinded_amount, blind4, asset_out, asset_blinder_out) == 0); + BOOST_CHECK(UnblindOutput(key2, tx4.vout[0], tx4.wit.vtxoutwit[0], unblinded_amount, blind4, asset_out, asset_blinder_out) == 1); BOOST_CHECK(unblinded_amount == 30); BOOST_CHECK(asset_out == unblinded_id); - BOOST_CHECK(UnblindOutput(key2, tx4.vout[2], unblinded_amount, blind4, asset_out, asset_blinder_out) == 1); + BOOST_CHECK(UnblindOutput(key2, tx4.vout[2], tx4.wit.vtxoutwit[2], unblinded_amount, blind4, asset_out, asset_blinder_out) == 1); BOOST_CHECK(asset_out == unblinded_id); BOOST_CHECK(unblinded_amount == 50); diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index a05893868a..bc79ecf60e 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -143,7 +143,8 @@ CMutableTransaction BuildSpendingTransaction(const CScript& scriptSig, const CSc txSpend.nLockTime = 0; txSpend.vin.resize(1); txSpend.vout.resize(1); - txSpend.vin[0].scriptWitness = scriptWitness; + txSpend.wit.vtxinwit.resize(1); + txSpend.wit.vtxinwit[0].scriptWitness = scriptWitness; txSpend.vin[0].prevout.hash = txCredit.GetHash(); txSpend.vin[0].prevout.n = 0; txSpend.vin[0].scriptSig = scriptSig; diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index e104c4b524..f41c4143f8 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -71,7 +71,7 @@ ScriptError VerifyWithFlag(const CTransaction& output, const CMutableTransaction { ScriptError error; CTransaction inputi(input); - bool ret = VerifyScript(inputi.vin[0].scriptSig, output.vout[0].scriptPubKey, &inputi.vin[0].scriptWitness, flags, TransactionSignatureChecker(&inputi, 0, output.vout[0].nValue, -1, CScript()), &error); + bool ret = VerifyScript(inputi.vin[0].scriptSig, output.vout[0].scriptPubKey, inputi.wit.vtxinwit.size() > 0 ? &inputi.wit.vtxinwit[0].scriptWitness : NULL, flags, TransactionSignatureChecker(&inputi, 0, output.vout[0].nValue, -1, CScript()), &error); BOOST_CHECK((ret == true) == (error == SCRIPT_ERR_OK)); return error; @@ -97,7 +97,8 @@ void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CMutableT spendingTx.vin[0].prevout.hash = creationTx.GetHash(); spendingTx.vin[0].prevout.n = 0; spendingTx.vin[0].scriptSig = scriptSig; - spendingTx.vin[0].scriptWitness = witness; + spendingTx.wit.vtxinwit.resize(1); + spendingTx.wit.vtxinwit[0].scriptWitness = witness; spendingTx.vout.resize(1); spendingTx.vout[0].nValue = 1; spendingTx.vout[0].scriptPubKey = CScript(); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 8dd273c900..429903acd2 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -169,7 +169,7 @@ return; amount = mapprevOutValues[tx.vin[i].prevout]; } unsigned int verify_flags = ParseScriptFlags(test[2].get_str()); - const CScriptWitness *witness = &tx.vin[i].scriptWitness; + const CScriptWitness *witness = (tx.wit.vtxinwit.size() > i) ? &tx.wit.vtxinwit[i].scriptWitness : NULL; BOOST_CHECK_MESSAGE(VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout], witness, verify_flags, TransactionNoWithdrawsSignatureChecker(&tx, i, amount, txdata), &err), strTest); @@ -254,7 +254,7 @@ return; if (mapprevOutValues.count(tx.vin[i].prevout)) { amount = mapprevOutValues[tx.vin[i].prevout]; } - const CScriptWitness *witness = &tx.vin[i].scriptWitness; + const CScriptWitness *witness = (tx.wit.vtxinwit.size() > i) ? &tx.wit.vtxinwit[i].scriptWitness : NULL; fValid = VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout], witness, verify_flags, TransactionNoWithdrawsSignatureChecker(&tx, i, amount, txdata), &err); } @@ -380,6 +380,7 @@ void CreateCreditAndSpend(const CKeyStore& keystore, const CScript& outscript, C inputm.vout.resize(1); inputm.vout[0].nValue = 1; inputm.vout[0].scriptPubKey = CScript(); + inputm.wit.vtxinwit.resize(1); bool ret = SignSignature(keystore, *output, inputm, 0, SIGHASH_ALL); assert(ret == success); CDataStream ssin(SER_NETWORK, PROTOCOL_VERSION); @@ -389,14 +390,16 @@ void CreateCreditAndSpend(const CKeyStore& keystore, const CScript& outscript, C assert(input.vin[0] == inputm.vin[0]); assert(input.vout.size() == 1); assert(input.vout[0] == inputm.vout[0]); - assert(input.vin[0].scriptWitness.stack == inputm.vin[0].scriptWitness.stack); + // serialization round-trip blows away empty witnesses + assert(input.HasWitness() == inputm.HasWitness()); + assert(!inputm.HasWitness() || (input.wit.vtxinwit[0].scriptWitness.stack == inputm.wit.vtxinwit[0].scriptWitness.stack)); } void CheckWithFlag(const CTransactionRef& output, const CMutableTransaction& input, int flags, bool success) { ScriptError error; CTransaction inputi(input); - bool ret = VerifyScript(inputi.vin[0].scriptSig, output->vout[0].scriptPubKey, &inputi.vin[0].scriptWitness, flags, TransactionNoWithdrawsSignatureChecker(&inputi, 0, output->vout[0].nValue), &error); + bool ret = VerifyScript(inputi.vin[0].scriptSig, output->vout[0].scriptPubKey, (inputi.wit.vtxinwit.size() > 0) ? &inputi.wit.vtxinwit[0].scriptWitness : NULL, flags, TransactionNoWithdrawsSignatureChecker(&inputi, 0, output->vout[0].nValue), &error); assert(ret == success); } @@ -492,7 +495,7 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction) { CScriptCheck* checks[mtx.vin.size()]; for(uint32_t i = 0; i < mtx.vin.size(); i++) { std::vector vChecks; - checks[i] = new CScriptCheck(coins, tx, i, -1, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false, &txdata); + checks[i] = new CScriptCheck(coins, tx, i, 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false, &txdata); vChecks.push_back(checks[i]); control.Add(vChecks); } diff --git a/src/validation.cpp b/src/validation.cpp index 74c6a1c0f3..cb39782528 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -485,7 +485,7 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i for (unsigned int i = 0; i < tx.vin.size(); i++) { const CTxOut &prevout = inputs.GetOutputFor(tx.vin[i]); - nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, &tx.vin[i].scriptWitness, flags); + nSigOps += CountWitnessSigOps(tx.vin[i].scriptSig, prevout.scriptPubKey, tx.wit.vtxinwit.size() > i ? &tx.wit.vtxinwit[i].scriptWitness : NULL, flags); } return nSigOps; } @@ -722,9 +722,12 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve { const CConfidentialValue& val = tx.vout[i].nValue; const CConfidentialAsset& asset = tx.vout[i].nAsset; - if (!asset.IsValid() || !val.IsValid() || !tx.vout[i].nNonce.IsValid()) + const CTxOutWitness* ptxoutwit = tx.wit.vtxoutwit.size() <= i? NULL: &tx.wit.vtxoutwit[i]; + if (!asset.IsValid() || (ptxoutwit && ptxoutwit->vchSurjectionproof.size() > 5000)) return false; - if (tx.vout[i].vchRangeproof.size() > 5000) + if (!val.IsValid() || (ptxoutwit && ptxoutwit->vchRangeproof.size() > 5000)) + return false; + if (!tx.vout[i].nNonce.IsValid()) return false; if (asset.IsExplicit()) { @@ -769,9 +772,17 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve // Range proofs for (size_t i = 0; i < tx.vout.size(); i++) { const CConfidentialValue& val = tx.vout[i].nValue; + const CTxOutWitness* ptxoutwit = tx.wit.vtxoutwit.size() <= i? NULL: &tx.wit.vtxoutwit[i]; if (val.IsExplicit()) + { + if (ptxoutwit && !ptxoutwit->vchRangeproof.empty()) + return false; continue; - if (!QueueCheck(pvChecks, new CRangeCheck(&val, tx.vout[i].vchRangeproof, &tx.vout[i].nAsset, &tx.vout[i].scriptPubKey, cacheStore))) { + } + if (!ptxoutwit || ptxoutwit->vchRangeproof.size() > 5000) { + return false; + } + if (!QueueCheck(pvChecks, new CRangeCheck(&val, ptxoutwit->vchRangeproof, &tx.vout[i].nAsset, &tx.vout[i].scriptPubKey, cacheStore))) { return false; } } @@ -799,16 +810,21 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve for (size_t i = 0; i < tx.vout.size(); i++) { const CConfidentialAsset& asset = tx.vout[i].nAsset; + const CTxOutWitness* ptxoutwit = tx.wit.vtxoutwit.size() <= i? NULL: &tx.wit.vtxoutwit[i]; //No need for surjective proof if (asset.IsExplicit()) { - assert(tx.vout[i].vchSurjectionproof.size() == 0); + if (ptxoutwit && !ptxoutwit->vchSurjectionproof.empty()) { + return false; + } continue; } + if (!ptxoutwit || ptxoutwit->vchSurjectionproof.size() > 5000) + return false; if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1) return false; secp256k1_surjectionproof proof; - if (secp256k1_surjectionproof_parse(secp256k1_ctx_verify_amounts, &proof, &tx.vout[i].vchSurjectionproof[0], tx.vout[i].vchSurjectionproof.size()) != 1) + if (secp256k1_surjectionproof_parse(secp256k1_ctx_verify_amounts, &proof, &ptxoutwit->vchSurjectionproof[0], ptxoutwit->vchSurjectionproof.size()) != 1) return false; if (!QueueCheck(pvChecks, new CSurjectionCheck(proof, ephemeral_input_tags, gen, cacheStore))) { @@ -1813,7 +1829,7 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight) bool CScriptCheck::operator()() { const CScript &scriptSig = ptxTo->vin[nIn].scriptSig; - const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness; + const CScriptWitness *witness = (nIn < ptxTo->wit.vtxinwit.size()) ? &ptxTo->wit.vtxinwit[nIn].scriptWitness : NULL; if (!VerifyScript(scriptSig, scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, amount, amountPreviousInput, Params().GetConsensus().fedpegScript, cacheStore, *txdata), &error)) { return false; } @@ -3647,8 +3663,9 @@ void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPr static const std::vector nonce(32, 0x00); if (commitpos != -1 && IsWitnessEnabled(pindexPrev, consensusParams) && !block.vtx[0]->HasWitness()) { CMutableTransaction tx(*block.vtx[0]); - tx.vin[0].scriptWitness.stack.resize(1); - tx.vin[0].scriptWitness.stack[0] = nonce; + tx.wit.vtxinwit.resize(1); + tx.wit.vtxinwit[0].scriptWitness.stack.resize(1); + tx.wit.vtxinwit[0].scriptWitness.stack[0] = nonce; block.vtx[0] = MakeTransactionRef(std::move(tx)); } } @@ -3660,7 +3677,10 @@ std::vector GenerateCoinbaseCommitment(CBlock& block, const CBloc std::vector ret(32, 0x00); if (consensusParams.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout != 0) { if (commitpos == -1) { - uint256 witnessroot = BlockWitnessMerkleRoot(block, NULL); + CMutableTransaction tx0(*block.vtx[0]); + tx0.vout.push_back(CTxOut()); + block.vtx[0] = MakeTransactionRef(std::move(tx0)); + uint256 witnessroot = BlockWitnessMerkleRoot(block); CHash256().Write(witnessroot.begin(), 32).Write(&ret[0], 32).Finalize(witnessroot.begin()); CTxOut out; out.nValue = 0; @@ -3675,7 +3695,7 @@ std::vector GenerateCoinbaseCommitment(CBlock& block, const CBloc memcpy(&out.scriptPubKey[6], witnessroot.begin(), 32); commitment = std::vector(out.scriptPubKey.begin(), out.scriptPubKey.end()); CMutableTransaction tx(*block.vtx[0]); - tx.vout.push_back(out); + tx.vout[tx.vout.size()-1] = out; block.vtx[0] = MakeTransactionRef(std::move(tx)); } } @@ -3755,15 +3775,11 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, const Co if (VersionBitsState(pindexPrev, consensusParams, Consensus::DEPLOYMENT_SEGWIT, versionbitscache) == THRESHOLD_ACTIVE) { int commitpos = GetWitnessCommitmentIndex(block); if (commitpos != -1) { - bool malleated = false; - uint256 hashWitness = BlockWitnessMerkleRoot(block, &malleated); - // The malleation check is ignored; as the transaction tree itself - // already does not permit it, it is impossible to trigger in the - // witness tree. - if (block.vtx[0]->vin[0].scriptWitness.stack.size() != 1 || block.vtx[0]->vin[0].scriptWitness.stack[0].size() != 32) { + uint256 hashWitness = BlockWitnessMerkleRoot(block); + if ((block.vtx[0]->wit.vtxinwit.size() > 0 &&block.vtx[0]->wit.vtxinwit[0].scriptWitness.stack.size() != 1) || block.vtx[0]->wit.vtxinwit[0].scriptWitness.stack[0].size() != 32) { return state.DoS(100, false, REJECT_INVALID, "bad-witness-nonce-size", true, strprintf("%s : invalid witness nonce size", __func__)); } - CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0]->vin[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin()); + CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0]->wit.vtxinwit[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin()); if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) { return state.DoS(100, false, REJECT_INVALID, "bad-witness-merkle-match", true, strprintf("%s : witness merkle commitment mismatch", __func__)); } @@ -3777,11 +3793,6 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, const Co if (block.vtx[i]->HasWitness()) { return state.DoS(100, false, REJECT_INVALID, "unexpected-witness", true, strprintf("%s : unexpected witness data found", __func__)); } - for (size_t o = 0; o < block.vtx[i]->vout.size(); o++) { - if (!CTxOutWitnessSerializer(REF(block.vtx[i]->vout[o])).IsNull()) { - return state.DoS(100, false, REJECT_INVALID, "unexpected-witness", true, strprintf("%s : unexpected output witness data found", __func__)); - } - } } } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c9b9052411..fa48993279 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2004,7 +2004,8 @@ void CWalletTx::GetBlindingData(unsigned int nOut, CAmount* pamountOut, CPubKey* memcpy(asset.begin(), &*(it + 73), 32); pubkey.Set(it + 105, it + 138); } else { - pwallet->ComputeBlindingData(tx->vout[nOut], amount, pubkey, blindingfactor, asset, assetBlindingFactor); + pwallet->ComputeBlindingData(tx->vout[nOut], tx->wit.vtxoutwit.size() <= nOut? CTxOutWitness(): tx->wit.vtxoutwit[nOut], amount, pubkey, blindingfactor, + asset, assetBlindingFactor); *it = 1; memcpy(&*(it + 1), &amount, 8); memcpy(&*(it + 9), blindingfactor.begin(), 32); @@ -2587,12 +2588,19 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov if (!CreateTransaction(vecSend, wtx, vpChangeKey, nFeeRet, nChangePosInOut, strFailReason, &coinControl, false)) return false; - if (nChangePosInOut != -1) + // Append fee output if any + // This assumes fee is appended to end, and only one fee added by the CreateTransaction call + if (wtx.tx->vout.back().IsFee()) { + tx.vout.push_back(wtx.tx->vout.back()); + } + + if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]); - // Copy output sizes from new transaction; they may have had the fee subtracted from them - for (unsigned int idx = 0; idx < tx.vout.size(); idx++) - tx.vout[idx].nValue = wtx.tx->vout[idx].nValue; + // Insert change witness + tx.wit.vtxoutwit.resize(tx.vout.size()-1); + tx.wit.vtxoutwit.insert(tx.wit.vtxoutwit.begin() + nChangePosInOut, wtx.tx->wit.vtxoutwit[nChangePosInOut]); + } // Add new txins (keeping original txin scriptSig/order) BOOST_FOREACH(const CTxIn& txin, wtx.tx->vin) @@ -2695,6 +2703,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt txNew.vin.clear(); txNew.vout.clear(); + txNew.wit.SetNull(); wtxNew.fFromMe = true; bool fFirst = true; @@ -2961,8 +2970,8 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt // Remove scriptSigs to eliminate the fee calculation dummy signatures for (auto& vin : txNew.vin) { vin.scriptSig = CScript(); - vin.scriptWitness.SetNull(); } + txNew.wit.vtxinwit.clear(); // Allow to override the default confirmation target over the CoinControl instance int currentConfirmationTarget = nTxConfirmTarget; @@ -3052,6 +3061,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt nIn++; } } + //TODO !sign -> remove blinding // Embed the constructed transaction data in wtxNew. wtxNew.SetTx(MakeTransactionRef(std::move(txNew))); @@ -4415,7 +4425,7 @@ bool CWallet::LoadAssetLabelMapping(const CAsset& id, const std::string& label) return true; } -void CWallet::ComputeBlindingData(const CTxOut& output, CAmount& amount, CPubKey& pubkey, uint256& blindingfactor, CAsset& asset, uint256& assetBlindingFactor) const +void CWallet::ComputeBlindingData(const CTxOut& output, const CTxOutWitness& witness, CAmount& amount, CPubKey& pubkey, uint256& blindingfactor, CAsset& asset, uint256& assetBlindingFactor) const { if (output.nValue.IsExplicit() && output.nAsset.IsExplicit()) { amount = output.nValue.GetAmount(); @@ -4429,7 +4439,7 @@ void CWallet::ComputeBlindingData(const CTxOut& output, CAmount& amount, CPubKey CKey blinding_key; if ((blinding_key = GetBlindingKey(&output.scriptPubKey)).IsValid()) { // For outputs using derived blinding. - if (UnblindOutput(blinding_key, output, amount, blindingfactor, + if (UnblindOutput(blinding_key, output, witness, amount, blindingfactor, asset, assetBlindingFactor)) { pubkey = blinding_key.GetPubKey(); return; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 6ebf4faee6..cd91e822c5 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1026,7 +1026,7 @@ public: CKey GetBlindingKey(const CScript* script) const; CPubKey GetBlindingPubKey(const CScript& script) const; - void ComputeBlindingData(const CTxOut& output, CAmount& amount, CPubKey& pubkey, uint256& blindingfactor, CAsset& asset, uint256& assetBlindingFactor) const; + void ComputeBlindingData(const CTxOut& output, const CTxOutWitness& witness, CAmount& amount, CPubKey& pubkey, uint256& blindingfactor, CAsset& asset, uint256& assetBlindingFactor) const; /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */ bool MarkReplaced(const uint256& originalHash, const uint256& newHash);