Skip to content

Commit

Permalink
Merge e620722 into merged_master (Elements PR ElementsProject#911)
Browse files Browse the repository at this point in the history
Spent a while figuring out where this new function should live now that
the PSBT stuff has been pulled into src/psbt.cpp and the wallet, and isn't
mixed up with the RPC logic.

In the end I modified the the function to return a normal error rather
than throwing an RPC exception and put it in src/psbt.cpp with the other
functions that behave this way.
  • Loading branch information
apoelstra committed Nov 30, 2020
2 parents c7bac89 + e620722 commit bcfa6d9
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 0 deletions.
68 changes: 68 additions & 0 deletions src/blind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,74 @@ class Blind_ECC_Init {

static Blind_ECC_Init ecc_init_on_load;

bool VerifyConfidentialPair(const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CAmount& claimed_value, const CAsset& claimed_asset, const uint256& value_blinding_factor, const uint256& asset_blinding_factor) {
if (conf_value.IsNull() || conf_asset.IsNull() || claimed_asset.IsNull()) {
return false;
}

if (conf_value.IsExplicit()) {
// Match behavior of UnblindConfidentialPair
return false;
}
if (conf_asset.IsExplicit() && conf_asset.GetAsset() != claimed_asset) {
return false;
}

// Just to be safe
if (!MoneyRange(claimed_value)) {
return false;
}

// Valid asset commitment?
secp256k1_generator observed_gen;
if (conf_asset.IsCommitment()) {
if (secp256k1_generator_parse(secp256k1_blind_context, &observed_gen, &conf_asset.vchCommitment[0]) != 1)
return false;
} else if (conf_asset.IsExplicit()) {
if (secp256k1_generator_generate(secp256k1_blind_context, &observed_gen, conf_asset.GetAsset().begin()) != 1)
return false;
}

// Valid value commitment?
secp256k1_pedersen_commitment value_commit;
if (secp256k1_pedersen_commitment_parse(secp256k1_blind_context, &value_commit, conf_value.vchCommitment.data()) != 1) {
return false;
}

const unsigned char *asset_type = claimed_asset.id.begin();
const unsigned char *asset_blinder = asset_blinding_factor.begin();
secp256k1_generator recalculated_gen;
if (secp256k1_generator_generate_blinded(secp256k1_blind_context, &recalculated_gen, asset_type, asset_blinder) != 1) {
return false;
}

// Serialize both generators then compare
unsigned char observed_generator[33];
unsigned char derived_generator[33];
secp256k1_generator_serialize(secp256k1_blind_context, observed_generator, &observed_gen);
secp256k1_generator_serialize(secp256k1_blind_context, derived_generator, &recalculated_gen);
if (memcmp(observed_generator, derived_generator, sizeof(observed_generator))) {
return false;
}

const unsigned char *value_blinder = value_blinding_factor.begin();
secp256k1_pedersen_commitment recalculated_commit;
if(secp256k1_pedersen_commit(secp256k1_blind_context, &recalculated_commit, value_blinder, claimed_value, &observed_gen) != 1) {
return false;
}

// Serialize both value commitments then compare
unsigned char claimed_commitment[33];
unsigned char derived_commitment[33];
secp256k1_pedersen_commitment_serialize(secp256k1_blind_context, claimed_commitment, &value_commit);
secp256k1_pedersen_commitment_serialize(secp256k1_blind_context, derived_commitment, &recalculated_commit);
if (memcmp(claimed_commitment, derived_commitment, sizeof(claimed_commitment))) {
return false;
}

return true;
}

bool UnblindConfidentialPair(const CKey& blinding_key, const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CConfidentialNonce& nonce_commitment, const CScript& committedScript, const std::vector<unsigned char>& vchRangeproof, CAmount& amount_out, uint256& blinding_factor_out, CAsset& asset_out, uint256& asset_blinding_factor_out)
{
if (!blinding_key.IsValid() || vchRangeproof.size() == 0) {
Expand Down
8 changes: 8 additions & 0 deletions src/blind.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ static const size_t DEFAULT_SURJECTIONPROOF_SIZE = 135;
// 32 bytes of asset type, 32 bytes of asset blinding factor in sidechannel
static const size_t SIDECHANNEL_MSG_SIZE = 64;

/*
* Verify a pair of confidential asset and value, given the blinding factors for both.
* Unlike UnblindConfidentialPair, this does _not_ require the recipient's blinding
* key, but it _does_ require the blinding factors be provided (rather than extracting
* them from the rangeproof.)
*/
bool VerifyConfidentialPair(const CConfidentialValue& conf_value, const CConfidentialAsset& conf_asset, const CAmount& claimed_value, const CAsset& claimed_asset, const uint256& value_blinding_factor, const uint256& asset_blinding_factor);

/*
* Unblind a pair of confidential asset and value.
* Note that unblinded data will only be outputted if *BOTH* asset and value could be unblinded.
Expand Down
41 changes: 41 additions & 0 deletions src/psbt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <blind.h>
#include <pegins.h>
#include <psbt.h>
#include <util/strencodings.h>
Expand Down Expand Up @@ -443,3 +444,43 @@ bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data,
}
return true;
}

bool CheckPSBTBlinding(const PartiallySignedTransaction& psbtx, std::string& error) {
// Plausibly, we may want a way to let the user continue anyway. However, we
// want to fail by default, to make it as hard as possible to do something
// really dangerous. And since this way of handling blinded PSBTs is going
// away "real soon now" in favor of a better one, no sense in trying too
// hard about it.

for (size_t i = 0; i < psbtx.outputs.size(); ++i) {
const PSBTOutput& output = psbtx.outputs[i];
const CTxOut& txo = psbtx.tx->vout[i];

if (txo.nValue.IsCommitment() || txo.nAsset.IsCommitment()) {
error = "PSBT's 'tx' field may not have pre-blinded outputs.";
return false;
}

if (!output.value_commitment.IsCommitment() &&
!output.asset_commitment.IsCommitment() &&
output.value_blinding_factor.IsNull() &&
output.asset_blinding_factor.IsNull()) {
// Nothing blinded, nothing to check.
continue;
} else if (!output.value_commitment.IsCommitment() ||
!output.asset_commitment.IsCommitment() ||
output.value_blinding_factor.IsNull() ||
output.asset_blinding_factor.IsNull()) {
// Something blinded, but not everything? That's not expected.
error = "PSBT has a partially-blinded output. Blinded outputs must be fully blinded.";
return false;
}

if (!VerifyConfidentialPair(output.value_commitment, output.asset_commitment, txo.nValue.GetAmount(), txo.nAsset.GetAsset(), output.value_blinding_factor, output.asset_blinding_factor)) {
error = "PSBT's 'tx' field output values do not match blinded output values (or are invalid in some way)! Either there is a bug, or the blinder is attacking you.";
return false;
}
}
return true;
}

3 changes: 3 additions & 0 deletions src/psbt.h
Original file line number Diff line number Diff line change
Expand Up @@ -1001,4 +1001,7 @@ NODISCARD bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std

std::string EncodePSBT(const PartiallySignedTransaction& psbt);

/** Check that the blinder did not tamper with the values in a blinded PSBT. */
bool CheckPSBTBlinding(const PartiallySignedTransaction& psbtx, std::string& error);

#endif // BITCOIN_PSBT_H
3 changes: 3 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,9 @@ static RPCHelpMan decodepsbt()
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
if (!CheckPSBTBlinding(psbtx, error)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, error);
}

UniValue result(UniValue::VOBJ);

Expand Down
3 changes: 3 additions & 0 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4733,6 +4733,9 @@ static RPCHelpMan walletsignpsbt()
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
if (!CheckPSBTBlinding(psbtx, error)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, error);
}

// Get the sighash type
int nHashType = ParseSighashString(request.params[1]);
Expand Down

0 comments on commit bcfa6d9

Please sign in to comment.