leonett commited on
Commit
7c96fc8
verified
1 Parent(s): 7c0d33f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +35 -75
app.py CHANGED
@@ -7,14 +7,14 @@ import re
7
  import os
8
  import tempfile
9
  import base64
10
- from fpdf import FPDF, HTMLMixin
11
 
12
  # ---------------- Funciones de an谩lisis y grafo -------------------
13
 
14
  def get_transaction(tx_id):
15
  """
16
- Intenta obtener los datos de una transacci贸n Bitcoin usando endpoints p煤blicos.
17
- Primero se prueba con Blockstream.info y, de fallar, con mempool.space.
18
  """
19
  urls = [
20
  f"https://blockstream.info/api/tx/{tx_id}",
@@ -70,8 +70,8 @@ def generar_grafo(tx_data):
70
  def check_blockchain_tags(tx_id):
71
  """
72
  Consulta la API p煤blica de blockchain.com para ver si el TXID (o sus metadatos)
73
- incluye informaci贸n que indique que la transacci贸n ha sido etiquetada como MIXER.
74
- (Esta funci贸n es un ejemplo y podr铆a necesitar ajustes seg煤n la respuesta real.)
75
  """
76
  url = f"https://blockchain.info/rawtx/{tx_id}?format=json"
77
  try:
@@ -96,9 +96,9 @@ def analizar_transaccion(tx_id):
96
  - Calcula totales en BTC y fee, mostrando tambi茅n su equivalente en USD.
97
  - Muestra informaci贸n adicional (versi贸n, tama帽o, peso, fee rate).
98
  - Aplica heur铆stica para detectar posibles CoinJoin/mixers.
99
- - Incorpora informaci贸n adicional de blockchain.com (etiqueta MIXER).
100
  - Genera un grafo interactivo.
101
- - Retorna un informe en HTML, la figura del grafo y una tupla (reporte, fig) para el PDF.
102
  """
103
  tx_data = get_transaction(tx_id)
104
  if not tx_data:
@@ -113,6 +113,7 @@ def analizar_transaccion(tx_id):
113
  total_output_value = sum(out.get("value", 0) for out in tx_data.get("vout", [])) / 1e8
114
  fee = total_input_value - total_output_value
115
 
 
116
  heuristic_mixer = ((num_inputs > 5 and num_outputs > 5 and montos_unicos < 3) or
117
  (num_outputs > 2 and montos_unicos <= 2))
118
  es_mixer_blockchain = check_blockchain_tags(tx_id)
@@ -246,73 +247,23 @@ def analizar_transaccion(tx_id):
246
  </p>
247
  </div>
248
  """
 
249
  return reporte, fig, (reporte, fig)
250
 
251
  except Exception as e:
252
  return f"鈿狅笍 Error durante el an谩lisis: {str(e)}", None, None
253
 
254
- # ---------------- Funci贸n para generar el PDF -------------------
255
 
256
- class PDF(FPDF, HTMLMixin):
257
- pass
258
-
259
- def generar_pdf(report, fig):
260
- """
261
- Genera un PDF que incluye:
262
- - T铆tulo: ANALISIS FORENSE DE BLOCKCHAIN
263
- - El contenido del informe (como texto plano)
264
- - El grafo (convertido a imagen PNG)
265
- - Pie de p谩gina: "Generado por Jos茅 R. Leonett"
266
- Retorna el PDF como bytes.
267
- """
268
- with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp:
269
- tmp_path = tmp.name
270
- try:
271
- fig.write_image(tmp_path)
272
- except Exception as e:
273
- print("Error al guardar la imagen del grafo:", e)
274
- tmp_path = None
275
-
276
- plain_text = re.sub('<[^<]+?>', '', report)
277
-
278
- pdf = PDF()
279
- pdf.add_page()
280
- pdf.set_font("Arial", "B", 16)
281
- pdf.cell(0, 10, "ANALISIS FORENSE DE BLOCKCHAIN", ln=True, align="C")
282
- pdf.ln(5)
283
- pdf.set_font("Arial", "", 12)
284
- pdf.multi_cell(0, 10, plain_text)
285
- pdf.ln(5)
286
- if tmp_path and os.path.exists(tmp_path):
287
- pdf.add_page()
288
- pdf.image(tmp_path, x=10, y=20, w=190)
289
- os.remove(tmp_path)
290
- pdf.set_font("Arial", "I", 10)
291
- pdf.ln(10)
292
- pdf.cell(0, 10, "Generado por Jos茅 R. Leonett", ln=True, align="C")
293
-
294
- pdf_bytes = pdf.output(dest="S").encode("latin1")
295
- return pdf_bytes
296
-
297
- def descargar_pdf(analysis_tuple):
298
  """
299
- Genera el PDF y retorna un HTML que dispara la descarga autom谩tica.
 
300
  """
301
  if not analysis_tuple:
302
- return "<p>No hay an谩lisis generado.</p>"
303
- report, fig = analysis_tuple
304
- pdf_bytes = generar_pdf(report, fig)
305
- pdf_b64 = base64.b64encode(pdf_bytes).decode("utf-8")
306
- html_download = f'''
307
- <html>
308
- <body>
309
- <a id="downloadLink" href="data:application/pdf;base64,{pdf_b64}" download="analisis.pdf" style="display:none;"></a>
310
- <script>document.getElementById("downloadLink").click();</script>
311
- <p>Si no se descarga autom谩ticamente, <a href="data:application/pdf;base64,{pdf_b64}" download="analisis.pdf">haz clic aqu铆</a>.</p>
312
- </body>
313
- </html>
314
- '''
315
- return html_download
316
 
317
  # ---------------- INTERFAZ GR脕FICA CON GRADIO -------------------
318
 
@@ -321,18 +272,18 @@ with gr.Blocks(
321
  title="馃攳 Detector de Mixers / CoinJoin en Transacciones Bitcoin",
322
  css=".custom-btn { background-color: #ADD8E6 !important; color: black !important; padding: 5px 10px; font-size: 12px; }"
323
  ) as demo:
 
324
  gr.Markdown("# 馃攳 Detector de Mixers / CoinJoin en Transacciones Bitcoin")
325
  gr.Markdown("Desarrollado por Jos茅 R. Leonett para la comunidad de Peritos Forenses Digitales de Guatemala [www.forensedigital.gt](http://www.forensedigital.gt)")
326
  gr.Markdown("**Nota:** Este analizador funciona 煤nicamente con transacciones de Bitcoin. No se pueden analizar transacciones de Ethereum.")
327
 
328
  with gr.Row():
 
329
  with gr.Column(scale=1):
330
- plot_output = gr.Plot()
331
  tx_input = gr.Textbox(
332
  label="TXID de la Transacci贸n",
333
  placeholder="Ej: 9dd51e2d45f4f7bddcc3f0f7a05c3fd60543a11cfc9fbd0e1ca4434668cfa3e1"
334
  )
335
- analyze_btn = gr.Button("Analizar Transacci贸n", elem_classes=["custom-btn"])
336
  gr.Markdown("### Ejemplos de TXIDs v谩lidos:")
337
  gr.Examples(
338
  examples=[
@@ -341,11 +292,7 @@ with gr.Blocks(
341
  ],
342
  inputs=tx_input
343
  )
344
- with gr.Column(scale=1):
345
- reporte_html = gr.HTML()
346
- pdf_download_btn = gr.Button("GENERAR ANALISIS EN PDF", elem_classes=["custom-btn"])
347
- download_html = gr.HTML()
348
- # Se ampli贸 el scroll de explicaci贸n a 350px de alto para ver mejor el contenido
349
  explanation_box = gr.HTML(value="""
350
  <div style="overflow-y: auto; height: 350px; border: 1px solid #cccccc; padding: 10px;">
351
  <h4>Explicaci贸n de los campos:</h4>
@@ -357,8 +304,8 @@ with gr.Blocks(
357
  <li><strong>Peso:</strong> Peso de la transacci贸n en unidades de peso.</li>
358
  <li><strong>Fee rate:</strong> Tarifa pagada por byte (sat/byte).</li>
359
  <li><strong>Inputs:</strong> N煤mero de entradas de la transacci贸n.</li>
360
- <li><strong>Outputs:</strong> Se muestra solo si se detecta posible mixer.</li>
361
- <li><strong>Montos 煤nicos en outputs:</strong> Se muestra solo si es mixer.</li>
362
  <li><strong>Detalles de outputs 煤nicos:</strong> Lista de cada monto (en BTC) y las direcciones asociadas (solo si es mixer).</li>
363
  <li><strong>Total Entradas:</strong> Suma total de los valores de entrada (en BTC y USD).</li>
364
  <li><strong>Total Salidas:</strong> Suma total de los valores de salida (en BTC y USD).</li>
@@ -368,11 +315,24 @@ with gr.Blocks(
368
  </ul>
369
  </div>
370
  """)
 
 
 
 
 
371
 
372
  # Estado para almacenar el an谩lisis (tupla: (reporte, fig))
373
  analysis_state = gr.State()
374
 
 
375
  analyze_btn.click(fn=analizar_transaccion, inputs=tx_input, outputs=[reporte_html, plot_output, analysis_state])
376
- pdf_download_btn.click(fn=descargar_pdf, inputs=analysis_state, outputs=download_html)
 
 
 
 
 
 
 
377
 
378
  demo.launch()
 
7
  import os
8
  import tempfile
9
  import base64
10
+ from fpdf import FPDF, HTMLMixin # Estos m贸dulos ya no se usan en este ejemplo
11
 
12
  # ---------------- Funciones de an谩lisis y grafo -------------------
13
 
14
  def get_transaction(tx_id):
15
  """
16
+ Obtiene datos de una transacci贸n Bitcoin usando endpoints p煤blicos.
17
+ Se prueba primero con Blockstream.info y, de fallar, con mempool.space.
18
  """
19
  urls = [
20
  f"https://blockstream.info/api/tx/{tx_id}",
 
70
  def check_blockchain_tags(tx_id):
71
  """
72
  Consulta la API p煤blica de blockchain.com para ver si el TXID (o sus metadatos)
73
+ indica que la transacci贸n ha sido etiquetada como MIXER.
74
+ (Esta funci贸n es un ejemplo y puede requerir ajustes seg煤n la respuesta real.)
75
  """
76
  url = f"https://blockchain.info/rawtx/{tx_id}?format=json"
77
  try:
 
96
  - Calcula totales en BTC y fee, mostrando tambi茅n su equivalente en USD.
97
  - Muestra informaci贸n adicional (versi贸n, tama帽o, peso, fee rate).
98
  - Aplica heur铆stica para detectar posibles CoinJoin/mixers.
99
+ - Incorpora informaci贸n de blockchain.com (etiqueta MIXER).
100
  - Genera un grafo interactivo.
101
+ - Retorna un informe en HTML, la figura del grafo y una tupla (reporte, fig) que se guarda en el estado.
102
  """
103
  tx_data = get_transaction(tx_id)
104
  if not tx_data:
 
113
  total_output_value = sum(out.get("value", 0) for out in tx_data.get("vout", [])) / 1e8
114
  fee = total_input_value - total_output_value
115
 
116
+ # Heur铆stica para detectar mixer
117
  heuristic_mixer = ((num_inputs > 5 and num_outputs > 5 and montos_unicos < 3) or
118
  (num_outputs > 2 and montos_unicos <= 2))
119
  es_mixer_blockchain = check_blockchain_tags(tx_id)
 
247
  </p>
248
  </div>
249
  """
250
+ # Se devuelve adem谩s la tupla (reporte, fig) para almacenarla en el estado.
251
  return reporte, fig, (reporte, fig)
252
 
253
  except Exception as e:
254
  return f"鈿狅笍 Error durante el an谩lisis: {str(e)}", None, None
255
 
256
+ # ---------------- Funci贸n para mostrar el modal -------------------
257
 
258
+ def mostrar_modal(analysis_tuple):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  """
260
+ Funci贸n que, a partir de la tupla de an谩lisis, devuelve la actualizaci贸n para
261
+ abrir el modal y el contenido HTML del informe.
262
  """
263
  if not analysis_tuple:
264
+ return gr.update(visible=False), "<p>No hay an谩lisis generado.</p>"
265
+ report, _ = analysis_tuple
266
+ return gr.update(visible=True), report
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  # ---------------- INTERFAZ GR脕FICA CON GRADIO -------------------
269
 
 
272
  title="馃攳 Detector de Mixers / CoinJoin en Transacciones Bitcoin",
273
  css=".custom-btn { background-color: #ADD8E6 !important; color: black !important; padding: 5px 10px; font-size: 12px; }"
274
  ) as demo:
275
+ # Encabezado
276
  gr.Markdown("# 馃攳 Detector de Mixers / CoinJoin en Transacciones Bitcoin")
277
  gr.Markdown("Desarrollado por Jos茅 R. Leonett para la comunidad de Peritos Forenses Digitales de Guatemala [www.forensedigital.gt](http://www.forensedigital.gt)")
278
  gr.Markdown("**Nota:** Este analizador funciona 煤nicamente con transacciones de Bitcoin. No se pueden analizar transacciones de Ethereum.")
279
 
280
  with gr.Row():
281
+ # Columna izquierda: TXID, Ejemplos y Explicaci贸n
282
  with gr.Column(scale=1):
 
283
  tx_input = gr.Textbox(
284
  label="TXID de la Transacci贸n",
285
  placeholder="Ej: 9dd51e2d45f4f7bddcc3f0f7a05c3fd60543a11cfc9fbd0e1ca4434668cfa3e1"
286
  )
 
287
  gr.Markdown("### Ejemplos de TXIDs v谩lidos:")
288
  gr.Examples(
289
  examples=[
 
292
  ],
293
  inputs=tx_input
294
  )
295
+ # Recuadro de explicaci贸n (scroll ampliado a 350px)
 
 
 
 
296
  explanation_box = gr.HTML(value="""
297
  <div style="overflow-y: auto; height: 350px; border: 1px solid #cccccc; padding: 10px;">
298
  <h4>Explicaci贸n de los campos:</h4>
 
304
  <li><strong>Peso:</strong> Peso de la transacci贸n en unidades de peso.</li>
305
  <li><strong>Fee rate:</strong> Tarifa pagada por byte (sat/byte).</li>
306
  <li><strong>Inputs:</strong> N煤mero de entradas de la transacci贸n.</li>
307
+ <li><strong>Outputs:</strong> Se muestran solo si se detecta posible mixer.</li>
308
+ <li><strong>Montos 煤nicos en outputs:</strong> Se muestran solo si es mixer.</li>
309
  <li><strong>Detalles de outputs 煤nicos:</strong> Lista de cada monto (en BTC) y las direcciones asociadas (solo si es mixer).</li>
310
  <li><strong>Total Entradas:</strong> Suma total de los valores de entrada (en BTC y USD).</li>
311
  <li><strong>Total Salidas:</strong> Suma total de los valores de salida (en BTC y USD).</li>
 
315
  </ul>
316
  </div>
317
  """)
318
+ # Columna derecha: Grafo y Reporte
319
+ with gr.Column(scale=1):
320
+ plot_output = gr.Plot()
321
+ reporte_html = gr.HTML()
322
+ analyze_btn = gr.Button("Analizar Transacci贸n", elem_classes=["custom-btn"])
323
 
324
  # Estado para almacenar el an谩lisis (tupla: (reporte, fig))
325
  analysis_state = gr.State()
326
 
327
+ # Al hacer clic en "Analizar Transacci贸n", se actualizan el reporte, el grafo y se guarda el an谩lisis en el estado.
328
  analyze_btn.click(fn=analizar_transaccion, inputs=tx_input, outputs=[reporte_html, plot_output, analysis_state])
329
+
330
+ # Bot贸n para generar el an谩lisis en un modal emergente.
331
+ modal_btn = gr.Button("GENERAR ANALISIS", elem_classes=["custom-btn"])
332
+ with gr.Modal("Resultados del An谩lisis", visible=False) as result_modal:
333
+ modal_report = gr.HTML()
334
+
335
+ # La funci贸n mostrar_modal abrir谩 el modal con el contenido del an谩lisis.
336
+ modal_btn.click(fn=mostrar_modal, inputs=analysis_state, outputs=[result_modal, modal_report])
337
 
338
  demo.launch()