Merge ElementsProject/elements#1156: [backport] Backport fixes from master to elements-22.x for rc5

37076d7b9d Bump version to -rc5 (Pablo Greco)
5629cae32b fs: Make compatible with boost 1.78 (Andrew Chow)
60b913e341 Elements-qt: Correctly display the amount in the sender's wallet after using Send button (Andrea Bonel)
872478b1be docs: describe elements transaction serialization format (#1148) (James Dorfman)
dd2d758bc6 fixed minor issues found in review (Allen Piscitello)
2da7d75ee9 removing test that fails due to blinded issuances, which results in incorrect reissuance token ids (Allen Piscitello)
420de43c1c removing code to blind issuances. PSET should be modified to include an option to blind or unblind issuances, defaulting to unblind. (Allen Piscitello)

Pull request description:

  Backport https://github.com/ElementsProject/elements/pull/1150 https://github.com/ElementsProject/elements/pull/1154 and https://github.com/ElementsProject/elements/pull/1148 from master.
  Backport https://github.com/bitcoin/bitcoin/pull/24104 from Bitcoin Core
  Bump to -rc5

ACKs for top commit:
  jamesdorfman:
    utACK 37076d7b9d.
  delta1:
    utACK 37076d7b9d

Tree-SHA512: 616322f94c17008cc7fd582203c22b33c73b922269ab33fd13b270f491eda9b30d4f18efa3f7887e247bef46b63c4810f4084e409bea98120702c426a83343a8
This commit is contained in:
Andrew Poelstra 2022-09-06 23:02:58 +00:00
commit 433a57a5bd
No known key found for this signature in database
GPG key ID: C588D63CE41B97C1
14 changed files with 673 additions and 111 deletions

View file

@ -2,7 +2,7 @@ AC_PREREQ([2.69])
define(_CLIENT_VERSION_MAJOR, 22)
define(_CLIENT_VERSION_MINOR, 0)
define(_CLIENT_VERSION_BUILD, 0)
define(_CLIENT_VERSION_RC, 4)
define(_CLIENT_VERSION_RC, 5)
define(_CLIENT_VERSION_IS_RELEASE, true)
define(_COPYRIGHT_YEAR, 2022)
define(_COPYRIGHT_HOLDERS,[The %s developers])

595
doc/elements-tx-format.md Normal file

File diff suppressed because one or more lines are too long

View file

@ -386,99 +386,54 @@ BlindingStatus BlindPSBT(PartiallySignedTransaction& psbt, std::map<uint32_t, st
}
// Handle issuances
if (input.m_issuance_value) {
if (!input.m_issuance_value_commitment.IsCommitment() && input.m_issuance_rangeproof.size() == 0 && input.m_issuance_inflation_keys_rangeproof.size() == 0) {
CAsset issuance_asset;
CAsset reissuance_asset;
if (input.m_issuance_value != std::nullopt || input.m_issuance_value_commitment.IsCommitment() || input.m_issuance_inflation_keys_amount != std::nullopt || input.m_issuance_inflation_keys_commitment.IsCommitment()) {
CAsset issuance_asset;
CAsset reissuance_asset;
uint256 entropy;
if (!input.m_issuance_blinding_nonce.IsNull()) {
// Reissuance, use assetEntropy as the asset entropy
entropy = input.m_issuance_asset_entropy;
} else {
// New issuance, make new entropy
GenerateAssetEntropy(entropy, input.GetOutPoint(), input.m_issuance_asset_entropy);
}
uint256 entropy;
if (!input.m_issuance_blinding_nonce.IsNull()) {
// Reissuance, use assetEntropy as the asset entropy
entropy = input.m_issuance_asset_entropy;
} else {
// New issuance, make new entropy
GenerateAssetEntropy(entropy, input.GetOutPoint(), input.m_issuance_asset_entropy);
}
if (input.m_issuance_value != std::nullopt || input.m_issuance_value_commitment.IsCommitment()) {
// Asset isn't blinded yet. Add it to the list of input assets
CalculateAsset(issuance_asset, entropy);
fixed_input_tags.emplace_back();
memcpy(fixed_input_tags.back().data, issuance_asset.begin(), 32);
ephemeral_input_tags.emplace_back();
if (secp256k1_generator_generate(secp256k1_blind_context, &ephemeral_input_tags.back(), issuance_asset.begin()) != 1) {
return BlindingStatus::INVALID_ASSET;
}
unsigned int iss_to_blind = 1; // Always do the first issuance blinding iteration for the issuance value
bool blind_issuance = our_issuances_to_blind.count(i) > 0;
if (input.m_issuance_blinding_nonce.IsNull() && input.m_issuance_inflation_keys_amount) {
// New issuance, do reissuance token things
CalculateReissuanceToken(reissuance_asset, entropy, blind_issuance);
// Add the reissuance_asset to the list of input assets
fixed_input_tags.emplace_back();
memcpy(fixed_input_tags.back().data, reissuance_asset.begin(), 32);
ephemeral_input_tags.emplace_back();
if (secp256k1_generator_generate(secp256k1_blind_context, &ephemeral_input_tags.back(), reissuance_asset.begin()) != 1) {
if (input.m_issuance_value_commitment.IsNull()) {
if (secp256k1_generator_generate(secp256k1_blind_context, &ephemeral_input_tags.back(), issuance_asset.begin()) != 1) {
return BlindingStatus::INVALID_ASSET;
}
iss_to_blind++; // If we have a reissuance, do the second blinding iteration for the inflation keys
}
if (blind_issuance) {
for (unsigned int blind_i = 0; blind_i < iss_to_blind; ++blind_i) {
// To blind an issuance, both the issuance value and the number of inflation keys need to be blinded
// Since this process is basically the same for both, do it in a loop and switch based on the index
bool blind_value = blind_i == 0; // True for blinding the value, false for blinding the inflation keys
CAmount value = blind_value ? *input.m_issuance_value : *input.m_issuance_inflation_keys_amount;
CAsset asset = blind_value ? issuance_asset : reissuance_asset;
CKey blinding_privkey = blind_value ? our_issuances_to_blind.at(i).first : our_issuances_to_blind.at(i).second;
uint256 value_blinder;
GetStrongRandBytes(value_blinder.begin(), value_blinder.size());
// Create unblinded generator. Throw away everything except asset_gen
uint256 asset_blinder;
CConfidentialAsset conf_asset;
secp256k1_generator asset_gen;
CreateAssetCommitment(conf_asset, asset_gen, asset, asset_blinder);
input_asset_blinders.push_back(asset_blinder);
// Compute the scalar for this blinding and add to the input scalar
if (!ComputeAndAddToScalarOffset(input_scalar, value, asset_blinder, value_blinder)) return BlindingStatus::SCALAR_UNABLE;
// Create value commitment
secp256k1_pedersen_commitment value_commit;
CConfidentialValue conf_value;
CreateValueCommitment(conf_value, value_commit, value_blinder, asset_gen, value);
// Nonce is the blinding key
uint256 nonce = uint256(std::vector<unsigned char>(blinding_privkey.begin(), blinding_privkey.end()));
// Generate rangeproof
std::vector<unsigned char> rangeproof;
bool rangeresult = CreateValueRangeProof(rangeproof, value_blinder, nonce, value, CScript(), value_commit, asset_gen, asset, asset_blinder);
assert(rangeresult);
// Create explicit value rangeproofs
std::vector<unsigned char> blind_value_proof;
rangeresult = CreateBlindValueProof(blind_value_proof, value_blinder, value, value_commit, asset_gen);
assert(rangeresult);
if (blind_value) {
input.m_issuance_value_commitment = conf_value;
input.m_issuance_rangeproof = rangeproof;
input.m_blind_issuance_value_proof = blind_value_proof;
} else {
input.m_issuance_inflation_keys_commitment = conf_value;
input.m_issuance_inflation_keys_rangeproof = rangeproof;
input.m_blind_issuance_inflation_keys_proof = blind_value_proof;
}
}
}
else {
input_asset_blinders.emplace_back();
memcpy(ephemeral_input_tags.back().data, input.m_issuance_value_commitment.vchCommitment.data(), 33);
}
input_asset_blinders.emplace_back();
}
bool blind_issuance = input.m_issuance_value_commitment.IsCommitment();
if (input.m_issuance_blinding_nonce.IsNull() && (input.m_issuance_inflation_keys_amount != std::nullopt || input.m_issuance_inflation_keys_commitment.IsCommitment())) {
// New issuance, do reissuance token things
CalculateReissuanceToken(reissuance_asset, entropy, blind_issuance);
// Add the reissuance_asset to the list of input assets
fixed_input_tags.emplace_back();
memcpy(fixed_input_tags.back().data, reissuance_asset.begin(), 32);
ephemeral_input_tags.emplace_back();
if (input.m_issuance_inflation_keys_commitment.IsNull()) {
if (secp256k1_generator_generate(secp256k1_blind_context, &ephemeral_input_tags.back(), reissuance_asset.begin()) != 1) {
return BlindingStatus::INVALID_ASSET;
}
}
else if(input.m_issuance_inflation_keys_commitment.IsCommitment()){
memcpy(ephemeral_input_tags.back().data, input.m_issuance_inflation_keys_commitment.vchCommitment.data(), 33);
}
input_asset_blinders.emplace_back();
}
}
}

View file

@ -36,6 +36,7 @@ struct PartiallySignedTransaction;
struct WalletContext;
struct bilingual_str;
typedef uint8_t isminefilter;
struct BlindDetails;
namespace interfaces {
@ -140,13 +141,14 @@ public:
bool sign,
int& change_pos,
CAmount& fee,
std::vector<CAmount>& out_amounts,
BlindDetails* blind_details,
bilingual_str& fail_reason) = 0;
//! Commit transaction.
virtual void commitTransaction(CTransactionRef tx,
WalletValueMap value_map,
WalletOrderForm order_form) = 0;
WalletOrderForm order_form,
BlindDetails* blind_details) = 0;
//! Return whether transaction can be abandoned.
virtual bool transactionCanBeAbandoned(const uint256& txid) = 0;

View file

@ -135,7 +135,7 @@ CMutableTransaction PartiallySignedTransaction::GetUnsignedTx(bool force_unblind
txin.assetIssuance.nAmount.SetNull();
}
if (input.m_issuance_inflation_keys_amount != std::nullopt && (input.m_issuance_inflation_keys_commitment.IsNull() || force_unblinded)) {
txin.assetIssuance.nInflationKeys.SetToAmount(*input.m_issuance_value);
txin.assetIssuance.nInflationKeys.SetToAmount(*input.m_issuance_inflation_keys_amount);
}
else if(!input.m_issuance_inflation_keys_commitment.IsNull()) {
txin.assetIssuance.nInflationKeys = input.m_issuance_inflation_keys_commitment;

View file

@ -281,11 +281,13 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
// prepare transaction for getting txFee earlier
m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
if (g_con_elementsmode)
m_current_blind_details = std::make_unique<BlindDetails>();
WalletModel::SendCoinsReturn prepareStatus;
updateCoinControlState();
prepareStatus = model->prepareTransaction(*m_current_transaction, *m_coin_control);
prepareStatus = model->prepareTransaction(*m_current_transaction, m_current_blind_details.get(), *m_coin_control);
// process prepareStatus and on error generate message shown to user
processSendCoinsReturn(prepareStatus,
@ -403,6 +405,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
QString question_string, informative_text, detailed_text;
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
assert(m_current_transaction);
assert(!g_con_elementsmode || m_current_blind_details);
const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Sign and send");
@ -461,7 +464,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
if (complete) {
const CTransactionRef tx = MakeTransactionRef(mtx);
m_current_transaction->setWtx(tx);
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction, m_current_blind_details.get());
// process sendStatus and on error generate message shown to user
processSendCoinsReturn(sendStatus);
@ -520,7 +523,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
} // msgBox.exec()
} else {
// now send the prepared transaction
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction, m_current_blind_details.get());
// process sendStatus and on error generate message shown to user
processSendCoinsReturn(sendStatus);
@ -537,11 +540,13 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
}
fNewRecipientAllowed = true;
m_current_transaction.reset();
m_current_blind_details.reset();
}
void SendCoinsDialog::clear()
{
m_current_transaction.reset();
m_current_blind_details.reset();
// Clear coin control settings
m_coin_control->UnSelectAll();

View file

@ -64,6 +64,7 @@ private:
WalletModel *model;
std::unique_ptr<CCoinControl> m_coin_control;
std::unique_ptr<WalletModelTransaction> m_current_transaction;
std::unique_ptr<BlindDetails> m_current_blind_details;
bool fNewRecipientAllowed;
bool fFeeMinimized;
const PlatformStyle *platformStyle;

View file

@ -176,7 +176,7 @@ bool WalletModel::validateAddress(const QString &address)
return IsValidDestinationString(address.toStdString());
}
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl)
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, BlindDetails *blind_details, const CCoinControl& coinControl)
{
CAmountMap total;
bool fSubtractFeeFromAmount = false;
@ -236,10 +236,13 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
auto& newTx = transaction.getWtx();
std::vector<CAmount> out_amounts;
newTx = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, out_amounts, error);
newTx = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, blind_details, error);
transaction.setTransactionFee(nFeeRequired);
if (fSubtractFeeFromAmount && newTx) {
assert(out_amounts.size() == newTx->vout.size());
if(blind_details) {
out_amounts = blind_details->o_amounts;
assert(out_amounts.size() == newTx->vout.size());
}
transaction.reassignAmounts(out_amounts, nChangePosRet);
}
@ -266,7 +269,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
return SendCoinsReturn(OK);
}
WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &transaction)
WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &transaction, BlindDetails *blind_details)
{
QByteArray transaction_array; /* store serialized transaction */
@ -279,7 +282,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran
}
auto& newTx = transaction.getWtx();
wallet().commitTransaction(newTx, {} /* mapValue */, std::move(vOrderForm));
wallet().commitTransaction(newTx, {} /* mapValue */, std::move(vOrderForm), blind_details);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << *newTx;

View file

@ -100,10 +100,10 @@ public:
};
// prepare transaction for getting txfee before sending coins
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl);
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, BlindDetails *blind_details, const CCoinControl& coinControl);
// Send coins to a list of recipients
SendCoinsReturn sendCoins(WalletModelTransaction &transaction);
SendCoinsReturn sendCoins(WalletModelTransaction &transaction, BlindDetails *blind_details);
// Wallet encryption
bool setWalletEncrypted(const SecureString& passphrase);

View file

@ -248,26 +248,25 @@ public:
bool sign,
int& change_pos,
CAmount& fee,
std::vector<CAmount>& out_amounts,
BlindDetails* blind_details,
bilingual_str& fail_reason) override
{
LOCK(m_wallet->cs_wallet);
CTransactionRef tx;
FeeCalculation fee_calc_out;
BlindDetails blind_details;
if (!m_wallet->CreateTransaction(recipients, tx, fee, change_pos,
fail_reason, coin_control, fee_calc_out, sign, gArgs.GetBoolArg("-blindedaddresses", g_con_elementsmode) ? &blind_details : nullptr)) {
fail_reason, coin_control, fee_calc_out, sign, blind_details)) {
return {};
}
out_amounts = blind_details.o_amounts;
return tx;
}
void commitTransaction(CTransactionRef tx,
WalletValueMap value_map,
WalletOrderForm order_form) override
WalletOrderForm order_form,
BlindDetails* blind_details) override
{
LOCK(m_wallet->cs_wallet);
m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form));
m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form), blind_details);
}
bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); }
bool abandonTransaction(const uint256& txid) override

