mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
When decoding or signing a PSBT, check blinded values.
When we decode or sign a PSBT given to us via RPC, first check that, if it contains blinded output values, they verifiably match the unblinded output values contained in the original transaction proposal. Otherwise fail.
This commit is contained in:
parent
4ae7d7abf1
commit
6d557b9a3c
5 changed files with 116 additions and 0 deletions
|
|
@ -36,6 +36,74 @@ public:
|
|||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
@ -1572,6 +1607,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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue