mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-15 12:51:00 +02:00
Merge e6207229b8 into merged_master (Elements PR #911)
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.
This commit is contained in:
commit
0bc6dbbce1
6 changed files with 126 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) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
41
src/psbt.cpp
41
src/psbt.cpp
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue