1use std::collections::{BTreeMap, BTreeSet, VecDeque};
23use fedimint_core::encoding::{Decodable, Encodable};
4use fedimint_logging::LOG_CLIENT_MODULE_WALLET;
5use tracing::debug;
67use crate::WalletClientModuleData;
8use crate::backup::FEDERATION_RECOVER_MAX_GAP;
9use crate::client_db::TweakIdx;
1011/// Tracks addresses `TweakIdx`s/addresses that are expected to have been used
12/// against the stream of addresses that were actually used for peg-ins in the
13/// Federation.
14///
15/// Since replaying Federation history is entirely private, the goal here
16/// is to find the last peg-in address already used without compromising
17/// privacy like when querying Bitcoin node.
18///
19/// While at it, collect some addresses that were actually used for peg-ins by
20/// other clients, just to query for them as decoys and thus hopefully make the
21/// malicious Bitcoin node operator have less confidence about which addresses
22/// are actually linked with each other.
23#[derive(Clone, Debug, Encodable, Decodable)]
24pub struct ConsensusPegInTweakIdxesUsedTracker {
25/// Any time we detect one of the scripts in `pending_pubkey_scripts` was
26 /// used we insert the `tweak_idx`, so we can skip asking network about
27 /// them (which would be bad for privacy)
28used_tweak_idxes: BTreeSet<TweakIdx>,
29/// All the pubkey scripts we are looking for in the federation history, to
30 /// detect previous successful peg-ins.
31pending_pubkey_scripts: BTreeMap<bitcoin::ScriptBuf, TweakIdx>,
32/// Next tweak idx to add to `pending_pubkey_scripts`
33next_pending_tweak_idx: TweakIdx,
3435/// Collection of recent scripts from federation history that do not belong
36 /// to us
37decoys: VecDeque<bitcoin::ScriptBuf>,
38// To avoid updating `decoys` for the whole recovery, which might be a lot of extra updates
39 // most of which will be thrown away, ignore script pubkeys from before this `session_idx`
40decoy_session_threshold: u64,
41}
4243impl ConsensusPegInTweakIdxesUsedTracker {
44pub(crate) fn new(
45 previous_next_unused_idx: TweakIdx,
46 start_session_idx: u64,
47 current_session_count: u64,
48 data: &WalletClientModuleData,
49 ) -> Self {
50debug_assert!(start_session_idx <= current_session_count);
5152let mut s = Self {
53 next_pending_tweak_idx: previous_next_unused_idx,
54 pending_pubkey_scripts: BTreeMap::new(),
55 decoys: VecDeque::new(),
56 decoy_session_threshold: current_session_count
57 .saturating_sub((current_session_count.saturating_sub(current_session_count)) / 20),
58 used_tweak_idxes: BTreeSet::new(),
59 };
6061 s.init(data);
6263 s
64 }
6566fn init(&mut self, data: &WalletClientModuleData) {
67for _ in 0..super::ONCHAIN_RECOVER_MAX_GAP {
68self.generate_next_pending_tweak_idx(data);
69 }
70debug_assert_eq!(
71self.pending_pubkey_scripts.len(),
72super::ONCHAIN_RECOVER_MAX_GAP as usize
73 );
74 }
7576pub fn used_tweak_idxes(&self) -> &BTreeSet<TweakIdx> {
77&self.used_tweak_idxes
78 }
7980fn generate_next_pending_tweak_idx(&mut self, data: &WalletClientModuleData) {
81let (script, _address, _tweak_key, _operation_id) =
82 data.derive_peg_in_script(self.next_pending_tweak_idx);
8384self.pending_pubkey_scripts
85 .insert(script, self.next_pending_tweak_idx);
86self.next_pending_tweak_idx = self.next_pending_tweak_idx.next();
87 }
8889fn refill_pending_pool_up_to_tweak_idx(
90&mut self,
91 data: &WalletClientModuleData,
92 tweak_idx: TweakIdx,
93 ) {
94while self.next_pending_tweak_idx < tweak_idx {
95self.generate_next_pending_tweak_idx(data);
96 }
97 }
9899pub(crate) fn handle_script(
100&mut self,
101 data: &WalletClientModuleData,
102 script: &bitcoin::ScriptBuf,
103 session_idx: u64,
104 ) {
105if let Some(tweak_idx) = self.pending_pubkey_scripts.get(script).copied() {
106debug!(target: LOG_CLIENT_MODULE_WALLET, %session_idx, ?tweak_idx, "Found previously used tweak_idx in federation history");
107108self.used_tweak_idxes.insert(tweak_idx);
109110self.refill_pending_pool_up_to_tweak_idx(
111 data,
112 tweak_idx.advance(FEDERATION_RECOVER_MAX_GAP),
113 );
114 } else if self.decoy_session_threshold < session_idx {
115self.push_decoy(script);
116 }
117 }
118119/// Write a someone-elses used deposit address to use a decoy
120fn push_decoy(&mut self, script: &bitcoin::ScriptBuf) {
121self.decoys.push_front(script.clone());
122if 50 < self.decoys.len() {
123self.decoys.pop_back();
124 }
125 }
126127/// Pop a someone-elses used deposit address to use a decoy
128pub(crate) fn pop_decoy(&mut self) -> Option<bitcoin::ScriptBuf> {
129self.decoys.pop_front()
130 }
131}