DILSHAD737 commited on
Commit
ea8a0b3
Β·
1 Parent(s): 275228c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -71
app.py CHANGED
@@ -14,12 +14,23 @@ from itertools import permutations, combinations
14
  from mnemonic import Mnemonic
15
  import coincurve
16
  from eth_utils import keccak, to_checksum_address
17
- import spaces
18
 
19
- @spaces.GPU
 
 
 
 
 
 
 
 
 
 
20
  def _noop():
21
  return "ok"
22
 
 
 
23
  # ============================================================
24
  _MNEMONIC = None
25
  def _get_mnemonic():
@@ -28,7 +39,6 @@ def _get_mnemonic():
28
  _MNEMONIC = Mnemonic("english")
29
  return _MNEMONIC
30
 
31
- # ---------- BIP32 manual ----------
32
  _SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
33
  _HARDENED = 0x80000000
34
 
@@ -51,7 +61,6 @@ def seed_to_privkey_hd(seed, address_index=0):
51
  key, chain = _bip32_derive_child_priv(key, chain, idx)
52
  return key
53
 
54
- # ---------- BIP39 ----------
55
  try:
56
  import bip39 as _bip39_rust
57
  _USE_RUST_BIP39 = True
@@ -76,9 +85,7 @@ def phrase_to_seed(phrase, passphrase=""):
76
  return _seed_fn(phrase, passphrase)
77
  return _get_mnemonic().to_seed(phrase, passphrase=passphrase)
78
 
79
- # ---------- Address Cache ----------
80
  _ADDRESS_CACHE = {}
81
-
82
  def load_addresses_cached(path):
83
  if path in _ADDRESS_CACHE:
84
  cached_mtime, cached_set = _ADDRESS_CACHE[path]
@@ -88,61 +95,51 @@ def load_addresses_cached(path):
88
  return cached_set
89
  except OSError:
90
  pass
91
-
92
  s = set()
93
  with open(path, "r", encoding="utf-8") as f:
94
  for line in f:
95
  a = line.strip().strip('"').strip("'").lstrip("\ufeff").lower()
96
- if a.startswith("0x"):
97
- a = a[2:]
98
- if a and len(a) == 40:
99
- s.add(a)
100
-
101
- try:
102
- mtime = os.path.getmtime(path)
103
- except OSError:
104
- mtime = 0
105
  _ADDRESS_CACHE[path] = (mtime, s)
106
  return s
107
 
108
  def normalize(addr):
109
  a = addr.strip().lower()
110
- if a.startswith("0x"):
111
- a = a[2:]
112
  return a
113
 
114
- # ---------- Generator ----------
115
  def generate_phrases_for_words(wordlist, mode):
116
  n = len(wordlist)
117
  if mode == "order":
118
- for perm in permutations(wordlist):
119
- yield " ".join(perm)
120
  elif mode == "replace":
121
  for i in range(n):
122
  for w in wordlist:
123
- new = wordlist.copy()
124
- new[i] = w
125
- yield " ".join(new)
126
  elif mode == "subset":
127
  for r in range(1, n + 1):
128
- for combo in combinations(wordlist, r):
129
- yield " ".join(combo)
130
 
131
  # ============================================================
132
  # Scan Function
133
  # ============================================================
134
-
135
  def run_scan(my_phrase, num_phrases, addresses_file, phrase_mode,
136
  start_index, end_index, passphrase, progress=gr.Progress()):
137
 
 
 
 
 
 
 
138
  if not os.path.exists(addresses_file):
139
  return f"❌ File nahi mili: {addresses_file}", ""
140
 
141
- num_phrases = int(num_phrases)
142
- start_index = int(start_index)
143
- end_index = int(end_index)
144
-
145
- progress(0, desc="Loading addresses...")
146
  try:
147
  known = load_addresses_cached(addresses_file)
148
  except Exception as e:
