Browse Source

privacy analysis: detect address reuse

add tx position to get_addr_io
master
ThomasV 3 years ago
parent
commit
2ed71579c3
  1. 6
      electrum/address_synchronizer.py
  2. 39
      electrum/gui/qt/utxo_dialog.py
  3. 22
      electrum/wallet.py

6
electrum/address_synchronizer.py

@ -778,13 +778,13 @@ class AddressSynchronizer(Logger, EventListener):
sent = {}
for tx_hash, height in h:
hh, pos = self.get_txpos(tx_hash)
assert hh == height
d = self.db.get_txo_addr(tx_hash, address)
for n, (v, is_cb) in d.items():
received[tx_hash + ':%d'%n] = (height, pos, v, is_cb)
for tx_hash, height in h:
l = self.db.get_txi_addr(tx_hash, address)
for txi, v in l:
sent[txi] = tx_hash, height
sent[txi] = tx_hash, height, pos
return received, sent
def get_addr_outputs(self, address: str) -> Dict[TxOutpoint, PartialTxInput]:
@ -799,7 +799,7 @@ class AddressSynchronizer(Logger, EventListener):
utxo.block_height = tx_height
utxo.block_txpos = tx_pos
if prevout_str in sent:
txid, height = sent[prevout_str]
txid, height, pos = sent[prevout_str]
utxo.spent_txid = txid
utxo.spent_height = height
else:

39
electrum/gui/qt/utxo_dialog.py

@ -28,13 +28,14 @@ import copy
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtGui import QTextCharFormat, QFont
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QTextBrowser
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QTextBrowser
from electrum.i18n import _
from .util import WindowModalDialog, ButtonsLineEdit, ShowQRLineEdit, ColorScheme, Buttons, CloseButton, MONOSPACE_FONT, WWLabel
from .history_list import HistoryList, HistoryModel
from .qrtextedit import ShowQRTextEdit
from .transaction_dialog import TxOutputColoring
if TYPE_CHECKING:
from .main_window import ElectrumWindow
@ -66,6 +67,10 @@ class UTXODialog(WindowModalDialog):
self.parents_list.setMinimumWidth(900)
self.parents_list.setMinimumHeight(400)
self.parents_list.setLineWrapMode(QTextBrowser.NoWrap)
self.txo_color_parent = TxOutputColoring(
legend=_("Direct parent"), color=ColorScheme.BLUE, tooltip=_("Direct parent"))
self.txo_color_uncle = TxOutputColoring(
legend=_("Address reuse"), color=ColorScheme.RED, tooltip=_("Address reuse"))
cursor = self.parents_list.textCursor()
ext = QTextCharFormat()
@ -81,7 +86,7 @@ class UTXODialog(WindowModalDialog):
ASCII_PIPE = ''
ASCII_SPACE = ' '
def print_ascii_tree(_txid, prefix, is_last):
def print_ascii_tree(_txid, prefix, is_last, is_uncle):
if _txid not in parents:
return
tx_height, tx_pos = self.wallet.adb.get_txpos(_txid)
@ -91,7 +96,10 @@ class UTXODialog(WindowModalDialog):
label = '[duplicate]'
c = '' if _txid == txid else (ASCII_EDGE if is_last else ASCII_BRANCH)
cursor.insertText(prefix + c, ext)
lnk = QTextCharFormat()
if is_uncle:
lnk = QTextCharFormat(self.txo_color_uncle.text_char_format)
else:
lnk = QTextCharFormat(self.txo_color_parent.text_char_format)
lnk.setToolTip(_('Click to open, right-click for menu'))
lnk.setAnchorHref(_txid)
#lnk.setAnchorNames([a_name])
@ -102,22 +110,27 @@ class UTXODialog(WindowModalDialog):
cursor.insertText(label, ext)
cursor.insertBlock()
next_prefix = '' if txid == _txid else prefix + (ASCII_SPACE if is_last else ASCII_PIPE)
parents_list = parents_copy.pop(_txid, [])
for i, p in enumerate(parents_list):
is_last = i == len(parents_list) - 1
print_ascii_tree(p, next_prefix, is_last)
parents_list, uncle_list = parents_copy.pop(_txid, ([],[]))
for i, p in enumerate(parents_list + uncle_list):
is_last = (i == len(parents_list) + len(uncle_list)- 1)
is_uncle = (i > len(parents_list) - 1)
print_ascii_tree(p, next_prefix, is_last, is_uncle)
# recursively build the tree
print_ascii_tree(txid, '', False)
print_ascii_tree(txid, '', False, False)
vbox = QVBoxLayout()
vbox.addWidget(QLabel(_("Output point") + ": " + str(self.utxo.short_id)))
vbox.addWidget(QLabel(_("Amount") + ": " + self.main_window.format_amount_and_units(self.utxo.value_sats())))
vbox.addWidget(QLabel(_("This UTXO has {} parent transactions in your wallet").format(num_parents)))
vbox.addWidget(self.parents_list)
msg = ' '.join([
_("Note: This analysis only shows parent transactions, and does not take address reuse into consideration."),
_("If you reuse addresses, more links can be established between your transactions, that are not displayed here.")
])
vbox.addWidget(WWLabel(msg))
legend_hbox = QHBoxLayout()
legend_hbox.setContentsMargins(0, 0, 0, 0)
legend_hbox.addStretch(2)
legend_hbox.addWidget(self.txo_color_parent.legend_label)
legend_hbox.addWidget(self.txo_color_uncle.legend_label)
vbox.addLayout(legend_hbox)
self.txo_color_parent.legend_label.setVisible(True)
self.txo_color_uncle.legend_label.setVisible(True)
vbox.addLayout(Buttons(CloseButton(self)))
self.setLayout(vbox)
# set cursor to top

22
electrum/wallet.py

@ -871,23 +871,37 @@ class Abstract_Wallet(ABC, Logger, EventListener):
"""
if not self.is_up_to_date():
return {}
if self._last_full_history is None:
self._last_full_history = self.get_full_history(None)
with self.lock, self.transaction_lock:
if self._last_full_history is None:
self._last_full_history = self.get_full_history(None)
result = self._tx_parents_cache.get(txid, None)
if result is not None:
return result
result = {}
parents = []
uncles = []
tx = self.adb.get_transaction(txid)
assert tx, f"cannot find {txid} in db"
for i, txin in enumerate(tx.inputs()):
_txid = txin.prevout.txid.hex()
parents.append(_txid)
# detect address reuse
addr = self.adb.get_txin_address(txin)
received, sent = self.adb.get_addr_io(addr)
if len(sent) > 1:
my_txid, my_height, my_pos = sent[txin.prevout.to_str()]
assert my_txid == txid
for k, v in sent.items():
if k != txin.prevout.to_str():
reuse_txid, reuse_height, reuse_pos = v
if (reuse_height, reuse_pos) < (my_height, my_pos):
uncle_txid, uncle_index = k.split(':')
uncles.append(uncle_txid)
for _txid in parents + uncles:
if _txid in self._last_full_history.keys():
result.update(self.get_tx_parents(_txid))
result[txid] = parents
result[txid] = parents, uncles
self._tx_parents_cache[txid] = result
return result

Loading…
Cancel
Save