fix: check OP_RETURN en motor de análisis, banda BAJA forzada

This commit is contained in:
Aitor
2026-06-04 10:37:25 +02:00
parent dca3758c68
commit 46636bdba9
+27 -3
View File
@@ -1024,7 +1024,7 @@
input_reuse: 25, input_type_mixing: 14, output_type_mismatch: 8, input_reuse: 25, input_type_mixing: 14, output_type_mismatch: 8,
round_numbers: 10, rbf: 5, peeling: 8, round_numbers: 10, rbf: 5, peeling: 8,
unnecessary_input: 12, dust: 8, change_detection: 10, unnecessary_input: 12, dust: 8, change_detection: 10,
wallet_fingerprint: 6, batch_payment: 45, entity_ofac: 20, wallet_fingerprint: 6, batch_payment: 45, entity_ofac: 20, op_return: 30,
}; };
let deductions = 0; let deductions = 0;
@@ -1459,7 +1459,29 @@
}); });
if (isBatch) deductions += weights.batch_payment; if (isBatch) deductions += weights.batch_payment;
// ── 15. Timing analysis — informativo ──────────────────────────── // ── 15. OP_RETURN ─────────────────────────────────────────────────
// Detecta outputs OP_RETURN (nulldata): presencia y tamaño total en bytes.
// No muestra el contenido — lo relevante para privacidad es que hay datos
// arbitrarios, no qué dicen. Ver v1.2.1 del changelog.
const opReturnOuts = tx.vout.filter(v => isOpReturn(v));
const hasOpReturn = opReturnOuts.length > 0;
const opReturnBytes = opReturnOuts.reduce((s, v) => {
// scriptpubkey: "6a" + longitud (1 byte) + datos. Datos = (hex.length/2) - 2
const hex = v.scriptpubkey || "";
return s + Math.max(0, hex.length / 2 - 2);
}, 0);
checks.push({
id:"op_return", label:"Datos OP_RETURN", certainty:"CERTEZA", pass:!hasOpReturn,
actionability: "no_corregible",
detail: hasOpReturn
? `${opReturnOuts.length} output${opReturnOuts.length > 1 ? "s" : ""} OP_RETURN con ${opReturnBytes} bytes de datos arbitrarios. Revela que esta transacción usa un protocolo o servicio que escribe datos en la cadena (Stamps, Ordinals, OpenTimestamps, Omni, etc.). Se informa la presencia y el tamaño, no el contenido.`
: "No se detectan outputs OP_RETURN.",
didactic: "Un output OP_RETURN contiene datos arbitrarios que cualquiera puede leer. Su presencia revela el uso de un protocolo concreto y puede vincular esta transacción con actividad on-chain identificable. El contenido no se muestra — para la privacidad lo relevante es que existe, no qué dice.",
penalty: hasOpReturn ? weights.op_return : 0,
});
if (hasOpReturn) deductions += weights.op_return;
// ── 16. Timing analysis — informativo ────────────────────────────
let timingDetail = "No hay timestamp disponible (transacción pendiente o sin confirmar)."; let timingDetail = "No hay timestamp disponible (transacción pendiente o sin confirmar).";
if (tx.status?.block_time) { if (tx.status?.block_time) {
const date = new Date(tx.status.block_time * 1000); const date = new Date(tx.status.block_time * 1000);
@@ -1547,7 +1569,9 @@
// ── Score compuesto ─────────────────────────────────────────────── // ── Score compuesto ───────────────────────────────────────────────
const maxPossible = Object.values(weights).reduce((a,b)=>a+b,0); const maxPossible = Object.values(weights).reduce((a,b)=>a+b,0);
const score = Math.max(0, Math.min(100, Math.round(100 - (deductions / maxPossible) * 100))); const rawScore = Math.max(0, Math.min(100, Math.round(100 - (deductions / maxPossible) * 100)));
// OP_RETURN es incompatible con privacidad aceptable — fuerza banda BAJA como máximo.
const score = hasOpReturn ? Math.min(rawScore, 44) : rawScore;
return { return {
score, checks, score, checks,