Skip to content

Commit

Permalink
Merge #917: [0.18 backport] Backport #911: When decoding or signing a…
Browse files Browse the repository at this point in the history
… PSBT, check blinded values.

6d557b9 When decoding or signing a PSBT, check blinded values. (Glenn Willen)

Pull request description:

Tree-SHA512: 0604ae37e42d0f83487d3370c64ac3968cbb813580138a32280bb147bbe8378a4306a4f6fd194cc58024b5288a551627d17c162cc110a60032fa9c69c9d51c4b
  • Loading branch information
stevenroose committed Oct 6, 2020
2 parents a882ef6 + 6d557b9 commit a0bd887
Show file tree
Hide file tree
Showing 5 changed files with 116 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 @@ -19,6 +19,14 @@ static const size_t DEFAULT_RANGEPROOF_SIZE = 4174;
// constant-size surjection proof
static const size_t SURJECTION_PROOF_SIZE = 67;

/*
* 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
36 changes: 36 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,41 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue&
}
}

void RPCCheckPSBTBlinding(const PartiallySignedTransaction& psbtx) {
// 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()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBT's 'tx' field may not have pre-blinded outputs.");
}

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.
throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBT has a partially-blinded output. Blinded outputs must be fully blinded.");
}

if (!VerifyConfidentialPair(output.value_commitment, output.asset_commitment, txo.nValue.GetAmount(), txo.nAsset.GetAsset(), output.value_blinding_factor, output.asset_blinding_factor)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "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.");
}
}
}

static UniValue getrawtransaction(const JSONRPCRequest& request)
{
const RPCHelpMan help{
Expand Down Expand Up @@ -1571,6 +1606,7 @@ UniValue decodepsbt(const JSONRPCRequest& request)
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
RPCCheckPSBTBlinding(psbtx);

UniValue result(UniValue::VOBJ);

Expand Down
3 changes: 3 additions & 0 deletions src/rpc/rawtransaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ namespace interfaces {
class Chain;
} // namespace interfaces

/** Check that the blinder did not tamper with the values in a blinded PSBT. */
void RPCCheckPSBTBlinding(const PartiallySignedTransaction& psbtx);

/** Sign a transaction with the given keystore and previous transactions */
UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, const UniValue& prevTxs, CBasicKeyStore *keystore, bool tempKeystore, const UniValue& hashType);

Expand Down
1 change: 1 addition & 0 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4464,6 +4464,7 @@ UniValue walletsignpsbt(const JSONRPCRequest& request)
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
RPCCheckPSBTBlinding(psbtx);

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

0 comments on commit a0bd887

Please sign in to comment.