@@ -163,85 +160,217 @@ def run_scan(my_phrase, num_phrases, addresses_file, phrase_mode,
163
 
164
  log_text = f"βœ… Loaded {len(known):,} addresses\n"
165
  log_text += f"πŸ”§ Mode: {phrase_mode} | πŸ”‘ Index: {start_index}-{end_index}\n"
166
- log_text += f"πŸ“ Total phrases: {len(phrases)}\n\n"
167
 
168
- log_text += "Phrases:\n"
169
  for i, p in enumerate(phrases, 1):
170
- tag = " (AAPKI)" if i == 1 and len(my_words) == 12 else ""
171
- log_text += f" {i}. {p}{tag}\n"
172
- log_text += "\n--- Scanning ---\n"
173
 
174
  matches = []
175
  start = time.time()
176
  total_phrases = len(phrases)
177
 
178
  for p_idx, phrase in enumerate(phrases, 1):
179
- progress((p_idx - 1) / total_phrases, desc=f"Phrase {p_idx}/{total_phrases}...")
180
- log_text += f"\nπŸ” [{p_idx}/{total_phrases}] Checking: {phrase}\n"
181
  words = phrase.strip().split()
182
 
183
  if len(words) != 12:
184
- log_text += " ⚠️ Skipped (not 12 words)\n"
185
  continue
186
 
187
  for idx in range(start_index, end_index + 1):
188
  for phrase_str in generate_phrases_for_words(words, phrase_mode):
189
- if not is_valid_bip39(phrase_str):
190
- continue
191
  try:
192
  seed = phrase_to_seed(phrase_str, passphrase)
193
  priv = seed_to_privkey_hd(seed, idx)
194
  pub = coincurve.PrivateKey(priv).public_key.format(compressed=False)[1:]
195
  addr = to_checksum_address(keccak(pub)[-20:])
196
- except Exception:
197
- continue
198
 
199
  if normalize(addr) in known:
200
- match_line = f"πŸŽ‰ MATCH!\nPhrase: {phrase_str}\nIndex: {idx}\nKey: {priv.hex()}\nAddr: {addr}\n"
201
- log_text += match_line
202
  matches.append(match_line)
203
 
204
  elapsed = time.time() - start
205
- log_text += f"\n=== DONE ===\nTotal time: {elapsed:.1f}s\nTotal matches: {len(matches)}\n"
206
-
207
- matches_text = "\n".join(matches) if matches else "Koi match nahi mila"
208
 
209
  return log_text, matches_text
210
 
211
  # ============================================================
212
- # Bulletproof UI
213
  # ============================================================
214
- with gr.Blocks() as demo:
215
- gr.Markdown("# πŸ” Phrase Scanner")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  my_phrase = gr.Textbox(
218
- label="πŸ”‘ Apni Phrase (optional)",
219
  value="lesson cushion crane affair federal decline limb spend reunion chief cart zebra",
220
  lines=2,
 
 
221
  )
222
-
223
- num_phrases = gr.Number(label="Kitni Random Phrases Check Karein?", value=10, precision=0, minimum=0)
224
- addresses_file = gr.Textbox(label="Addresses File", value="12730802_addresses.csv")
225
-
226
- phrase_mode = gr.Radio(choices=["replace", "order", "subset"], value="replace", label="Phrase Mode")
227
- start_index = gr.Number(label="Start Index", value=0, precision=0, minimum=0)
228
- end_index = gr.Number(label="End Index", value=20, precision=0, minimum=0)
229
-
230
- passphrase = gr.Textbox(label="BIP39 Passphrase (khali chhod dein)", value="", type="password")
231
-
232
- run_btn = gr.Button("πŸš€ Scan Shuru Karein", variant="primary")
233
 
234
- gr.Markdown("### Output")
235
- log_output = gr.Textbox(lines=15, interactive=False, show_label=False)
236
-
237
- gr.Markdown("### Matches")
238
- matches_output = gr.Textbox(lines=8, interactive=False, show_label=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
  run_btn.click(
241
  fn=run_scan,
242
  inputs=[my_phrase, num_phrases, addresses_file, phrase_mode,
243
  start_index, end_index, passphrase],
244
  outputs=[log_output, matches_output],
 
245
  )
246
 
247
  # ZeroGPU requirement
@@ -249,7 +378,6 @@ _noop()
249
 
250
  _original_launch = demo.launch
251
  def _patched_launch(**kwargs):
252
- kwargs.setdefault("ssr_mode", False)
253
  kwargs.setdefault("share", True)
254
  kwargs.setdefault("server_name", "0.0.0.0")
255
  kwargs.setdefault("server_port", 7860)
@@ -257,4 +385,4 @@ def _patched_launch(**kwargs):
257
  demo.launch = _patched_launch
258
 
259
  if __name__ == "__main__":
260
- demo.launch(theme=gr.themes.Soft())
 
14
  from mnemonic import Mnemonic
15
  import coincurve
16
  from eth_utils import keccak, to_checksum_address
 
17
 
18
+ # ============================================================
19
+ # Spaces GPU Fallback
20
+ # ============================================================
21
+ try:
22
+ import spaces
23
+ gpu_decorator = spaces.GPU
24
+ except ImportError:
25
+ def gpu_decorator(func):
26
+ return func
27
+
28
+ @gpu_decorator
29
  def _noop():
30
  return "ok"
31
 
32
+ # ============================================================
33
+ # Backend Logic (Unchanged)
34
  # ============================================================
35
  _MNEMONIC = None
36
  def _get_mnemonic():
 
39
  _MNEMONIC = Mnemonic("english")
40
  return _MNEMONIC
41
 
 
42
  _SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
43
  _HARDENED = 0x80000000
44
 
 
61
  key, chain = _bip32_derive_child_priv(key, chain, idx)
62
  return key
63
 
 
64
  try:
65
  import bip39 as _bip39_rust
66
  _USE_RUST_BIP39 = True
 
85
  return _seed_fn(phrase, passphrase)
86
  return _get_mnemonic().to_seed(phrase, passphrase=passphrase)
87
 
 
88
  _ADDRESS_CACHE = {}
 
89
  def load_addresses_cached(path):
90
  if path in _ADDRESS_CACHE:
91
  cached_mtime, cached_set = _ADDRESS_CACHE[path]
 
95
  return cached_set
96
  except OSError:
97
  pass
 
98
  s = set()
99
  with open(path, "r", encoding="utf-8") as f:
100
  for line in f:
101
  a = line.strip().strip('"').strip("'").lstrip("\ufeff").lower()
102
+ if a.startswith("0x"): a = a[2:]
103
+ if a and len(a) == 40: s.add(a)
104
+ try: mtime = os.path.getmtime(path)
105
+ except OSError: mtime = 0
 
 
 
 
 
106
  _ADDRESS_CACHE[path] = (mtime, s)
107
  return s
108
 
109
  def normalize(addr):
110
  a = addr.strip().lower()
111
+ if a.startswith("0x"): a = a[2:]
 
112
  return a
113
 
 
114
  def generate_phrases_for_words(wordlist, mode):
115
  n = len(wordlist)
116
  if mode == "order":
117
+ for perm in permutations(wordlist): yield " ".join(perm)
 
118
  elif mode == "replace":
119
  for i in range(n):
120
  for w in wordlist:
121
+ new = wordlist.copy(); new[i] = w; yield " ".join(new)
 
 
122
  elif mode == "subset":
123
  for r in range(1, n + 1):
124
+ for combo in combinations(wordlist, r): yield " ".join(combo)
 
125
 
126
  # ============================================================
127
  # Scan Function
128
  # ============================================================
129
+ @gpu_decorator
130
  def run_scan(my_phrase, num_phrases, addresses_file, phrase_mode,
131
  start_index, end_index, passphrase, progress=gr.Progress()):
132
 
133
+ num_phrases = int(num_phrases or 0)
134
+ start_index = int(start_index or 0)
135
+ end_index = int(end_index or 0)
136
+ my_phrase = my_phrase or ""
137
+ passphrase = passphrase or ""
138
+
139
  if not os.path.exists(addresses_file):
140
  return f"❌ File nahi mili: {addresses_file}", ""
141
 
142
+ progress(0, desc="Initializing scanner...")
 
 
 
 
143
  try:
144
  known = load_addresses_cached(addresses_file)
145
  except Exception as e:
 
160
 
161
  log_text = f"βœ… Loaded {len(known):,} addresses\n"
162
  log_text += f"πŸ”§ Mode: {phrase_mode} | πŸ”‘ Index: {start_index}-{end_index}\n"
163
+ log_text += f"πŸ“ Total phrases to check: {len(phrases)}\n\n"
164
 
165
+ log_text += "πŸ“œ Phrases:\n"
166
  for i, p in enumerate(phrases, 1):
167
+ tag = " (YOUR PHRASE)" if i == 1 and len(my_words) == 12 else ""
168
+ log_text += f" [{i}] {p}{tag}\n"
169
+ log_text += "\n" + "="*40 + "\nπŸš€ STARTING SCAN...\n" + "="*40 + "\n"
170
 
171
  matches = []
172
  start = time.time()
173
  total_phrases = len(phrases)
174
 
175
  for p_idx, phrase in enumerate(phrases, 1):
176
+ progress((p_idx - 1) / total_phrases, desc=f"Scanning Phrase {p_idx}/{total_phrases}...")
177
+ log_text += f"\nπŸ” [{p_idx}/{total_phrases}] {phrase}\n"
178
  words = phrase.strip().split()
179
 
180
  if len(words) != 12:
181
+ log_text += " ⚠️ Skipped (invalid length)\n"
182
  continue
183
 
184
  for idx in range(start_index, end_index + 1):
185
  for phrase_str in generate_phrases_for_words(words, phrase_mode):
186
+ if not is_valid_bip39(phrase_str): continue
 
187
  try:
188
  seed = phrase_to_seed(phrase_str, passphrase)
189
  priv = seed_to_privkey_hd(seed, idx)
190
  pub = coincurve.PrivateKey(priv).public_key.format(compressed=False)[1:]
191
  addr = to_checksum_address(keccak(pub)[-20:])
192
+ except Exception: continue
 
193
 
194
  if normalize(addr) in known:
195
+ match_line = f"πŸŽ‰ MATCH FOUND!\nPhrase: {phrase_str}\nIndex: {idx}\nKey: {priv.hex()}\nAddr: {addr}\n"
196
+ log_text += f" >>> {match_line}"
197
  matches.append(match_line)
198
 
199
  elapsed = time.time() - start
200
+ log_text += f"\n{'='*40}\nβœ… SCAN COMPLETE\nTime: {elapsed:.1f}s | Matches: {len(matches)}\n{'='*40}"
201
+ matches_text = "\n".join(matches) if matches else "No matches found in this run."
 
202
 
203
  return log_text, matches_text
204
 
205
  # ============================================================
206
+ # 🎨 PREMIUM UI DESIGN
207
  # ============================================================
208
+
209
+ custom_css = """
210
+ /* Container & Typography */
211
+ .gradio-container {
212
+ max-width: 1100px !important;
213
+ margin: auto !important;
214
+ font-family: 'Inter', 'Segoe UI', system-ui, sans-serif !important;
215
+ }
216
+ h1 {
217
+ text-align: center;
218
+ background: linear-gradient(90deg, #10b981, #3b82f6);
219
+ -webkit-background-clip: text;
220
+ -webkit-text-fill-color: transparent;
221
+ font-weight: 800 !important;
222
+ margin-bottom: 5px !important;
223
+ }
224
+ h3.sub-header {
225
+ color: #64748b !important;
226
+ text-align: center;
227
+ margin-top: 0 !important;
228
+ margin-bottom: 30px !important;
229
+ font-weight: 400 !important;
230
+ }
231
+ h4.section-title {
232
+ border-bottom: 2px solid #1e293b;
233
+ padding-bottom: 8px;
234
+ margin-bottom: 15px !important;
235
+ color: #e2e8f0 !important;
236
+ font-weight: 600 !important;
237
+ }
238
+
239
+ /* Terminal / Log Boxes */
240
+ .terminal-log textarea, .match-log textarea {
241
+ font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace !important;
242
+ font-size: 13px !important;
243
+ border-radius: 10px !important;
244
+ padding: 15px !important;
245
+ line-height: 1.6 !important;
246
+ border: 1px solid #1e293b !important;
247
+ }
248
+ .terminal-log textarea {
249
+ background-color: #0f172a !important;
250
+ color: #cbd5e1 !important;
251
+ box-shadow: inset 0 0 20px rgba(0,0,0,0.5) !important;
252
+ }
253
+ .match-log textarea {
254
+ background-color: #022c22 !important;
255
+ color: #6ee7b7 !important;
256
+ border: 1px solid #065f46 !important;
257
+ font-weight: bold !important;
258
+ font-size: 14px !important;
259
+ }
260
+
261
+ /* Buttons */
262
+ .primary-btn button {
263
+ background: linear-gradient(90deg, #10b981, #059669) !important;
264
+ border: none !important;
265
+ font-weight: 700 !important;
266
+ font-size: 16px !important;
267
+ padding: 14px 24px !important;
268
+ border-radius: 10px !important;
269
+ box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3) !important;
270
+ transition: all 0.3s ease !important;
271
+ letter-spacing: 0.5px !important;
272
+ }
273
+ .primary-btn button:hover {
274
+ transform: translateY(-2px) !important;
275
+ box-shadow: 0 8px 20px rgba(16, 185, 129, 0.5) !important;
276
+ }
277
+
278
+ /* Hide default Gradio footer */
279
+ footer { display: none !important; }
280
+
281
+ /* Tabs styling */
282
+ .tabs > .tab-nav > button {
283
+ font-weight: 600 !important;
284
+ font-size: 15px !important;
285
+ }
286
+ """
287
+
288
+ theme = gr.themes.Soft(
289
+ primary_hue="emerald",
290
+ secondary_hue="blue",
291
+ neutral_hue="slate",
292
+ ).set(
293
+ body_background_fill_dark="#020617",
294
+ block_background_fill_dark="#0f172a",
295
+ block_border_color_dark="#1e293b",
296
+ input_border_color_dark="#334155",
297
+ )
298
+
299
+ with gr.Blocks(theme=theme, css=custom_css, title="Crypto Phrase Scanner") as demo:
300
 
301
+ gr.Markdown("# πŸš€ Crypto Phrase Scanner")
302
+ gr.Markdown("### Advanced BIP39 / BIP32 Address Recovery & Scanning Tool", elem_classes=["sub-header"])
303
+
304
+ with gr.Row():
305
+ # Left Column: Target Settings
306
+ with gr.Column(scale=2):
307
+ gr.Markdown("#### 🎯 Target Configuration", elem_classes=["section-title"])
308
+ addresses_file = gr.Textbox(
309
+ label="Target Addresses File (CSV/TXT)",
310
+ value="12730802_addresses.csv",
311
+ info="Path to the file containing known Ethereum addresses."
312
+ )
313
+ with gr.Row():
314
+ start_index = gr.Number(label="Start Derivation Index", value=0, precision=0, minimum=0, scale=1)
315
+ end_index = gr.Number(label="End Derivation Index", value=20, precision=0, minimum=0, scale=1)
316
+
317
+ # Right Column: Scan Settings
318
+ with gr.Column(scale=1):
319
+ gr.Markdown("#### βš™οΈ Scan Parameters", elem_classes=["section-title"])
320
+ phrase_mode = gr.Radio(
321
+ choices=["replace", "order", "subset"],
322
+ value="replace",
323
+ label="Mutation Mode",
324
+ info="How to modify the base words."
325
+ )
326
+ num_phrases = gr.Number(label="Random Phrases to Generate", value=10, precision=0, minimum=0)
327
+ passphrase = gr.Textbox(
328
+ label="BIP39 Passphrase (Optional)",
329
+ value="",
330
+ type="password",
331
+ info="Leave blank if no 25th word/passphrase is used."
332
+ )
333
+
334
+ gr.Markdown("#### πŸ”‘ Your Base Phrase (Optional)", elem_classes=["section-title"])
335
  my_phrase = gr.Textbox(
336
+ label="Enter your 12-word seed phrase",
337
  value="lesson cushion crane affair federal decline limb spend reunion chief cart zebra",
338
  lines=2,
339
+ placeholder="word1 word2 word3 ... word12",
340
+ info="If left blank, the scanner will only use randomly generated phrases."
341
  )
 
 
 
 
 
 
 
 
 
 
 
342
 
343
+ with gr.Row():
344
+ with gr.Column():
345
+ run_btn = gr.Button("πŸš€ START SCANNING", variant="primary", elem_classes=["primary-btn"], size="lg")
346
+
347
+ gr.Markdown("---")
348
+
349
+ with gr.Tabs():
350
+ with gr.Tab("πŸ“Š Live Execution Log"):
351
+ log_output = gr.Textbox(
352
+ lines=20,
353
+ interactive=False,
354
+ show_label=False,
355
+ elem_classes=["terminal-log"],
356
+ value="Waiting for scan to begin..."
357
+ )
358
+
359
+ with gr.Tab("πŸŽ‰ Matches Found"):
360
+ matches_output = gr.Textbox(
361
+ lines=10,
362
+ interactive=False,
363
+ show_label=False,
364
+ elem_classes=["match-log"],
365
+ value="No matches found yet."
366
+ )
367
 
368
  run_btn.click(
369
  fn=run_scan,
370
  inputs=[my_phrase, num_phrases, addresses_file, phrase_mode,
371
  start_index, end_index, passphrase],
372
  outputs=[log_output, matches_output],
373
+ show_progress="full"
374
  )
375
 
376
  # ZeroGPU requirement
 
378
 
379
  _original_launch = demo.launch
380
  def _patched_launch(**kwargs):
 
381
  kwargs.setdefault("share", True)
382
  kwargs.setdefault("server_name", "0.0.0.0")
383
  kwargs.setdefault("server_port", 7860)
 
385
  demo.launch = _patched_launch
386
 
387
  if __name__ == "__main__":
388
+ demo.launch()