Skip to content

Commit a035b42

Browse files
committed
wallet: Deniability API
This PR is the wallet API and implementation portion of the GUI PR ( bitcoin-core/gui#733 ) which is an implementation of the ideas in Paul Sztorc's blog post "Deniability - Unilateral Transaction Meta-Privacy"(https://www.truthcoin.info/blog/deniability/). The GUI PR has all the details and screenshots of the GUI additions. Here I'll just copy the relevant context for the wallet API changes: " In short, Paul's idea is to periodically split coins and send them to yourself, making it look like common "spend" transactions, such that blockchain ownership analysis becomes more difficult, and thus improving the user's privacy. I've implemented this as an additional "Deniability" wallet view. The majority of the code is in a new deniabilitydialog.cpp/h source files containing a new DeniabilityDialog class, hooked up to the WalletView class.  " While the Deniability dialog can be implemented entirely with the existing API, adding the core "deniabilization" functions to the CWallet and interfaces::Wallet API allows us to implement the GUI portion with much less code, and more importantly allows us to add RPC support and more thorough unit tests.
1 parent a13f374 commit a035b42

File tree

6 files changed

+396
-0
lines changed

6 files changed

+396
-0
lines changed

src/interfaces/wallet.h

+17
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,13 @@ class Wallet
151151
WalletValueMap value_map,
152152
WalletOrderForm order_form) = 0;
153153

154+
virtual util::Result<CTransactionRef> createDeniabilizationTransaction(const std::set<COutPoint>& inputs,
155+
uint confirm_target,
156+
uint deniabilization_cycles,
157+
bool sign,
158+
bool& insufficient_amount,
159+
CAmount& fee) = 0;
160+
154161
//! Return whether transaction can be abandoned.
155162
virtual bool transactionCanBeAbandoned(const uint256& txid) = 0;
156163

@@ -177,6 +184,13 @@ class Wallet
177184
std::vector<bilingual_str>& errors,
178185
uint256& bumped_txid) = 0;
179186

187+
//! Create a fee bump transaction for a deniabilization transaction
188+
virtual util::Result<CTransactionRef> createBumpDeniabilizationTransaction(const uint256& txid,
189+
uint confirm_target,
190+
bool sign,
191+
CAmount& old_fee,
192+
CAmount& new_fee) = 0;
193+
180194
//! Get a transaction.
181195
virtual CTransactionRef getTx(const uint256& txid) = 0;
182196

@@ -248,6 +262,9 @@ class Wallet
248262
int* returned_target,
249263
FeeReason* reason) = 0;
250264

265+
//! Get the fee rate for deniabilization
266+
virtual CFeeRate getDeniabilizationFeeRate(uint confirm_target) = 0;
267+
251268
//! Get tx confirm target.
252269
virtual unsigned int getConfirmTarget() = 0;
253270

src/wallet/feebumper.cpp

+83
Original file line numberDiff line numberDiff line change
@@ -365,5 +365,88 @@ Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransacti
365365
return Result::OK;
366366
}
367367

368+
Result CreateRateBumpDeniabilizationTransaction(CWallet& wallet, const uint256& txid, uint confirm_target, bool sign, bilingual_str& error, CAmount& old_fee, CAmount& new_fee, CTransactionRef& new_tx)
369+
{
370+
CCoinControl coin_control = SetupDeniabilizationCoinControl(confirm_target);
371+
coin_control.m_feerate = CalculateDeniabilizationFeeRate(wallet, confirm_target);
372+
373+
LOCK(wallet.cs_wallet);
374+
375+
auto it = wallet.mapWallet.find(txid);
376+
if (it == wallet.mapWallet.end()) {
377+
error = Untranslated("Invalid or non-wallet transaction id");
378+
return Result::INVALID_ADDRESS_OR_KEY;
379+
}
380+
const CWalletTx& wtx = it->second;
381+
382+
// Retrieve all of the UTXOs and add them to coin control
383+
// While we're here, calculate the input amount
384+
std::map<COutPoint, Coin> coins;
385+
CAmount input_value = 0;
386+
for (const CTxIn& txin : wtx.tx->vin) {
387+
coins[txin.prevout]; // Create empty map entry keyed by prevout.
388+
}
389+
wallet.chain().findCoins(coins);
390+
for (const CTxIn& txin : wtx.tx->vin) {
391+
const Coin& coin = coins.at(txin.prevout);
392+
if (coin.out.IsNull()) {
393+
error = Untranslated(strprintf("%s:%u is already spent", txin.prevout.hash.GetHex(), txin.prevout.n));
394+
return Result::MISC_ERROR;
395+
}
396+
if (!wallet.IsMine(txin.prevout)) {
397+
error = Untranslated("All inputs must be from our wallet.");
398+
return Result::MISC_ERROR;
399+
}
400+
coin_control.Select(txin.prevout);
401+
input_value += coin.out.nValue;
402+
}
403+
404+
std::vector<bilingual_str> dymmy_errors;
405+
Result result = PreconditionChecks(wallet, wtx, /*require_mine=*/true, dymmy_errors);
406+
if (result != Result::OK) {
407+
error = dymmy_errors.front();
408+
return result;
409+
}
410+
411+
// Calculate the old output amount.
412+
CAmount output_value = 0;
413+
for (const auto& old_output : wtx.tx->vout) {
414+
output_value += old_output.nValue;
415+
}
416+
417+
old_fee = input_value - output_value;
418+
419+
std::vector<CRecipient> recipients;
420+
for (const auto& output : wtx.tx->vout) {
421+
CRecipient recipient = {output.scriptPubKey, output.nValue, false};
422+
recipients.push_back(recipient);
423+
}
424+
// the last recipient gets the old fee
425+
recipients.back().nAmount += old_fee;
426+
// and pays the new fee
427+
recipients.back().fSubtractFeeFromAmount = true;
428+
// we don't expect to get change, but we provide the address to prevent CreateTransactionInternal from generating a change address
429+
ExtractDestination(recipients.back().scriptPubKey, coin_control.destChange);
430+
431+
for (const auto& inputs : wtx.tx->vin) {
432+
coin_control.Select(COutPoint(inputs.prevout));
433+
}
434+
435+
constexpr int RANDOM_CHANGE_POSITION = -1;
436+
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, coin_control, sign);
437+
if (!res) {
438+
error = util::ErrorString(res);
439+
return Result::WALLET_ERROR;
440+
}
441+
442+
// make sure we didn't get a change position assigned (we don't expect to use the channge address)
443+
Assert(res->change_pos == RANDOM_CHANGE_POSITION);
444+
// write back the new fee
445+
new_fee = res->fee;
446+
// write back the transaction
447+
new_tx = res->tx;
448+
return Result::OK;
449+
}
450+
368451
} // namespace feebumper
369452
} // namespace wallet

