mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-17 13:07:54 +02:00
Merge 6eed792d43 into merged_master (Elements PR ElementsProject/elements#1050)
Conflicts were mostly obvious, except for those in CreateTransactionInternal. (This function was moved from wallet.cpp to spend.cpp, and substantially rewritten, between 0.21 and 22.0.) For those I manually applied the changes from the diff, which wound up taking a slightly different form. Also had to update the new test because the `addresses` field of the RPC output was removed.
This commit is contained in:
commit
b64ca7f411
7 changed files with 175 additions and 16 deletions
|
|
@ -263,6 +263,7 @@ bool SubtractScalars(uint256& a, const uint256& b)
|
|||
|
||||
// Compute the scalar offset used for the final blinder computation
|
||||
// value * asset_blinder + value_blinder
|
||||
// FIXME this method should be in libsecp, as should `ComputeAndAddToScalarOffset`
|
||||
bool CalculateScalarOffset(uint256& out, CAmount value, const uint256& asset_blinder, const uint256& value_blinder)
|
||||
{
|
||||
// If the asset_blinder is 0, then the equation resolves to just the value_blinder
|
||||
|
|
@ -276,8 +277,24 @@ bool CalculateScalarOffset(uint256& out, CAmount value, const uint256& asset_bli
|
|||
// tweak_mul expects a 32 byte, big endian tweak.
|
||||
// We need to pack the 8 byte CAmount into a uint256 with the correct padding, so start it at 24 bytes from the front
|
||||
WriteBE64(val.begin() + 24, value);
|
||||
if (secp256k1_ec_privkey_tweak_mul(secp256k1_blind_context, out.begin(), val.begin()) != 1) return false;
|
||||
if (!value_blinder.IsNull() && secp256k1_ec_privkey_tweak_add(secp256k1_blind_context, out.begin(), value_blinder.begin()) != 1) return false;
|
||||
if (value > 0) {
|
||||
if (secp256k1_ec_privkey_tweak_mul(secp256k1_blind_context, out.begin(), val.begin()) != 1) return false;
|
||||
} else {
|
||||
out = value_blinder;
|
||||
return true;
|
||||
}
|
||||
if (!value_blinder.IsNull()) {
|
||||
uint256 value_negated = value_blinder;
|
||||
if (secp256k1_ec_seckey_negate(secp256k1_blind_context, value_negated.begin()) != 1) {
|
||||
return false;
|
||||
}
|
||||
// Special-case zero, which would otherwise cause `secp256k1_ec_privkey_tweak_add` to fail
|
||||
if (value_negated == out) {
|
||||
out = uint256{};
|
||||
return true;
|
||||
}
|
||||
if (secp256k1_ec_privkey_tweak_add(secp256k1_blind_context, out.begin(), value_blinder.begin()) != 1) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -294,8 +311,17 @@ bool ComputeAndAddToScalarOffset(uint256& a, CAmount value, const uint256& asset
|
|||
if (a.IsNull()) {
|
||||
a = scalar;
|
||||
} else {
|
||||
// If we have a, then add the scalar to it.
|
||||
if (secp256k1_ec_privkey_tweak_add(secp256k1_blind_context, a.begin(), scalar.begin()) != 1) return false;
|
||||
uint256 scalar_negated = scalar;
|
||||
if (secp256k1_ec_seckey_negate(secp256k1_blind_context, scalar_negated.begin()) != 1) {
|
||||
return false;
|
||||
}
|
||||
// Special-case zero, which would otherwise cause `secp256k1_ec_privkey_tweak_add` to fail
|
||||
if (scalar_negated == a) {
|
||||
a = uint256{};
|
||||
} else {
|
||||
// If we have a, then add the scalar to it.
|
||||
if (secp256k1_ec_privkey_tweak_add(secp256k1_blind_context, a.begin(), scalar.begin()) != 1) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ bilingual_str TransactionErrorString(const TransactionError err)
|
|||
return Untranslated("Proof of blinded value is invalid");
|
||||
case TransactionError::INVALID_ASSET_PROOF:
|
||||
return Untranslated("Proof of blinded asset is invalid");
|
||||
case TransactionError::MISSING_BLINDING_KEY:
|
||||
return Untranslated("Wallet does not have necessary blinding key");
|
||||
case TransactionError::MISSING_SIDECHANNEL_DATA:
|
||||
return Untranslated("A rangeproof did not encode necessary blinding data");
|
||||
// no default case, so the compiler can warn about missing cases
|
||||
}
|
||||
assert(false);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ enum class TransactionError {
|
|||
UTXOS_MISSING_BALANCE_CHECK,
|
||||
INVALID_VALUE_PROOF,
|
||||
INVALID_ASSET_PROOF,
|
||||
MISSING_BLINDING_KEY,
|
||||
MISSING_SIDECHANNEL_DATA,
|
||||
};
|
||||
|
||||
bilingual_str TransactionErrorString(const TransactionError error);
|
||||
|
|
|
|||
|
|
@ -5025,8 +5025,16 @@ static RPCHelpMan walletcreatefundedpsbt()
|
|||
// Automatically select coins, unless at least one is manually selected. Can
|
||||
// be overridden by options.add_inputs.
|
||||
coin_control.m_add_inputs = rawTx.vin.size() == 0;
|
||||
// FundTransaction expects blinding keys, if present, to appear in the output nonces
|
||||
for (CTxOut& txout : rawTx.vout) {
|
||||
auto search_it = psbt_outs.find(txout);
|
||||
assert (search_it != psbt_outs.end());
|
||||
CPubKey& blind_pub = search_it->second.m_blinding_pubkey;
|
||||
if (blind_pub.IsFullyValid()) {
|
||||
txout.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
|
||||
}
|
||||
}
|
||||
FundTransaction(wallet, rawTx, fee, change_position, request.params[3], coin_control, /* solving_data */ request.params[5], /* override_min_fee */ true);
|
||||
PartiallySignedTransaction psbtx(rawTx, psbt_version);
|
||||
// Find an input that is ours
|
||||
unsigned int blinder_index = 0;
|
||||
{
|
||||
|
|
@ -5039,6 +5047,17 @@ static RPCHelpMan walletcreatefundedpsbt()
|
|||
}
|
||||
}
|
||||
assert(blinder_index < rawTx.vin.size()); // We added inputs, or existing inputs are ours, we should have a blinder index at this point.
|
||||
// It may add outputs (change, and in some edge case OP_RETURN) which need to be
|
||||
// blinded. So pull these into `psbt_outs`.
|
||||
for (const CTxOut& txout : rawTx.vout) {
|
||||
if (!txout.nNonce.IsNull() && !psbt_outs.count(txout)) {
|
||||
PSBTOutput new_out{2}; // psbtv2 output
|
||||
new_out.m_blinding_pubkey.Set(txout.nNonce.vchCommitment.begin(), txout.nNonce.vchCommitment.end());
|
||||
new_out.m_blinder_index = blinder_index;
|
||||
psbt_outs.insert(std::make_pair(txout, new_out));
|
||||
}
|
||||
}
|
||||
PartiallySignedTransaction psbtx(rawTx, psbt_version);
|
||||
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
|
||||
PSBTOutput& output = psbtx.outputs[i];
|
||||
auto it = psbt_outs.find(rawTx.vout.at(i));
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <interfaces/chain.h>
|
||||
#include <issuance.h> // ELEMENTS: for GenerateAssetEntropy and others
|
||||
#include <policy/policy.h>
|
||||
#include <rpc/util.h> // for GetDestinationBlindingKey and IsBlindDestination
|
||||
#include <util/check.h>
|
||||
#include <util/fees.h>
|
||||
#include <util/moneystr.h>
|
||||
|
|
@ -765,6 +766,8 @@ bool fillBlindDetails(BlindDetails* det, CWallet* wallet, CMutableTransaction& t
|
|||
// We need to make sure to dupe an asset that is in input set
|
||||
//TODO Have blinding do some extremely minimal rangeproof
|
||||
CTxOut newTxOut(det->o_assets.back(), 0, CScript() << OP_RETURN);
|
||||
CPubKey blind_pub = wallet->GetBlindingPubKey(newTxOut.scriptPubKey); // irrelevent, just needs to be non-null
|
||||
newTxOut.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
|
||||
txNew.vout.push_back(newTxOut);
|
||||
det->o_pubkeys.push_back(wallet->GetBlindingPubKey(newTxOut.scriptPubKey));
|
||||
det->o_amount_blinds.push_back(uint256());
|
||||
|
|
@ -878,12 +881,20 @@ bool CWallet::CreateTransactionInternal(
|
|||
// ELEMENTS: A map that keeps track of the change script for each asset and also
|
||||
// the index of the reservedest used for that script (-1 if none).
|
||||
std::map<CAsset, std::pair<int, CScript>> mapScriptChange;
|
||||
// For manually set change, we need to use the blinding pubkey associated
|
||||
// with the manually-set address rather than generating one from the wallet
|
||||
std::map<CAsset, std::optional<CPubKey> > mapBlindingKeyChange;
|
||||
|
||||
// coin control: send change to custom address
|
||||
if (coin_control.destChange.size() > 0) {
|
||||
for (const auto& dest : coin_control.destChange) {
|
||||
// No need to test we cover all assets. We produce error for that later.
|
||||
mapScriptChange[dest.first] = std::pair<int, CScript>(-1, GetScriptForDestination(dest.second));
|
||||
if (IsBlindDestination(dest.second)) {
|
||||
mapBlindingKeyChange[dest.first] = GetDestinationBlindingKey(dest.second);
|
||||
} else {
|
||||
mapBlindingKeyChange[dest.first] = std::nullopt;
|
||||
}
|
||||
}
|
||||
} else { // no coin control: send change to newly generated address
|
||||
// Note: We use a new key here to keep it from being obvious which side is the change.
|
||||
|
|
@ -1138,19 +1149,34 @@ bool CWallet::CreateTransactionInternal(
|
|||
CTxOut newTxOut(asset, change_and_fee, itScript->second.second);
|
||||
|
||||
if (blind_details) {
|
||||
std::optional<CPubKey> blind_pub = std::nullopt;
|
||||
// We cannot blind zero-valued outputs, and anyway they will be dropped
|
||||
// later in this function during the dust check
|
||||
if (change_and_fee > 0) {
|
||||
CPubKey blind_pub = GetBlindingPubKey(itScript->second.second);
|
||||
blind_details->o_pubkeys.insert(blind_details->o_pubkeys.begin() + i, blind_pub);
|
||||
assert(blind_pub.IsFullyValid());
|
||||
const auto itBlindingKey = mapBlindingKeyChange.find(asset);
|
||||
if (itBlindingKey != mapBlindingKeyChange.end()) {
|
||||
// If the change output was specified, use the blinding key that
|
||||
// came with the specified address (if any)
|
||||
blind_pub = itBlindingKey->second;
|
||||
} else {
|
||||
// Otherwise, we generated it from our own wallet, so get the
|
||||
// blinding key from our own wallet.
|
||||
blind_pub = GetBlindingPubKey(itScript->second.second);
|
||||
}
|
||||
} else {
|
||||
assert(asset == policyAsset);
|
||||
}
|
||||
|
||||
if (blind_pub) {
|
||||
blind_details->o_pubkeys.insert(blind_details->o_pubkeys.begin() + i, *blind_pub);
|
||||
assert(blind_pub->IsFullyValid());
|
||||
|
||||
blind_details->num_to_blind++;
|
||||
blind_details->change_to_blind++;
|
||||
blind_details->only_change_pos = i;
|
||||
// Place the blinding pubkey here in case of fundraw calls
|
||||
newTxOut.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub.begin(), blind_pub.end());
|
||||
newTxOut.nNonce.vchCommitment = std::vector<unsigned char>(blind_pub->begin(), blind_pub->end());
|
||||
} else {
|
||||
// We cannot blind zero-valued outputs, and anyway they will be dropped
|
||||
// later in this function during the dust check
|
||||
assert(asset == policyAsset);
|
||||
blind_details->o_pubkeys.insert(blind_details->o_pubkeys.begin() + i, CPubKey());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1963,7 +1963,7 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp
|
|||
|
||||
CConfidentialNonce nonce;
|
||||
nonce.vchCommitment.insert(nonce.vchCommitment.end(), o.m_ecdh_pubkey.begin(), o.m_ecdh_pubkey.end());
|
||||
if (!UnblindConfidentialPair(blinding_key, o.m_value_commitment, o.m_asset_commitment, nonce, *o.script, o.m_value_rangeproof, value, value_factor, asset, asset_factor)) {
|
||||
if (UnblindConfidentialPair(blinding_key, o.m_value_commitment, o.m_asset_commitment, nonce, *o.script, o.m_value_rangeproof, value, value_factor, asset, asset_factor)) {
|
||||
// These assertions are cryptographically impossible to trigger, as we
|
||||
// checked the proofs above, and then `UnblindConfidentialPair` checks
|
||||
// the extracted value/asset against the commitments.
|
||||
|
|
@ -1973,10 +1973,11 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp
|
|||
if (!o.m_asset.IsNull()) {
|
||||
assert(CAsset(o.m_asset) == asset);
|
||||
}
|
||||
return TransactionError::INVALID_ASSET_PROOF; // FIXME
|
||||
} else {
|
||||
return TransactionError::MISSING_SIDECHANNEL_DATA;
|
||||
}
|
||||
} else {
|
||||
return TransactionError::INVALID_ASSET_PROOF; // FIXME
|
||||
return TransactionError::MISSING_BLINDING_KEY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue