diff --git a/src/blind.cpp b/src/blind.cpp index e1a15ab05a3..f8210acf8b2 100644 --- a/src/blind.cpp +++ b/src/blind.cpp @@ -283,7 +283,7 @@ void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_co assert(conf_value.IsValid()); } -int BlindTransaction(std::vector& input_value_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& out_val_blind_factors, std::vector& out_asset_blind_factors, const std::vector& output_pubkeys, const std::vector& issuance_blinding_privkey, const std::vector& token_blinding_privkey, CMutableTransaction& tx, std::vector >* auxiliary_generators) +BlindInfo BlindTransaction(std::vector& input_value_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& out_val_blind_factors, std::vector& out_asset_blind_factors, const std::vector& output_pubkeys, const std::vector& issuance_blinding_privkey, const std::vector& token_blinding_privkey, CMutableTransaction& tx, std::vector>* auxiliary_generators) { // Sanity check input data and output_pubkey size, clear other output data assert(tx.vout.size() >= output_pubkeys.size()); @@ -304,8 +304,8 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const value_blindptrs.reserve(tx.vout.size() + tx.vin.size()); asset_blindptrs.reserve(tx.vout.size() + tx.vin.size()); - int ret; - int num_blind_attempts = 0, num_issuance_blind_attempts = 0, num_blinded = 0; + int num_blind_attempts = 0, num_issuance_blind_attempts = 0; + uint16_t num_blinded = 0; //Surjection proof prep @@ -337,18 +337,16 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const // If non-empty generator exists, parse if (auxiliary_generators) { // Parse generator here - ret = secp256k1_generator_parse(secp256k1_blind_context, &target_asset_generators[totalTargets], &(*auxiliary_generators)[i][0]); - if (ret != 1) { - return -1; + if (secp256k1_generator_parse(secp256k1_blind_context, &target_asset_generators[totalTargets], &(*auxiliary_generators)[i][0]) != 1) { + return BlindInfo { BlindStatus::ERR_GENERATOR_PARSE , num_blinded }; } } else { - return -1; + return BlindInfo { BlindStatus::ERR_NO_AUX_GENERATORS, num_blinded }; } } else { - ret = secp256k1_generator_generate_blinded(secp256k1_blind_context, &target_asset_generators[totalTargets], input_assets[i].begin(), input_asset_blinding_factors[i].begin()); - if (ret != 1) { + if (secp256k1_generator_generate_blinded(secp256k1_blind_context, &target_asset_generators[totalTargets], input_assets[i].begin(), input_asset_blinding_factors[i].begin()) != 1) { // Possibly invalid blinding factor provided by user. - return -1; + return BlindInfo { BlindStatus::ERR_FAIL_BLINDED_GENERATOR, num_blinded }; } } memcpy(&surjection_targets[totalTargets], input_assets[i].begin(), 32); @@ -362,7 +360,7 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const CAsset token; if (!issuance.IsNull()) { if (issuance.nAmount.IsCommitment() || issuance.nInflationKeys.IsCommitment()) { - return -1; + return BlindInfo { BlindStatus::ERR_ISSUANCE_INVALID, num_blinded }; } // New Issuance if (issuance.assetBlindingNonce.IsNull()) { @@ -376,7 +374,7 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const if (!issuance.nAmount.IsNull()) { memcpy(&surjection_targets[totalTargets], asset.begin(), 32); - ret = secp256k1_generator_generate(secp256k1_blind_context, &target_asset_generators[totalTargets], asset.begin()); + int ret = secp256k1_generator_generate(secp256k1_blind_context, &target_asset_generators[totalTargets], asset.begin()); assert(ret != 0); // Issuance asset cannot be blinded by definition target_asset_blinders.push_back(uint256()); @@ -385,7 +383,7 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const if (!issuance.nInflationKeys.IsNull()) { assert(!token.IsNull()); memcpy(&surjection_targets[totalTargets], token.begin(), 32); - ret = secp256k1_generator_generate(secp256k1_blind_context, &target_asset_generators[totalTargets], token.begin()); + int ret = secp256k1_generator_generate(secp256k1_blind_context, &target_asset_generators[totalTargets], token.begin()); assert(ret != 0); // Issuance asset cannot be blinded by definition target_asset_blinders.push_back(uint256()); @@ -398,9 +396,8 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const // Process any additional targets from auxiliary_generators // we know nothing about it other than the generator itself for (size_t i = tx.vin.size(); i < auxiliary_generators->size(); i++) { - ret = secp256k1_generator_parse(secp256k1_blind_context, &target_asset_generators[totalTargets], &(*auxiliary_generators)[i][0]); - if (ret != 1) { - return -1; + if (secp256k1_generator_parse(secp256k1_blind_context, &target_asset_generators[totalTargets], &(*auxiliary_generators)[i][0]) != 1) { + return BlindInfo { BlindStatus::ERR_GENERATOR_PARSE, num_blinded }; } memset(&surjection_targets[totalTargets], 0, 32); target_asset_blinders.push_back(uint256()); @@ -426,7 +423,7 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { if (!input_value_blinding_factors[nIn].IsNull() || !input_asset_blinding_factors[nIn].IsNull()) { if (input_amounts[nIn] < 0) { - return -1; + return BlindInfo { BlindStatus::ERR_NEGATIVE_INPUT_AMOUNT , num_blinded }; } value_blindptrs.push_back(input_value_blinding_factors[nIn].begin()); asset_blindptrs.push_back(input_asset_blinding_factors[nIn].begin()); @@ -442,14 +439,14 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const if(issuance.nAmount.IsExplicit() && tx.witness.vtxinwit[nIn].vchIssuanceAmountRangeproof.empty()) { num_to_blind++; } else { - return -1; + return BlindInfo { BlindStatus::ERR_ISSUANCE_INVALID, num_blinded }; } } if (token_blinding_privkey.size() > nIn && token_blinding_privkey[nIn].IsValid()) { if(issuance.nInflationKeys.IsExplicit() && tx.witness.vtxinwit[nIn].vchInflationKeysRangeproof.empty()) { num_to_blind++; } else { - return -1; + return BlindInfo { BlindStatus::ERR_ISSUANCE_INVALID, num_blinded }; } } } @@ -458,18 +455,21 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const 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 + bool is_fee = tx.vout[nOut].IsFee(); if (!output_pubkeys[nOut].IsFullyValid() || (!tx.vout[nOut].nValue.IsExplicit() || !tx.vout[nOut].nAsset.IsExplicit()) || (txoutwitsize > nOut && !tx.witness.vtxoutwit[nOut].IsNull()) - || tx.vout[nOut].IsFee()) { - return -1; + || is_fee) { + auto status = BlindStatus::ERR_PUBKEY_INVALID_OR_OUTPUTS_BLINDED; + if (is_fee) status = BlindStatus::ERR_OUTPUT_IS_FEE; + return BlindInfo { status, num_blinded }; } num_to_blind++; } } - //Running total of newly blinded outputs + // Running total of newly blinded outputs static const unsigned char diff_zero[32] = {0}; assert(num_to_blind <= 10000); // More than 10k outputs? Stop spamming. unsigned char blind[10000][32]; @@ -514,16 +514,16 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const } // Fill out the value blinders and blank asset blinder - GetStrongRandBytes(&blind[num_blind_attempts-1][0], 32); + GetStrongRandBytes(&blind[num_blind_attempts - 1][0], 32); // Issuances are not asset-blinded - memset(&asset_blind[num_blind_attempts-1][0], 0, 32); - value_blindptrs.push_back(&blind[num_blind_attempts-1][0]); - asset_blindptrs.push_back(&asset_blind[num_blind_attempts-1][0]); + memset(&asset_blind[num_blind_attempts - 1][0], 0, 32); + value_blindptrs.push_back(&blind[num_blind_attempts - 1][0]); + asset_blindptrs.push_back(&asset_blind[num_blind_attempts - 1][0]); if (num_blind_attempts == num_to_blind) { // All outputs we own are unblinded, we don't support this type of blinding // though it is possible. No privacy gained here, incompatible with secp api - return num_blinded; + return BlindInfo { BlindStatus::ERR_ALL_OUTPUTS_OWNED_UNBLINDED, num_blinded }; } if (tx.witness.vtxinwit.size() <= nIn) { @@ -579,14 +579,13 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const // Adversary would need to create all input blinds // therefore would already know all your summed output amount anyways. if (num_blind_attempts == 1 && num_known_input_blinds == 0) { - return num_blinded; + return BlindInfo { BlindStatus::ERR_SINGLE_ATTEMPT_WITH_NO_INPUT_BLINDS, num_blinded }; } // Generate value we intend to insert - ret = secp256k1_pedersen_blind_generator_blind_sum(secp256k1_blind_context, &blinded_amounts[0], &asset_blindptrs[0], &value_blindptrs[0], num_blind_attempts + num_known_input_blinds, num_issuance_blind_attempts + num_known_input_blinds); - if (!ret) { + if (!secp256k1_pedersen_blind_generator_blind_sum(secp256k1_blind_context, &blinded_amounts[0], &asset_blindptrs[0], &value_blindptrs[0], num_blind_attempts + num_known_input_blinds, num_issuance_blind_attempts + num_known_input_blinds)) { // Possibly invalid blinding factor provided by user. - return -1; + return BlindInfo { BlindStatus::ERR_BLINDING_KEY_INVALID, num_blinded }; } // Resulting blinding factor can sometimes be 0 @@ -598,7 +597,7 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const // abort and not blind and the math adds up. // Count as success(to signal caller that nothing wrong) and return early if (memcmp(diff_zero, &blind[num_blind_attempts-1][0], 32) == 0) { - return ++num_blinded; + return BlindInfo { BlindStatus::SUCCESS, ++num_blinded }; } } @@ -630,7 +629,7 @@ int BlindTransaction(std::vector& input_value_blinding_factors, const } } - return num_blinded; + return BlindInfo { BlindStatus::SUCCESS, num_blinded }; } void RawFillBlinds(CMutableTransaction& tx, std::vector& output_value_blinds, std::vector& output_asset_blinds, std::vector& output_pubkeys) { @@ -653,3 +652,35 @@ void RawFillBlinds(CMutableTransaction& tx, std::vector& output_value_b assert(output_pubkeys.size() == tx.vout.size()); // We cannot unwind issuance inputs because there is no nonce placeholder for pubkeys } + +std::string BlindStatusString(const BlindStatus status) +{ + switch (status) + { + case BlindStatus::SUCCESS: + return "All outputs successfully blinded."; + case BlindStatus::ERR_GENERATOR_PARSE: + return "Failed to parse the auxiliary generator."; + case BlindStatus::ERR_NO_AUX_GENERATORS: + return "Missing expected auxiliary generator."; + case BlindStatus::ERR_FAIL_BLINDED_GENERATOR: + return "Failed to generate a blinded generator for the curve."; + case BlindStatus::ERR_ISSUANCE_INVALID: + return "Issuance outputs are invalid. Either they are already blinded, or they had existing range proofs."; + case BlindStatus::ERR_NEGATIVE_INPUT_AMOUNT: + return "Given input amount is invalid as it is negative."; + case BlindStatus::ERR_PUBKEY_INVALID_OR_OUTPUTS_BLINDED: + return "Given PubKey is either invalid or the outputs were already blinded."; + case BlindStatus::ERR_OUTPUT_IS_FEE: + return "Fee outputs must be explicit."; + case BlindStatus::ERR_ALL_OUTPUTS_OWNED_UNBLINDED: + return "All outputs we own are unblinded. This type of blinding is not supported."; + case BlindStatus::ERR_BLINDING_KEY_INVALID: + return "A blinding factor or generator blind are invalid. Retry with different values."; + case BlindStatus::ERR_SINGLE_ATTEMPT_WITH_NO_INPUT_BLINDS: + return "Number of known input blinds is 0 and number of blind attempts is 1."; + default: + break; + } + return "unknown error"; +} diff --git a/src/blind.h b/src/blind.h index 483444ad35d..54e0c8f93b2 100644 --- a/src/blind.h +++ b/src/blind.h @@ -48,7 +48,7 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& bool GenerateRangeproof(std::vector& rangeproof, const std::vector& value_blindptrs, const uint256& nonce, const CAmount amount, const CScript& scriptPubKey, const secp256k1_pedersen_commitment& value_commit, const secp256k1_generator& gen, const CAsset& asset, std::vector& asset_blindptrs); -bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector& surjection_targets, const std::vector& target_asset_generators, const std::vector& target_asset_blinders, const std::vector asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset); +bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector& surjection_targets, const std::vector& target_asset_generators, const std::vector& target_asset_blinders, const std::vector asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset); uint256 GenerateOutputRangeproofNonce(CTxOut& out, const CPubKey output_pubkey); @@ -56,8 +56,27 @@ void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_commitment& value_commit, const unsigned char* value_blindptr, const secp256k1_generator& asset_gen, const CAmount amount); -/* Returns the number of outputs that were successfully blinded. - * In many cases a `0` can be fixed by adding an additional output. +enum class BlindStatus { + SUCCESS, + ERR_GENERATOR_PARSE, + ERR_NO_AUX_GENERATORS, + ERR_FAIL_BLINDED_GENERATOR, + ERR_ISSUANCE_INVALID, + ERR_NEGATIVE_INPUT_AMOUNT, + ERR_PUBKEY_INVALID_OR_OUTPUTS_BLINDED, + ERR_OUTPUT_IS_FEE, + ERR_ALL_OUTPUTS_OWNED_UNBLINDED, + ERR_BLINDING_KEY_INVALID, + ERR_SINGLE_ATTEMPT_WITH_NO_INPUT_BLINDS, +}; + +struct BlindInfo { + BlindStatus status; + uint16_t num_blinded = 0; +}; + +/* Returns the blinding status and number of outputs that were successfully blinded. + * In many cases an error 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 of each corresponding input @@ -70,11 +89,13 @@ void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_co * @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. Passing in non-empty elements results in ignoring of other input arguments for that index */ -int BlindTransaction(std::vector& input_value_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& out_val_blind_factors, std::vector& out_asset_blind_factors, const std::vector& output_pubkeys, const std::vector& issuance_blinding_privkey, const std::vector& token_blinding_privkey, CMutableTransaction& tx, std::vector >* auxiliary_generators = nullptr); +BlindInfo BlindTransaction(std::vector& input_value_blinding_factors, const std::vector& input_asset_blinding_factors, const std::vector& input_assets, const std::vector& input_amounts, std::vector& out_val_blind_factors, std::vector& out_asset_blind_factors, const std::vector& output_pubkeys, const std::vector& issuance_blinding_privkey, const std::vector& token_blinding_privkey, CMutableTransaction& tx, std::vector>* auxiliary_generators = nullptr); /* * Extract pubkeys from nonce commitment placeholders, fill out vector of blank output blinding data */ void RawFillBlinds(CMutableTransaction& tx, std::vector& output_value_blinds, std::vector& output_asset_blinds, std::vector& output_pubkeys); +std::string BlindStatusString(const BlindStatus status); + #endif // BITCOIN_BLIND_H diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 1881bb12f7e..4a29740a825 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -2854,11 +2854,11 @@ static RPCHelpMan rawblindrawtransaction() } } - int ret = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_value_blinds, output_asset_blinds, output_pubkeys, std::vector(), std::vector(), tx); - if (ret != num_pubkeys) { - // TODO Have more rich return values, communicating to user what has been blinded - // User may be ok not blinding something that for instance has no corresponding type on input - throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?"); + auto info = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_value_blinds, output_asset_blinds, output_pubkeys, std::vector(), std::vector(), tx); + if (info.num_blinded != num_pubkeys) { + auto status = BlindStatusString(info.status); + auto message = strprintf("Unable to blind transaction: %s Number of blinded outputs: %d.", status, info.num_blinded); + throw JSONRPCError(RPC_INVALID_PARAMETER, message); } return EncodeHexTx(CTransaction(tx)); diff --git a/src/test/blind_tests.cpp b/src/test/blind_tests.cpp index 02e53a01b15..aec5fd98910 100644 --- a/src/test/blind_tests.cpp +++ b/src/test/blind_tests.cpp @@ -117,12 +117,12 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) input_amounts.push_back(111); output_pubkeys.push_back(pubkey1); output_pubkeys.push_back(CPubKey()); - BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx3) == 0); + BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx3).num_blinded == 0); // Add a dummy output. Must be unspendable since it's 0-valued. tx3.vout.push_back(CTxOut(bitcoinID, 0, CScript() << OP_RETURN)); output_pubkeys.push_back(pubkeyDummy); - BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx3) == 2); + BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx3).num_blinded == 2); BOOST_CHECK(!tx3.vout[0].nValue.IsExplicit()); BOOST_CHECK(!tx3.vout[2].nValue.IsExplicit()); BOOST_CHECK(VerifyAmounts(inputs, CTransaction(tx3), nullptr, false)); @@ -182,7 +182,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) output_pubkeys.push_back(CPubKey()); output_pubkeys.push_back(CPubKey()); output_pubkeys.push_back(CPubKey()); - BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx4) == 0); // Blinds nothing + BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx4).num_blinded == 0); // Blinds nothing } { @@ -226,7 +226,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) output_pubkeys.push_back(pubkey2); output_pubkeys.push_back(CPubKey()); - BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx4) == 2); + BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, tx4).num_blinded == 2); BOOST_CHECK(!tx4.vout[0].nValue.IsExplicit()); BOOST_CHECK(tx4.vout[1].nValue.IsExplicit()); BOOST_CHECK(!tx4.vout[2].nValue.IsExplicit()); @@ -323,14 +323,16 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) CMutableTransaction txtemp(tx5); // No blinding keys for fees, bails out blinding nothing, still invalid due to imbalance - BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp) == -1); + auto info = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp); + BOOST_CHECK(info.status == BlindStatus::ERR_OUTPUT_IS_FEE); + BOOST_CHECK(info.num_blinded == 0); BOOST_CHECK(!VerifyAmounts(inputs, CTransaction(txtemp), nullptr, false)); // Last will be implied blank keys output_pubkeys.resize(4); // Blind transaction, verify amounts txtemp = tx5; - BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp) == 4); + BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp).num_blinded == 4); BOOST_CHECK(VerifyAmounts(inputs, CTransaction(txtemp), nullptr, false)); // Transaction may not have spendable 0-value output @@ -343,7 +345,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) BOOST_CHECK(!VerifyAmounts(inputs, CTransaction(txtemp), nullptr, false)); txtemp.vout.resize(4); BOOST_CHECK(!VerifyAmounts(inputs, CTransaction(txtemp), nullptr, false)); - BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp) == 4); + BOOST_CHECK(BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp).num_blinded == 4); BOOST_CHECK(!VerifyAmounts(inputs, CTransaction(txtemp), nullptr, false)); txtemp = tx5; @@ -364,7 +366,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) t_input_assets.resize(1); t_input_amounts.resize(1); BOOST_CHECK(!VerifyAmounts(inputs, CTransaction(txtemp), nullptr, false)); - BOOST_CHECK(BlindTransaction(t_input_blinds, t_input_asset_blinds, t_input_assets, t_input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp) == 2); + BOOST_CHECK(BlindTransaction(t_input_blinds, t_input_asset_blinds, t_input_assets, t_input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vDummy, vDummy, txtemp).num_blinded == 2); BOOST_CHECK(!VerifyAmounts(inputs, CTransaction(txtemp), nullptr, false)); } } diff --git a/src/wallet/rpc/elements.cpp b/src/wallet/rpc/elements.cpp index 296d8b8561c..400c9fdc7c5 100644 --- a/src/wallet/rpc/elements.cpp +++ b/src/wallet/rpc/elements.cpp @@ -1293,10 +1293,11 @@ RPCHelpMan blindrawtransaction() } } - if (BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, asset_keys, token_keys, tx, (auxiliary_generators.size() ? &auxiliary_generators : NULL)) != num_pubkeys) { - // TODO Have more rich return values, communicating to user what has been blinded - // User may be ok not blinding something that for instance has no corresponding type on input - throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?"); + auto info = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, asset_keys, token_keys, tx, (auxiliary_generators.size() ? &auxiliary_generators : NULL)); + if (info.num_blinded != num_pubkeys) { + auto status = BlindStatusString(info.status); + auto message = strprintf("Unable to blind transaction: %s Number of blinded outputs: %d. Are you sure each asset type to blind is represented in the inputs?", status, info.num_blinded); + throw JSONRPCError(RPC_INVALID_PARAMETER, message); } return EncodeHexTx(CTransaction(tx)); diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 65a7a6f228d..72ee99ec34a 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -1441,10 +1441,15 @@ static bool CreateTransactionInternal( } txNew = tx_blinded; // sigh, `fillBlindDetails` may have modified txNew - int ret = BlindTransaction(blind_details->i_amount_blinds, blind_details->i_asset_blinds, blind_details->i_assets, blind_details->i_amounts, blind_details->o_amount_blinds, blind_details->o_asset_blinds, blind_details->o_pubkeys, issuance_asset_keys, issuance_token_keys, tx_blinded); - assert(ret != -1); - if (ret != blind_details->num_to_blind) { - error = _("Unable to blind the transaction properly. This should not happen."); + auto info = BlindTransaction(blind_details->i_amount_blinds, blind_details->i_asset_blinds, blind_details->i_assets, blind_details->i_amounts, blind_details->o_amount_blinds, blind_details->o_asset_blinds, blind_details->o_pubkeys, issuance_asset_keys, issuance_token_keys, tx_blinded); + assert(info.status == BlindStatus::SUCCESS || (info.status == BlindStatus::ERR_SINGLE_ATTEMPT_WITH_NO_INPUT_BLINDS && blind_details->num_to_blind == 0)); + if (info.num_blinded != blind_details->num_to_blind) { + auto message = strprintf("Unable to blind transaction: Number to blind: %d. Number blinded: %d.", blind_details->num_to_blind, info.num_blinded); + auto num_inputs = result->GetInputSet().size(); + if (num_inputs > 256) { + message = strprintf("Unable to blind transaction. Only 256 inputs can be blinded at once. Transaction has %d inputs.", num_inputs); + } + error = Untranslated(message); return false; } @@ -1646,11 +1651,17 @@ static bool CreateTransactionInternal( } if (sign) { - int ret = BlindTransaction(blind_details->i_amount_blinds, blind_details->i_asset_blinds, blind_details->i_assets, blind_details->i_amounts, blind_details->o_amount_blinds, blind_details->o_asset_blinds, blind_details->o_pubkeys, issuance_asset_keys, issuance_token_keys, txNew); - assert(ret != -1); - if (ret != blind_details->num_to_blind) { - wallet.WalletLogPrintf("ERROR: tried to blind %d outputs but only blinded %d\n", (int) blind_details->num_to_blind, (int) ret); - error = _("Unable to blind the transaction properly. This should not happen."); + auto info = BlindTransaction(blind_details->i_amount_blinds, blind_details->i_asset_blinds, blind_details->i_assets, blind_details->i_amounts, blind_details->o_amount_blinds, blind_details->o_asset_blinds, blind_details->o_pubkeys, issuance_asset_keys, issuance_token_keys, txNew); + assert(info.status == BlindStatus::SUCCESS || (info.status == BlindStatus::ERR_SINGLE_ATTEMPT_WITH_NO_INPUT_BLINDS && blind_details->num_to_blind == 0)); + if (info.num_blinded != blind_details->num_to_blind) { + auto status = BlindStatusString(info.status); + auto message = strprintf("Unable to blind transaction: %s Number of blinded outputs: %d.", status, info.num_blinded); + auto num_inputs = result->GetInputSet().size(); + wallet.WalletLogPrintf("ERROR: tried to blind %d outputs but only blinded %d. Number of inputs: %d. Blind status: %s\n", (int)blind_details->num_to_blind, info.num_blinded, num_inputs, status); + if (num_inputs > 256) { + message = strprintf("Unable to blind transaction. Only 256 inputs can be blinded at once. Transaction has %d inputs.", num_inputs); + } + error = Untranslated(message); return false; } } diff --git a/test/functional/feature_confidential_transactions.py b/test/functional/feature_confidential_transactions.py index 3ecb74fdd58..54a17c4bfde 100755 --- a/test/functional/feature_confidential_transactions.py +++ b/test/functional/feature_confidential_transactions.py @@ -723,8 +723,8 @@ def run_test(self): # Make sure RPC throws when an invalid blinding factor is provided. bad_blinder = 'FF'*32 - assert_raises_rpc_error(-8, "Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?", self.nodes[0].rawblindrawtransaction, rawtx, [unspent[0]["amountblinder"], bad_blinder], [unspent[0]["amount"], unspent[1]["amount"]], [unspent[0]["asset"], unspent[1]["asset"]], [unspent[0]["assetblinder"], unspent[1]["assetblinder"]]) - assert_raises_rpc_error(-8, "Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?", self.nodes[0].rawblindrawtransaction, rawtx, [unspent[0]["amountblinder"], unspent[1]["amountblinder"]], [unspent[0]["amount"], unspent[1]["amount"]], [unspent[0]["asset"], unspent[1]["asset"]], [unspent[0]["assetblinder"], bad_blinder]) + assert_raises_rpc_error(-8, "Unable to blind transaction: A blinding factor or generator blind are invalid. Retry with different values. Number of blinded outputs: 0.", self.nodes[0].rawblindrawtransaction, rawtx, [unspent[0]["amountblinder"], bad_blinder], [unspent[0]["amount"], unspent[1]["amount"]], [unspent[0]["asset"], unspent[1]["asset"]], [unspent[0]["assetblinder"], unspent[1]["assetblinder"]]) + assert_raises_rpc_error(-8, "Unable to blind transaction: Failed to generate a blinded generator for the curve. Number of blinded outputs: 0.", self.nodes[0].rawblindrawtransaction, rawtx, [unspent[0]["amountblinder"], unspent[1]["amountblinder"]], [unspent[0]["amount"], unspent[1]["amount"]], [unspent[0]["asset"], unspent[1]["asset"]], [unspent[0]["assetblinder"], bad_blinder]) blindtx = self.nodes[0].rawblindrawtransaction(rawtx, [unspent[0]["amountblinder"], unspent[1]["amountblinder"]], [unspent[0]["amount"], unspent[1]["amount"]], [unspent[0]["asset"], unspent[1]["asset"]], [unspent[0]["assetblinder"], unspent[1]["assetblinder"]]) signtx = self.nodes[0].signrawtransactionwithwallet(blindtx) diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 1862f665b14..465b09cdc4d 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -1287,7 +1287,7 @@ def test_surjectionproof_many_inputs(self): # ...and try to send them all in one transaction # This should fail but we should not see an assertion failure. rawtx = recipient.createrawtransaction([], [{wallet.getnewaddress(): 49.99}]) - assert_raises_rpc_error(-4, "Unable to blind the transaction properly. This should not happen.", recipient.fundrawtransaction, rawtx) + assert_raises_rpc_error(-4, "Unable to blind transaction. Only 256 inputs can be blinded at once. Transaction has 500 inputs.", recipient.fundrawtransaction, rawtx) # Try to send them across two transactions. This should succeed. rawtx = recipient.createrawtransaction([], [{wallet.getnewaddress(): 24.99}])