mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Merge branch 'master' into elements-22-rc5
This commit is contained in:
commit
a531cf5f77
11 changed files with 670 additions and 108 deletions
595
doc/elements-tx-format.md
Normal file
595
doc/elements-tx-format.md
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue