diff --git a/src/blindpsbt.cpp b/src/blindpsbt.cpp index 295d57691b..952be23429 100644 --- a/src/blindpsbt.cpp +++ b/src/blindpsbt.cpp @@ -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; } diff --git a/src/util/error.cpp b/src/util/error.cpp index 18131f2cdf..5048ac5dff 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -41,6 +41,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); diff --git a/src/util/error.h b/src/util/error.h index 831d1a8df5..d2268872f4 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -35,6 +35,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); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index c4f30b9973..b234c37d02 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4957,8 +4957,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(blind_pub.begin(), blind_pub.end()); + } + } FundTransaction(pwallet, 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; { @@ -4971,6 +4979,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)); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 08b545454a..5c80fdca90 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -19,6 +19,7 @@ #include #include #include +#include // for GetDestinationBlindingKey and IsBlindDestination #include