src/wallet/feebumper.h

+9
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ Result CommitTransaction(CWallet& wallet,
6969
std::vector<bilingual_str>& errors,
7070
uint256& bumped_txid);
7171

72+
Result CreateRateBumpDeniabilizationTransaction(CWallet& wallet,
73+
const uint256& txid,
74+
uint confirm_target,
75+
bool sign,
76+
bilingual_str& error,
77+
CAmount& old_fee,
78+
CAmount& new_fee,
79+
CTransactionRef& new_tx);
80+
7281
struct SignatureWeights
7382
{
7483
private:

src/wallet/interfaces.cpp

+34
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,22 @@ class WalletImpl : public Wallet
295295
LOCK(m_wallet->cs_wallet);
296296
m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form));
297297
}
298+
util::Result<CTransactionRef> createDeniabilizationTransaction(const std::set<COutPoint>& inputs,
299+
uint confirm_target,
300+
uint deniabilization_cycles,
301+
bool sign,
302+
bool& insufficient_amount,
303+
CAmount& fee) override
304+
{
305+
LOCK(m_wallet->cs_wallet); // TODO - Do we need a lock here?
306+
auto res = CreateDeniabilizationTransaction(*m_wallet, inputs, confirm_target, deniabilization_cycles, sign, insufficient_amount);
307+
if (!res) {
308+
return util::Error{util::ErrorString(res)};
309+
}
310+
const auto& txr = *res;
311+
fee = txr.fee;
312+
return txr.tx;
313+
}
298314
bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); }
299315
bool abandonTransaction(const uint256& txid) override
300316
{
@@ -324,6 +340,20 @@ class WalletImpl : public Wallet
324340
return feebumper::CommitTransaction(*m_wallet.get(), txid, std::move(mtx), errors, bumped_txid) ==
325341
feebumper::Result::OK;
326342
}
343+
util::Result<CTransactionRef> createBumpDeniabilizationTransaction(const uint256& txid,
344+
uint confirm_target,
345+
bool sign,
346+
CAmount& old_fee,
347+
CAmount& new_fee) override
348+
{
349+
bilingual_str error;
350+
CTransactionRef new_tx;
351+
auto res = feebumper::CreateRateBumpDeniabilizationTransaction(*m_wallet.get(), txid, confirm_target, sign, error, old_fee, new_fee, new_tx);
352+
if (res != feebumper::Result::OK) {
353+
return util::Error{error};
354+
}
355+
return new_tx;
356+
}
327357
CTransactionRef getTx(const uint256& txid) override
328358
{
329359
LOCK(m_wallet->cs_wallet);
@@ -506,6 +536,10 @@ class WalletImpl : public Wallet
506536
if (reason) *reason = fee_calc.reason;
507537
return result;
508538
}
539+
CFeeRate getDeniabilizationFeeRate(uint confirm_target) override
540+
{
541+
return CalculateDeniabilizationFeeRate(*m_wallet, confirm_target);
542+
}
509543
unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; }
510544
bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
511545
bool canGetAddresses() override { return m_wallet->CanGetAddresses(); }

0 commit comments

Comments
 (0)