diff --git a/src/blind.cpp b/src/blind.cpp index 1cc9b969ad..8c65ef6d7b 100644 --- a/src/blind.cpp +++ b/src/blind.cpp @@ -74,11 +74,14 @@ bool UnblindOutput(const CKey &key, const CTxOut& txout, CAmount& amount_out, ui } } -int BlindOutputs(std::vector& input_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& output_blinding_factors, std::vector& output_asset_blinding_factors, const std::vector& output_pubkeys, CMutableTransaction& tx) +int BlindOutputs(std::vector& input_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& output_blinding_factors, std::vector& output_asset_blinding_factors, const std::vector& output_pubkeys, CMutableTransaction& tx, std::vector >* auxiliary_generators) { - assert(tx.vout.size() == output_blinding_factors.size()); - assert(tx.vout.size() == output_pubkeys.size()); - assert(tx.vout.size() == output_asset_blinding_factors.size()); + // Sanity check input data and output_pubkey size, clear other output data + assert(tx.vout.size() >= output_pubkeys.size()); + output_blinding_factors.clear(); + output_blinding_factors.resize(tx.vout.size()); + output_asset_blinding_factors.clear(); + output_asset_blinding_factors.resize(tx.vout.size()); assert(tx.vin.size() == input_blinding_factors.size()); assert(tx.vin.size() == input_asset_blinding_factors.size()); assert(tx.vin.size() == input_assets.size()); @@ -91,25 +94,43 @@ int BlindOutputs(std::vector& input_blinding_factors, const std::vecto assetblindptrs.reserve(tx.vout.size() + tx.vin.size()); int ret; - int nBlinded = 0; + int nBlindAttempts = 0, nSuccessfullyBlinded = 0; //Surjection proof prep + + // Needed to surj init, only matches to output asset matters, rest can be garbage std::vector inputAssets; + + // Needed to construct the proof itself. Generators must match final transaction to be valid std::vector inputAssetGenerators; inputAssets.resize(tx.vin.size()); inputAssetGenerators.resize(tx.vin.size()); for (size_t i = 0; i < tx.vin.size(); i++) { + // If non-empty generator exists, parse + if (auxiliary_generators && auxiliary_generators->size() > i && auxiliary_generators[i].size() == 33) { + // Parse generator here + ret = secp256k1_generator_parse(secp256k1_blind_context, &inputAssetGenerators[i], &(*auxiliary_generators)[i][0]); + if (ret != 1) { + return -1; + } + } else { + // Needs to be non-null + if (input_assets[i].IsNull()) { + return -1; + } + ret = secp256k1_generator_generate_blinded(secp256k1_blind_context, &inputAssetGenerators[i], input_assets[i].begin(), input_asset_blinding_factors[i].begin()); + assert(ret == 1); + } memcpy(&inputAssets[i], input_assets[i].begin(), 32); - ret = secp256k1_generator_generate_blinded(secp256k1_blind_context, &inputAssetGenerators[i], input_assets[i].begin(), input_asset_blinding_factors[i].begin()); - assert(ret == 1); } - //Total blinded inputs + //Total blinded inputs that you own (that you are balancing against) int nBlindsIn = 0; for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { - if (input_blinding_factors[nIn] != uint256()) { - assert(input_blinding_factors[nIn].size() == 32); - assert(input_asset_blinding_factors[nIn].size() == 32); + if (!input_blinding_factors[nIn].IsNull() || !input_asset_blinding_factors[nIn].IsNull()) { + if (input_amounts[nIn] < 0) { + return -1; + } blindptrs.push_back(input_blinding_factors[nIn].begin()); assetblindptrs.push_back(input_asset_blinding_factors[nIn].begin()); blindedAmounts.push_back(input_amounts[nIn]); @@ -117,36 +138,21 @@ int BlindOutputs(std::vector& input_blinding_factors, const std::vecto } } - //Running total of blinded outputs - int nBlindsOut = 0; - //Number of outputs to newly blind + //Number of outputs to blind int nToBlind = 0; - for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { - CTxOut& out = tx.vout[nOut]; - // Wallet only understands all-blinded or all-unblinded - assert((output_blinding_factors[nOut] != uint256()) == !out.nValue.IsExplicit()); - assert(out.nValue.IsExplicit() == out.nAsset.IsExplicit() || out.nValue.IsExplicit() == out.nAsset.IsAssetGeneration()); - assert(out.nAsset.IsCommitment() == !out.vchSurjectionproof.empty()); - if (output_blinding_factors[nOut] != uint256()) { - assert(output_asset_blinding_factors[nOut] != uint256()); - blindptrs.push_back(output_blinding_factors[nOut].begin()); - assetblindptrs.push_back(output_asset_blinding_factors[nOut].begin()); - blindedAmounts.push_back(tx.vout[nOut].nValue.GetAmount()); - nBlindsOut++; - - //Assert-check surjective proofs - secp256k1_generator gen; - secp256k1_surjectionproof proof; - assert(secp256k1_generator_parse(secp256k1_blind_context, &gen, &out.nAsset.vchCommitment[0]) == 1); - assert(secp256k1_surjectionproof_parse(secp256k1_blind_context, &proof, &out.vchSurjectionproof[0], out.vchSurjectionproof.size()) == 1); - assert(secp256k1_surjectionproof_verify(secp256k1_blind_context, &proof, &inputAssetGenerators[0], inputAssetGenerators.size(), &gen) == 1); - } else { - if (output_pubkeys[nOut].IsFullyValid()) { - nToBlind++; - } + for (size_t nOut = 0; nOut < output_pubkeys.size(); nOut++) { + if (output_pubkeys[nOut].IsValid()) { + // Keys must be valid and outputs completely unblinded or else call fails + if (!output_pubkeys[nOut].IsFullyValid() || + (!tx.vout[nOut].nValue.IsExplicit() || !tx.vout[nOut].nAsset.IsExplicit()) || + tx.vout[nOut].IsFee()) { + return -1; + } + nToBlind++; } } + //Running total of newly blinded outputs static const unsigned char diff_zero[32] = {0}; unsigned char blind[tx.vout.size()][32]; @@ -155,47 +161,47 @@ int BlindOutputs(std::vector& input_blinding_factors, const std::vecto secp256k1_generator gen; CAsset assetID; - for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { + // This section of code *only* deals with unblinded outputs + // that we want to blind + for (size_t nOut = 0; nOut < output_pubkeys.size(); nOut++) { CTxOut& out = tx.vout[nOut]; if (out.nValue.IsExplicit() && output_pubkeys[nOut].IsFullyValid()) { + nBlindAttempts++; CConfidentialAsset& asset = out.nAsset; CConfidentialValue& value = out.nValue; CAmount amount = value.GetAmount(); assetID = out.nAsset.GetAsset(); blindedAmounts.push_back(value.GetAmount()); - GetRandBytes(&blind[nBlinded][0], 32); - GetRandBytes(&asset_blind[nBlinded][0], 32); - blindptrs.push_back(&blind[nBlinded][0]); - assetblindptrs.push_back(&asset_blind[nBlinded][0]); - - nBlindsOut++; + GetRandBytes(&blind[nBlindAttempts-1][0], 32); + GetRandBytes(&asset_blind[nBlindAttempts-1][0], 32); + blindptrs.push_back(&blind[nBlindAttempts-1][0]); + assetblindptrs.push_back(&asset_blind[nBlindAttempts-1][0]); // Last blinding factor r' is set as -(output's (vr + r') - input's (vr + r')). // Before modifying the transaction or return arguments we must // ensure the final blinding factor to not be its corresponding -vr (aka unblinded), // or 0, in the case of 0-value output, insisting on additional output to blind. - if (nBlinded + 1 == nToBlind) { + if (nBlindAttempts == nToBlind) { // Can't successfully blind in this case, since -vr = r // This check is assuming blinds are generated randomly // Adversary would need to create all input blinds // therefore would already know all your summed output amount anyways. - if (nBlindsOut == 1 && nBlindsIn == 0) { - return nBlinded; + if (nBlindAttempts == 1 && nBlindsIn == 0) { + return nSuccessfullyBlinded; } // Generate value we intend to insert - ret = secp256k1_pedersen_blind_generator_blind_sum(secp256k1_blind_context, &blindedAmounts[0], &assetblindptrs[0], &blindptrs[0], nBlindsOut + nBlindsIn, nBlindsIn); + ret = secp256k1_pedersen_blind_generator_blind_sum(secp256k1_blind_context, &blindedAmounts[0], &assetblindptrs[0], &blindptrs[0], nBlindAttempts + nBlindsIn, nBlindsIn); assert(ret); // Resulting blinding factor shouldn't be 0 - if (memcmp(diff_zero, &blind[nBlinded][0], 32) == 0) { - return nBlinded; + if (memcmp(diff_zero, &blind[nBlindAttempts-1][0], 32) == 0) { + return nSuccessfullyBlinded; } } - nBlinded++; 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)); @@ -249,8 +255,7 @@ int BlindOutputs(std::vector& input_blinding_factors, const std::vecto secp256k1_fixed_asset_tag tag; memcpy(&tag, assetID.begin(), 32); if (secp256k1_surjectionproof_initialize(secp256k1_blind_context, &proof, &input_index, &inputAssets[0], input_assets.size(), nInputsToSelect, &tag, 100, randseed) == 0) { - // actually failed to blind this one - return nBlinded-1; + continue; } ret = secp256k1_surjectionproof_generate(secp256k1_blind_context, &proof, &inputAssetGenerators[0], inputAssetGenerators.size(), &gen, input_index, input_asset_blinding_factors[input_index].begin(), assetblindptrs[assetblindptrs.size()-1]); assert(ret == 1); @@ -260,22 +265,11 @@ int BlindOutputs(std::vector& input_blinding_factors, const std::vecto 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); + + // Successfully blinded this output + nSuccessfullyBlinded++; } } - // No known blinding means the blinding attempt is vacuously successful - if (nBlindsOut == 0) { - return nBlinded; - } - - // Check blinding(even if nothing has been done) - unsigned char tempFinalBlind[32]; - memcpy(tempFinalBlind, blindptrs.back(), 32); - memset(blindptrs.back(), 0, 32); - ret = secp256k1_pedersen_blind_generator_blind_sum(secp256k1_blind_context, &blindedAmounts[0], &assetblindptrs[0], &blindptrs[0], nBlindsOut + nBlindsIn, nBlindsIn); - assert(ret != 0); - if (memcmp(blindptrs.back(), tempFinalBlind, 32)) - return -1; - - return nBlinded; + return nSuccessfullyBlinded; } diff --git a/src/blind.h b/src/blind.h index f31df16626..892de767d6 100644 --- a/src/blind.h +++ b/src/blind.h @@ -11,13 +11,14 @@ bool UnblindOutput(const CKey& blinding_key, const CTxOut& txout, CAmount& amoun * In many cases a `0` can be fixed by adding an additional output. * @param[in] input_blinding_factors - A vector of input blinding factors that will be used to create the balanced output blinding factors * @param[in] input_asset_blinding_factors - A vector of input asset blinding factors that will be used to create the balanced output blinding factors - * @param[in] input_assets - the asset ids of each corresponding input - * @param[in] input_amounts - the unblinded amounts of each input. This is required only for calls with already-blinded inputs for sum calculations. - * @param[in/out] output_blinding_factors - A vector of blinding factors. Null uint256 values are used to signal to the callee that a new blinding is needed. New blinds then replace the blank values. - * @param[in/out] output_asset_blinding_factors - A vector of asset blinding factors. Null uint256 values are used to signal to the callee that a new blinding is needed. New blinds then replace the blank values. These values being blind/unblind should correspond to output_blinding_factors. - * @param[in] output_pubkeys - If non-null, these pubkeys will be used in conjunction with the non-null passed in output blinding factors. + * @param[in] input_assets - the asset of each corresponding input + * @param[in] input_amounts - the unblinded amounts of each input. Required for owned blinded inputs + * @param[in/out] output_blinding_factors - A vector of blinding factors. New blinding factors will replace these values. + * @param[in/out] output_asset_blinding_factors - A vector of asset blinding factors. New blinding factors will replace these values. + * @param[in] output_pubkeys - If valid, corresponding output must be unblinded, and will result in fully blinded output, modifying the output blinding arguments as well. * @param[in/out] tx - The transaction to be modified. + * @param[in] auxiliary_generators - a list of generators to create surjection proofs when inputs are not owned by caller */ -int BlindOutputs(std::vector& input_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& output_blinding_factors, std::vector& output_asset_blinding_factors, const std::vector& output_pubkeys, CMutableTransaction& tx); +int BlindOutputs(std::vector& input_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& output_blinding_factors, std::vector& output_asset_blinding_factors, const std::vector& output_pubkeys, CMutableTransaction& tx, std::vector >* auxiliary_generators = NULL); #endif diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index f9c68c046e..8db6a0995e 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -569,9 +569,9 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) return EncodeHexTx(rawTx); } -// Retrieve already-existing output blinds for a given transaction (if known to wallet) -// or blank spots to be filled by BlindOutputs -void FillOutputBlinds(const CMutableTransaction& tx, bool fUseWallet, std::vector& output_value_blinds, std::vector& output_asset_blinds, std::vector& output_assets, std::vector& output_pubkeys) { +// Rewind the outputs to unblinded, and push placeholders for blinding info +// Failure if any are unable to be unblinded +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()) { uint256 blinding_factor; @@ -580,10 +580,19 @@ void FillOutputBlinds(const CMutableTransaction& tx, bool fUseWallet, std::vecto 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) { - output_value_blinds.push_back(blinding_factor); - output_pubkeys.push_back(CPubKey()); - output_asset_blinds.push_back(asset_blinding_factor); - output_assets.push_back(asset); + // Wipe out confidential info from output except nonce + CConfidentialNonce nNonce = tx.vout[nOut].nNonce; + CScript scriptPubKey = tx.vout[nOut].scriptPubKey; + CTxOut newOut(asset, amount, scriptPubKey); + newOut.nNonce = nNonce; + tx.vout[nOut] = newOut; + + // Pubkey is valid, since we re-wound successfully + CPubKey pubkey(tx.vout[nOut].nNonce.vchCommitment); + output_pubkeys.push_back(pubkey); + output_value_blinds.push_back(uint256()); + output_asset_blinds.push_back(uint256()); + } else if (fUseWallet) throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: transaction outputs must be unblinded or to wallet")); #endif @@ -593,7 +602,6 @@ void FillOutputBlinds(const CMutableTransaction& tx, bool fUseWallet, std::vecto output_pubkeys.push_back(CPubKey()); output_value_blinds.push_back(uint256()); output_asset_blinds.push_back(uint256()); - output_assets.push_back(CAsset()); } else { CPubKey pubkey(tx.vout[nOut].nNonce.vchCommitment); if (!pubkey.IsValid()) { @@ -602,7 +610,6 @@ void FillOutputBlinds(const CMutableTransaction& tx, bool fUseWallet, std::vecto output_pubkeys.push_back(pubkey); output_value_blinds.push_back(uint256()); output_asset_blinds.push_back(uint256()); - output_assets.push_back(CAsset()); } } } @@ -698,11 +705,11 @@ UniValue rawblindrawtransaction(const UniValue& params, bool fHelp) input_amounts.push_back(inputAmounts[nIn].get_int64()); } - FillOutputBlinds(tx, false, output_value_blinds, output_asset_blinds, output_assets, output_pubkeys); + FillOutputBlinds(tx, false, output_value_blinds, output_asset_blinds, output_pubkeys); // Since we assume all inputs must be unblinded, we can pass in blank input_amounts to BlindOutputs - if (!BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_value_blinds, output_asset_blinds, output_pubkeys, tx)) { + if (BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_value_blinds, output_asset_blinds, output_pubkeys, tx) == 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unable to blind transaction: add an additional output with a blinding pubkey")); } @@ -777,9 +784,18 @@ UniValue blindrawtransaction(const UniValue& params, bool fHelp) } } - FillOutputBlinds(tx, true, output_blinds, output_asset_blinds, output_assets, output_pubkeys); + FillOutputBlinds(tx, true, output_blinds, output_asset_blinds, output_pubkeys); - if (!BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, tx)) { + // How many are we trying to blind? + int numPubKeys = 0; + for (auto&& key : output_pubkeys) { + if (key.IsValid()) { + numPubKeys++; + } + } + + // Something must become blinded, and all attempts must work + if (numPubKeys == 0 || BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, tx) != numPubKeys) { throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unable to blind transaction: add an additional output with a blinding pubkey")); } diff --git a/src/test/blind_tests.cpp b/src/test/blind_tests.cpp index 183c6a5a05..acec712eef 100644 --- a/src/test/blind_tests.cpp +++ b/src/test/blind_tests.cpp @@ -61,6 +61,13 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) tx2->vout[0].nAsset = bitcoinID; } + { + CCoinsModifier tx2_2 = cache.ModifyCoins(ArithToUint256(5)); + tx2_2->vout.resize(1); + tx2_2->vout[0].nValue = 500; + tx2_2->vout[0].nAsset = otherID; + } + { // Build a transaction that spends 2 unblinded coins (11, 111), and produces a single blinded one (100) and fee (22). CMutableTransaction tx3; @@ -70,9 +77,8 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) tx3.vin[0].prevout.n = 0; tx3.vin[1].prevout.hash = ArithToUint256(2); tx3.vin[1].prevout.n = 0; - tx3.vout.resize(1); - tx3.vout[0].nValue = 100; - tx3.vout[0].nAsset = bitcoinID; + tx3.vout.resize(0); + tx3.vout.push_back(CTxOut(bitcoinID, 100, CScript() << OP_TRUE)); // Fee outputs are blank scriptpubkeys, and unblinded value/asset tx3.vout.push_back(CTxOut(bitcoinID, 22, CScript())); BOOST_CHECK(VerifyAmounts(cache, tx3)); @@ -93,20 +99,12 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) input_assets.push_back(bitcoinID); input_amounts.push_back(11); input_amounts.push_back(111); - output_blinds.push_back(uint256()); - output_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); output_pubkeys.push_back(pubkey1); output_pubkeys.push_back(CPubKey()); BOOST_CHECK(BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, tx3) == 0); // Add a dummy output. - tx3.vout.resize(3); - tx3.vout[2].nValue = 0; - tx3.vout[2].nAsset = bitcoinID; - output_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); + tx3.vout.push_back(CTxOut(bitcoinID, 0, CScript() << OP_TRUE)); output_pubkeys.push_back(pubkeyDummy); BOOST_CHECK(BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, tx3) == 2); BOOST_CHECK(!tx3.vout[0].nValue.IsExplicit()); @@ -162,12 +160,6 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) input_amounts.push_back(100); input_assets.push_back(unblinded_id); input_assets.push_back(unblinded_id); - output_blinds.push_back(uint256()); - output_blinds.push_back(uint256()); - output_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); output_pubkeys.push_back(CPubKey()); output_pubkeys.push_back(CPubKey()); output_pubkeys.push_back(CPubKey()); @@ -206,14 +198,6 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) input_assets.push_back(unblinded_id); input_assets.push_back(unblinded_id); - output_blinds.push_back(uint256()); - output_blinds.push_back(uint256()); - output_blinds.push_back(uint256()); - output_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); - output_asset_blinds.push_back(uint256()); output_pubkeys.push_back(pubkey2); output_pubkeys.push_back(CPubKey()); output_pubkeys.push_back(pubkey2); @@ -299,6 +283,87 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) tx4.vout[3].nValue = CConfidentialValue(tx4.vout[3].nValue.GetAmount() - 1); BOOST_CHECK(!VerifyAmounts(cache, tx4)); } -} + { + // Spends 100 blinded bitcoin, 500 of unblinded "other" + CMutableTransaction tx5; + tx5.vin.resize(0); + tx5.vout.resize(0); + tx5.vin.push_back(CTxIn(COutPoint(ArithToUint256(3), 0))); + tx5.vin.push_back(CTxIn(COutPoint(ArithToUint256(5), 0))); + tx5.vout.push_back(CTxOut(bitcoinID, 29, CScript() << OP_TRUE)); + tx5.vout.push_back(CTxOut(bitcoinID, 70, CScript() << OP_TRUE)); + tx5.vout.push_back(CTxOut(otherID, 250, CScript() << OP_TRUE)); + tx5.vout.push_back(CTxOut(otherID, 249, CScript() << OP_TRUE)); + // Fees + tx5.vout.push_back(CTxOut(bitcoinID, 1, CScript())); + tx5.vout.push_back(CTxOut(otherID, 1, CScript())); + + // Blinds don't balance + BOOST_CHECK(!VerifyAmounts(cache, tx5)); + + // Blinding setup stuff + std::vector input_blinds; + std::vector input_asset_blinds; + std::vector input_assets; + std::vector input_amounts; + std::vector output_blinds; + std::vector output_asset_blinds; + std::vector output_pubkeys; + input_blinds.push_back(blind3); + input_blinds.push_back(uint256()); // + input_asset_blinds.push_back(asset_blind); + input_asset_blinds.push_back(uint256()); + input_amounts.push_back(100); + input_amounts.push_back(500); + input_assets.push_back(bitcoinID); + input_assets.push_back(otherID); + for (unsigned int i = 0; i < 6; i++) { + output_pubkeys.push_back(pubkey2); + } + + CMutableTransaction txtemp(tx5); + + // No blinding keys for fees, bails out blinding nothing, still valid due to imbalance + BOOST_CHECK(BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, txtemp) == -1); + BOOST_CHECK(!VerifyAmounts(cache, txtemp)); + // Last will be implied blank keys + output_pubkeys.resize(4); + + // Blind transaction, verify amounts + txtemp = tx5; + BOOST_CHECK(BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, txtemp) == 4); + BOOST_CHECK(VerifyAmounts(cache, txtemp)); + + // Create imbalance by removing fees, should still be able to blind + txtemp = tx5; + txtemp.vout.resize(5); + BOOST_CHECK(!VerifyAmounts(cache, txtemp)); + txtemp.vout.resize(4); + BOOST_CHECK(!VerifyAmounts(cache, txtemp)); + BOOST_CHECK(BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, txtemp) == 4); + BOOST_CHECK(!VerifyAmounts(cache, txtemp)); + + txtemp = tx5; + // Remove other input, make surjection proof impossible for 2 "otherID" outputs + std::vector t_input_blinds; + std::vector t_input_asset_blinds; + std::vector t_input_assets; + std::vector t_input_amounts; + + t_input_blinds = input_blinds; + t_input_asset_blinds = input_asset_blinds; + t_input_assets = input_assets; + t_input_amounts = input_amounts; + txtemp.vin.resize(1); + t_input_blinds.resize(1); + t_input_asset_blinds.resize(1); + t_input_assets.resize(1); + t_input_amounts.resize(1); + BOOST_CHECK(!VerifyAmounts(cache, txtemp)); + BOOST_CHECK(BlindOutputs(t_input_blinds, t_input_asset_blinds, t_input_assets, t_input_amounts, output_blinds, output_asset_blinds, output_pubkeys, txtemp) == 2); + BOOST_CHECK(!VerifyAmounts(cache, txtemp)); + + } +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7079c6745e..b543a7a4cd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2658,7 +2658,8 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt vAmounts.push_back(txNew.vout[nOut].nValue.GetAmount()); output_assets.push_back(txNew.vout[nOut].nAsset.GetAsset()); } - + // Keep a backup of transaction in case re-blinding necessary + CMutableTransaction txBackup(txNew); if (BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, txNew) != numBlindingKeys) { // We need a dummy output to put a non-zero blinding factor. // TODO: if fBlindedOutputs, don't use an OP_RETURN but create an (extra) change output @@ -2666,15 +2667,16 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt // We need to make sure to dupe an asset that is in input set CTxOut newTxOut(output_assets.back(), 0, CScript() << OP_RETURN); - txNew.vout.push_back(newTxOut); + txBackup.vout.push_back(newTxOut); output_pubkeys.push_back(GetBlindingPubKey(newTxOut.scriptPubKey)); output_blinds.push_back(uint256()); output_asset_blinds.push_back(uint256()); output_assets.push_back(output_assets.back()); vAmounts.push_back(0); // Now it has to succeed - bool ret = BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, txNew); + int ret = BlindOutputs(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, txBackup); assert(ret); + txNew = txBackup; } if (newAsset != NULL && !newAsset->IsNull()) {