Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: change BlindTransaction to return more detailed error information #1288

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 64 additions & 33 deletions src/blind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ void CreateValueCommitment(CConfidentialValue& conf_value, secp256k1_pedersen_co
assert(conf_value.IsValid());
}

int BlindTransaction(std::vector<uint256 >& input_value_blinding_factors, const std::vector<uint256 >& input_asset_blinding_factors, const std::vector<CAsset >& input_assets, const std::vector<CAmount >& input_amounts, std::vector<uint256 >& out_val_blind_factors, std::vector<uint256 >& out_asset_blind_factors, const std::vector<CPubKey>& output_pubkeys, const std::vector<CKey>& issuance_blinding_privkey, const std::vector<CKey>& token_blinding_privkey, CMutableTransaction& tx, std::vector<std::vector<unsigned char> >* auxiliary_generators)
BlindInfo BlindTransaction(std::vector<uint256>& input_value_blinding_factors, const std::vector<uint256>& input_asset_blinding_factors, const std::vector<CAsset>& input_assets, const std::vector<CAmount>& input_amounts, std::vector<uint256>& out_val_blind_factors, std::vector<uint256>& out_asset_blind_factors, const std::vector<CPubKey>& output_pubkeys, const std::vector<CKey>& issuance_blinding_privkey, const std::vector<CKey>& token_blinding_privkey, CMutableTransaction& tx, std::vector<std::vector<unsigned char>>* auxiliary_generators)
{
// Sanity check input data and output_pubkey size, clear other output data
assert(tx.vout.size() >= output_pubkeys.size());
Expand All @@ -304,8 +304,8 @@ int BlindTransaction(std::vector<uint256 >& 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

Expand Down Expand Up @@ -337,18 +337,16 @@ int BlindTransaction(std::vector<uint256 >& 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);
Expand All @@ -362,7 +360,7 @@ int BlindTransaction(std::vector<uint256 >& 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()) {
Expand All @@ -376,7 +374,7 @@ int BlindTransaction(std::vector<uint256 >& 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());
Expand All @@ -385,7 +383,7 @@ int BlindTransaction(std::vector<uint256 >& 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());
Expand All @@ -398,9 +396,8 @@ int BlindTransaction(std::vector<uint256 >& 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());
Expand All @@ -426,7 +423,7 @@ int BlindTransaction(std::vector<uint256 >& 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());
Expand All @@ -442,14 +439,14 @@ int BlindTransaction(std::vector<uint256 >& 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 };
}
}
}
Expand All @@ -458,18 +455,21 @@ int BlindTransaction(std::vector<uint256 >& 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];
Expand Down Expand Up @@ -514,16 +514,16 @@ int BlindTransaction(std::vector<uint256 >& 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) {
Expand Down Expand Up @@ -579,14 +579,13 @@ int BlindTransaction(std::vector<uint256 >& 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
Expand All @@ -598,7 +597,7 @@ int BlindTransaction(std::vector<uint256 >& 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 };
}
}

Expand Down Expand Up @@ -630,7 +629,7 @@ int BlindTransaction(std::vector<uint256 >& input_value_blinding_factors, const
}
}

return num_blinded;
return BlindInfo { BlindStatus::SUCCESS, num_blinded };
}

void RawFillBlinds(CMutableTransaction& tx, std::vector<uint256>& output_value_blinds, std::vector<uint256>& output_asset_blinds, std::vector<CPubKey>& output_pubkeys) {
Expand All @@ -653,3 +652,35 @@ void RawFillBlinds(CMutableTransaction& tx, std::vector<uint256>& 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";
}
29 changes: 25 additions & 4 deletions src/blind.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,35 @@ bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue&

bool GenerateRangeproof(std::vector<unsigned char>& rangeproof, const std::vector<unsigned char*>& 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<const unsigned char*>& asset_blindptrs);

bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector<secp256k1_fixed_asset_tag>& surjection_targets, const std::vector<secp256k1_generator>& target_asset_generators, const std::vector<uint256 >& target_asset_blinders, const std::vector<const unsigned char*> asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset);
bool SurjectOutput(CTxOutWitness& txoutwit, const std::vector<secp256k1_fixed_asset_tag>& surjection_targets, const std::vector<secp256k1_generator>& target_asset_generators, const std::vector<uint256>& target_asset_blinders, const std::vector<const unsigned char*> asset_blindptrs, const secp256k1_generator& output_asset_gen, const CAsset& asset);

uint256 GenerateOutputRangeproofNonce(CTxOut& out, const CPubKey output_pubkey);

void BlindAsset(CConfidentialAsset& conf_asset, secp256k1_generator& asset_gen, const CAsset& asset, const unsigned char* asset_blindptr);

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
Expand All @@ -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<uint256 >& input_value_blinding_factors, const std::vector<uint256 >& input_asset_blinding_factors, const std::vector<CAsset >& input_assets, const std::vector<CAmount >& input_amounts, std::vector<uint256 >& out_val_blind_factors, std::vector<uint256 >& out_asset_blind_factors, const std::vector<CPubKey>& output_pubkeys, const std::vector<CKey>& issuance_blinding_privkey, const std::vector<CKey>& token_blinding_privkey, CMutableTransaction& tx, std::vector<std::vector<unsigned char> >* auxiliary_generators = nullptr);
BlindInfo BlindTransaction(std::vector<uint256>& input_value_blinding_factors, const std::vector<uint256>& input_asset_blinding_factors, const std::vector<CAsset>& input_assets, const std::vector<CAmount>& input_amounts, std::vector<uint256>& out_val_blind_factors, std::vector<uint256>& out_asset_blind_factors, const std::vector<CPubKey>& output_pubkeys, const std::vector<CKey>& issuance_blinding_privkey, const std::vector<CKey>& token_blinding_privkey, CMutableTransaction& tx, std::vector<std::vector<unsigned char>>* auxiliary_generators = nullptr);

/*
* Extract pubkeys from nonce commitment placeholders, fill out vector of blank output blinding data
*/
void RawFillBlinds(CMutableTransaction& tx, std::vector<uint256>& output_value_blinds, std::vector<uint256>& output_asset_blinds, std::vector<CPubKey>& output_pubkeys);

std::string BlindStatusString(const BlindStatus status);

#endif // BITCOIN_BLIND_H
10 changes: 5 additions & 5 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CKey>(), std::vector<CKey>(), 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<CKey>(), std::vector<CKey>(), 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));
Expand Down
Loading