La matemática pasa todos los vectores oficiales (RIPEMD-160, secp256k1, BIP32
vectores 1 y 2, bech32/BIP173) y no se ha tocado. Los fallos estaban en la
validación de la entrada:
- No se comprobaba la checksum del xpub: un carácter mal copiado generaba 200
direcciones ajenas y el usuario veía su cartera 'sin actividad'. Mismo
patrón de falso negativo silencioso que el resto de fallos de hoy.
- No se validaba la longitud (78 bytes) ni el formato de la clave pública.
- tpub se trataba como mainnet: la red se detectaba por prefijo de texto
('tb'/'u'/'v') y un tpub empieza por 't' pero no por 'tb'. Ahora se detecta
por bytes de versión, con las diez variantes.
- deriveChildPubkey aceptaba índices endurecidos, imposibles desde una clave
pública. No alcanzable desde la UI, pero debe defenderse sola.
Se añade tests/ con las cuatro baterías, documentando también qué NO cubren:
no sustituyen una auditoría externa.
43 lines
2.0 KiB
JavaScript
43 lines
2.0 KiB
JavaScript
const { B32, SECP, deriveChildPubkey, deriveAddresses } = require('/tmp/cripto/crypto.js');
|
|
let ok=0, ko=0;
|
|
const debeFallar = async (nombre, fn) => {
|
|
try { await fn(); console.log(` ✗ ${nombre} — NO falló`); ko++; }
|
|
catch(e){ console.log(` ✓ ${nombre}\n → "${e.message}"`); ok++; }
|
|
};
|
|
(async () => {
|
|
const bueno = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8";
|
|
|
|
console.log("=== los cuatro hallazgos, revisados ===\n");
|
|
await debeFallar("checksum rota (un carácter cambiado)", () =>
|
|
deriveAddresses(bueno.slice(0,-1) + (bueno.slice(-1)==="8"?"9":"8"), 2));
|
|
await debeFallar("xpub truncado", () => deriveAddresses("xpub661MyMwAqRbcFtXgS5sYJ", 2));
|
|
await debeFallar("índice endurecido", async () => {
|
|
const raw = await B32.decodeBase58Check(bueno);
|
|
return deriveChildPubkey(raw.slice(45,78), raw.slice(13,45), 0x80000000);
|
|
});
|
|
await debeFallar("índice negativo", async () => {
|
|
const raw = await B32.decodeBase58Check(bueno);
|
|
return deriveChildPubkey(raw.slice(45,78), raw.slice(13,45), -1);
|
|
});
|
|
|
|
console.log("\n=== el xpub bueno sigue funcionando ===");
|
|
const a = await deriveAddresses(bueno, 3);
|
|
console.log(" recepción:", a.receive.join(", "));
|
|
console.log(" huella:", a.fingerprint);
|
|
const bien = a.receive.every(x=>x.startsWith("bc1q")&&x.length===42);
|
|
console.log(` ${bien?'✓':'✗'} formato correcto`); bien?ok++:ko++;
|
|
|
|
console.log("\n=== testnet: tpub ahora se detecta bien ===");
|
|
|
|
// vector 1 del BIP32 con los bytes de versión de testnet — dato público, no de nadie
|
|
const tp = "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp";
|
|
try {
|
|
const t = await deriveAddresses(tp, 2);
|
|
const esTb = t.receive.every(x=>x.startsWith("tb1q"));
|
|
console.log(` ${esTb?'✓':'✗'} genera direcciones de testnet: ${t.receive[0]}`);
|
|
esTb?ok++:ko++;
|
|
} catch(e){ console.log(" ✗ falló:", e.message); ko++; }
|
|
|
|
console.log(`\nRESULTADO: ${ok} correctas, ${ko} incorrectas`);
|
|
})();
|