View file

@ -22,7 +22,7 @@ bool VerifyWallets(interfaces::Chain& chain)
fs::path wallet_dir = gArgs.GetArg("-walletdir", "");
boost::system::error_code error;
// The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory
fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error);
fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error).remove_trailing_separator();
if (error || !fs::exists(wallet_dir)) {
chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string()));
return false;

View file

@ -1921,8 +1921,8 @@ BlindingStatus CWallet::WalletBlindPSBT(PartiallySignedTransaction& psbtx) const
our_input_data[i] = std::make_tuple(amount, asset, asset_blinder, value_blinder);
}
// Blind issuances on our inputs
if (input.m_issuance_value || input.m_issuance_inflation_keys_amount) {
// Blind issuances on our inputs if at least one commitment was provided.
if (input.m_issuance_value_commitment.IsCommitment() || input.m_issuance_inflation_keys_commitment.IsCommitment()) {
CScript blinding_script(CScript() << OP_RETURN << std::vector<unsigned char>(input.prev_txid.begin(), input.prev_txid.end()) << *input.prev_out);
our_issuances_to_blind[i] = std::make_pair(GetBlindingKey(&blinding_script), GetBlindingKey(&blinding_script));
}

View file

@ -98,6 +98,7 @@ class SighashRangeproofTest(BitcoinTestFramework):
"blind": True, # FIXME: if blind=False, `blindrawtranaction` fails. Should fix this in a future PR
}]
)[0]["hex"]
blinded_hex = self.nodes[1].blindrawtransaction(unsigned_hex)
blinded_tx = tx_from_hex(blinded_hex)
signed_hex = self.nodes[1].signrawtransactionwithwallet(blinded_hex)["hex"]
@ -164,12 +165,13 @@ class SighashRangeproofTest(BitcoinTestFramework):
assert test_accept["allowed"], "not accepted: {}".format(test_accept["reject-reason"])
# Try signing using the PSBT interface
psbt_hex = self.nodes[0].converttopsbt(unsigned_hex)
signed_psbt = self.nodes[1].walletprocesspsbt(psbt_hex, True, "ALL|RANGEPROOF")
extracted_tx = self.nodes[0].finalizepsbt(signed_psbt["psbt"])
assert extracted_tx["complete"]
test_accept = self.nodes[0].testmempoolaccept([extracted_tx["hex"]])[0]
assert test_accept["allowed"], "not accepted: {}".format(test_accept["reject-reason"])
if not attach_issuance: # FIXME: We need to skip the issuance since the example assumes it was a blinded issuance, thus the reissuance token is incorrect.
psbt_hex = self.nodes[0].converttopsbt(unsigned_hex)
signed_psbt = self.nodes[1].walletprocesspsbt(psbt_hex, True, "ALL|RANGEPROOF")
extracted_tx = self.nodes[0].finalizepsbt(signed_psbt["psbt"])
assert extracted_tx["complete"]
test_accept = self.nodes[0].testmempoolaccept([extracted_tx["hex"]])[0]
assert test_accept["allowed"], "not accepted: {}".format(test_accept["reject-reason"])
else:
signed_tx.rehash()

View file

@ -141,7 +141,7 @@ class MultiWalletTest(BitcoinTestFramework):
# should raise rpc error if wallet path can't be created
err_code = -4 if self.options.descriptors else -1
assert_raises_rpc_error(err_code, "boost::filesystem::create_directory:", self.nodes[0].createwallet, "w8/bad")
assert_raises_rpc_error(err_code, "boost::filesystem::create_director", self.nodes[0].createwallet, "w8/bad")
# check that all requested wallets were created
self.stop_node(0)