[Análisis técnico] Tonalli Wallet y P2SH: cómo funciona una bóveda 2-de-3 en eCash

Xoloitzcuintle custodia una bóveda digital P2SH con un esquema multifirma 2-de-3 de Tonalli Wallet sobre eCash.

El código público de Tonalli Wallet contiene un flujo experimental para bóvedas P2SH m-de-n con XEC puro. En una configuración 2-de-3, cada input requiere al menos dos firmas válidas para poder transmitirse. La arquitectura reduce la dependencia de una sola llave, pero todavía exige coordinación manual, carece de pruebas funcionales automatizadas dedicadas al flujo y no presenta una auditoría de seguridad independiente publicada.

El riesgo de depender de una sola llave

En una wallet de firma única, una sola llave privada puede autorizar un movimiento. Esa sencillez ayuda en el uso cotidiano, pero concentra el riesgo: perder la semilla sin respaldo, exponer la llave a malware o comprometer el dispositivo puede bastar para perder el acceso o permitir un gasto no deseado.

Una wallet multifirma cambia la condición de autorización. En vez de aceptar una sola firma, utiliza una regla m-de-n:

  • n es el total de llaves públicas autorizadas.
  • m es el mínimo de firmas válidas requerido para gastar.

La multifirma no elimina las llaves privadas ni sustituye los respaldos. Distribuye la autoridad entre varios firmantes y hace que cada input de la transacción cumpla un umbral. La documentación de Tonalli describe bóvedas P2SH m-de-n y usa 2-de-3 como ejemplo operativo. Documentación primaria, líneas 18-32

Qué significa una bóveda 2-de-3

Una bóveda 2-de-3 tiene tres firmantes -A, B y C- y acepta cualquiera de estas combinaciones:

  • A + B.
  • A + C.
  • B + C.

Una sola llave no puede gastar. Si A pierde su dispositivo, B y C todavía pueden alcanzar el umbral. Si un atacante obtiene únicamente B, tampoco puede mover los fondos.

El límite matemático también es claro: perder dos llaves puede bloquear permanentemente una bóveda 2-de-3; comprometer dos firmantes puede permitir un gasto no autorizado. La tolerancia depende de que semillas, dispositivos, personas y ubicaciones sean realmente independientes.

Capacidad observada en el código

La revisión técnica se fijó en el commit 9cce6d516a3dd498a9ab8a4f9cf7a38fcc4769fc de xolosArmy/RMZWallet. La multifirma fue introducida originalmente por el commit ecdca3a1cf6d9ad8a12497aa0754e1a9c26862df.

En ese árbol puede observarse lo siguiente:

Esto describe una capacidad presente en el código. No prueba por sí mismo que todos los escenarios, combinaciones m-de-n, navegadores, dispositivos o recuperaciones hayan sido ensayados.

Qué significa P2SH en eCash

P2SH significa Pay to Script Hash: pagar al hash de un script. Los XEC no se bloquean directamente a una sola llave pública, sino a una dirección que representa el hash de una condición.

En una bóveda 2-de-3, la condición se puede representar así:

OP_2 <llave A> <llave B> <llave C> OP_3 OP_CHECKMULTISIG

Antes del primer gasto, la salida P2SH publica el hash del script, no la lista completa de firmantes. Al gastar, el input revela el redeemScript y con él las llaves públicas y el umbral. La pareja de transacciones verificada en mainnet muestra ese comportamiento: el fondeo contiene OP_HASH160 <hash> OP_EQUAL y el gasto revela el script 2-de-3. Fondeo en el explorador Gasto en el explorador

Wallet personal P2PKH y bóveda P2SH: dos espacios distintos

La implementación separa:

  1. Wallet personal P2PKH. Su firmante se construye con la llave privada y la llave pública de la wallet activa. La semilla cifrada se conserva en almacenamiento local. Servicio personal, líneas 230-260 Cifrado local, líneas 457-500 Firmante P2PKH, líneas 957-974
  2. Bóveda P2SH. Conserva su dirección, UTXO y datos públicos bajo claves de almacenamiento separadas. Los fondos de la bóveda solo pueden gastarse cuando cada input reúne el umbral. Tipos y almacenamiento de bóvedas, líneas 35-98

La separación no significa que Tonalli cree automáticamente otra semilla para cada bóveda. La llave de la wallet personal activa funciona como una de las llaves firmantes. Por eso la guía y la interfaz establecen la regla un dispositivo, una wallet, un firmante. Guía, líneas 34-46 Interfaz de firma, líneas 87-103

Información pública de la bóveda y secretos que nunca deben compartirse

El JSON exportado contiene la etiqueta, m, n, llaves públicas, redeemScriptHex, scriptHashHex, dirección P2SH y fecha. No incluye la semilla ni la llave privada. Exportación, líneas 613-627

Al importar, el servicio vuelve a ordenar las llaves y reconstruye el script, su hash y la dirección. Rechaza diferencias y también rechaza a una wallet cuya llave pública no pertenezca al grupo. Importación y validación, líneas 629-681

Se pueden compartir entre firmantes:

  • m y n.
  • Llaves públicas.
  • redeemScriptHex y scriptHashHex.
  • Dirección P2SH.
  • JSON público de la bóveda.
  • partialTxHex de una operación concreta, después de verificar su integridad.

Nunca deben compartirse:

  • Frase semilla.
  • Llave privada.
  • Contraseña de cifrado.
  • Respaldos sin protección.

Una llave pública no autoriza un gasto, pero puede revelar relaciones entre firmantes. El JSON es compartible para coordinar la bóveda; no necesariamente conviene publicarlo junto con nombres y responsabilidades.

Flujo observado: crear, firmar y transmitir

1. Preparar firmantes independientes

Cada participante desbloquea una wallet distinta en un dispositivo distinto, abre /multisig/create y comparte únicamente su llave pública. La pantalla incorpora la llave pública de la wallet activa y envía m y la lista al servicio de creación. Ruta de creación, líneas 14-48

2. Crear la bóveda

El servicio ordena las llaves, comprueba el umbral, exige que la wallet creadora sea firmante, construye el redeemScript, calcula HASH160 y deriva la dirección P2SH. Servicio, líneas 560-600

3. Exportar e importar el JSON público

Los demás dispositivos importan el JSON. Cada uno debe obtener la misma dirección P2SH y confirmar el dato por un canal independiente antes de fondear. La guía documenta el procedimiento y el código vuelve a derivar los campos críticos. Guía, líneas 66-82

4. Fondear con XEC puro

La ruta específica de la bóveda usa la wallet personal como fuente, descarta UTXO con tokens, construye el envío a P2SH y transmite mediante Chronik. Servicio de fondeo, líneas 705-788 La documentación advierte no usar el flujo general /send-xec para este fondeo. Guía, líneas 84-101

5. Crear una propuesta parcial

Un firmante indica destino, monto, una comisión de servicio opcional y, si corresponde, un memo OP_RETURN. El servicio selecciona XEC puro, devuelve el cambio a la bóveda, firma con el creador y produce partialTxHex. Creación de propuesta, líneas 791-885

En 2-de-3, esa propuesta normalmente queda con una firma y necesita otra. La guía documenta ese estado, pero no sustituye una prueba funcional automatizada. Guía, líneas 103-115

6. Inspeccionar y cofirmar

El segundo firmante pega el partialTxHex, solicita el resumen y revisa inputs, firmas válidas, destinos, montos, cambio, comisión, memo y scripts desconocidos. La interfaz no habilita “Agregar mi firma” sin un resumen y no habilita “Transmitir” mientras summary.isComplete sea falso. Interfaz de firma, líneas 34-85 Controles, líneas 105-166

El servicio verifica las firmas por llave pública y por input, rechaza firmas inválidas o duplicadas y exige ALL_BIP143. Verificación criptográfica, líneas 331-428

7. Transmitir y verificar

Antes del broadcast, el servicio vuelve a inspeccionar la transacción, bloquea UTXO con token, firmas inválidas o duplicadas y cualquier input por debajo de m; después llama a Chronik. Broadcast, líneas 962-995

Después de transmitir, los firmantes deben abrir el TXID y comprobar destino, monto, cambio, comisión y confirmación. Guía, líneas 172-195

Comportamiento probado: qué significan realmente las 171 pruebas

La cifra 171 no corresponde a 171 pruebas de multifirma. Es únicamente la suma aritmética de resultados pertenecientes a dos ejecutores y áreas distintas:

  • Vitest: 167 de 167 pruebas aprobadas en 15 archivos, ejecutadas con npx vitest run --exclude src/services/slpNftTxBuilder.test.ts.
  • Ejecutor de pruebas de Node con el cargador tsx: 4 de 4 pruebas aprobadas en un archivo sobre construcción SLP NFT1 Group, ejecutadas con node --import tsx --test src/services/slpNftTxBuilder.test.ts.

El script de pruebas del repositorio confirma que el proyecto separa ambos ejecutores. package.json, líneas 6-16

Confirmación expresa: no se localizaron pruebas funcionales, criptográficas o de integración dedicadas a EcashMultisigService, CreateVault, VaultDashboard, CreateProposal o SignProposal. Las únicas apariciones de /multisig en archivos de prueba comprueban el estado activo de la navegación y el catálogo de rutas. Prueba de navegación, líneas 60-90 Prueba de navegación, líneas 148-160

Una primera corrida de Vitest realizada simultáneamente con el ejecutor SLP registró 165 aprobadas y dos expiraciones de cinco segundos en WcWallet.test.ts; al repetir Vitest de forma aislada, las 167 pasaron. Los dos casos eran de WalletConnect, no de multifirma. Esta variación aconseja no usar la suite general como prueba de madurez de la bóveda.

El typecheck y el build terminaron con código de salida 0 en el commit revisado. El build generó advertencias de empaquetado y tamaño, pero no errores de compilación. Esto prueba compilabilidad del árbol bajo el entorno registrado; no prueba corrección de seguridad.

Evidencia en mainnet: una operación pública 2-de-3

La documentación registra una prueba realizada el 24 de junio de 2026. Registro primario, líneas 237-246 La revisión final contrastó ese registro con Chronik y con el explorador de eCash.

Identificadores

Fondeo P2SH

Chronik devuelve una salida de 110,000 satoshis, equivalentes a 1,100.00 XEC, con este outputScript:

a91472ca722698016accd0198c0d1a65ae2ca2026c0a87

La forma decodificada es:

OP_HASH160 72ca722698016accd0198c0d1a65ae2ca2026c0a OP_EQUAL

La salida aparece como gastada por el TXID multifirma indicado.

Estructura del scriptSig

El input del gasto contiene cuatro elementos:

OP_0 <firma 1> <firma 2> <redeemScript>

  • Firma 1: 71 bytes; último byte 0x41.
  • Firma 2: 72 bytes; último byte 0x41.
  • 0x41 corresponde al tipo utilizado por el código como ALL_BIP143.

El OP_0 inicial es el elemento vacío requerido por la semántica histórica de OP_CHECKMULTISIG. La estructura coincide con la que construye y valida el servicio. Constructor y parser de scriptSig, líneas 263-308

RedeemScript exacto

522102669b1ee6cc4596ff4b486f35518f2ec24703d17cb1799f1cc44c9408c0731710
2102aba83b38996d54e6183199123d88023d5adf677cadc962b0db2f13187efce0b9
2102b7c6d1350ea9a289efcb7e96b4b25d8aed3849a76dd50901aa8ec4971f075c46
53ae

Sin saltos de línea:

522102669b1ee6cc4596ff4b486f35518f2ec24703d17cb1799f1cc44c9408c07317102102aba83b38996d54e6183199123d88023d5adf677cadc962b0db2f13187efce0b92102b7c6d1350ea9a289efcb7e96b4b25d8aed3849a76dd50901aa8ec4971f075c4653ae

Decodificado:

OP_2
<02669b1ee6cc4596ff4b486f35518f2ec24703d17cb1799f1cc44c9408c0731710>
<02aba83b38996d54e6183199123d88023d5adf677cadc962b0db2f13187efce0b9>
<02b7c6d1350ea9a289efcb7e96b4b25d8aed3849a76dd50901aa8ec4971f075c46>
OP_3
OP_CHECKMULTISIG

El HASH160 calculado del redeemScript es:

72ca722698016accd0198c0d1a65ae2ca2026c0a

Coincide exactamente con el hash del outputScript fondeado y deriva la dirección P2SH publicada.

Montos y comisión

  • Input: 110,000 satoshis = 1,100.00 XEC.
  • Salida a destino: 10,000 satoshis = 100.00 XEC.
  • Cambio a la misma bóveda P2SH: 99,400 satoshis = 994.00 XEC.
  • Comisión de red: 600 satoshis = 6.00 XEC.

Método reproducible de verificación

La consulta se realizó con chronik-client contra el endpoint predeterminado del repositorio y el script se decodificó con ecash-lib:

node --input-type=module -e "import { ChronikClient } from 'chronik-client'; import { Script, fromHex, isPushOp, shaRmd160, toHex, Address } from 'ecash-lib'; const txid='3dec057346e0e31e82e40ef5802ef179bd02441e6eb3ccc94d3f6fe0f5319329'; const tx=await new ChronikClient('https://chronik.e.cash').tx(txid); const read=s=>{const a=[];const it=s.ops();for(let op=it.next();op!==undefined;op=it.next())a.push(op);return a}; const all=read(new Script(fromHex(tx.inputs[0].inputScript))); const pushes=all.filter(isPushOp); const redeem=new Script(pushes.at(-1).data); const hash=shaRmd160(redeem.bytecode); const fee=BigInt(tx.inputs[0].sats)-tx.outputs.reduce((n,o)=>n+BigInt(o.sats),0n); console.log(JSON.stringify({block:tx.block.height,scriptSigOps:all.length,signatures:pushes.length-1,sighash:pushes.slice(0,-1).map(x=>'0x'+x.data.at(-1).toString(16)),redeemScriptHex:redeem.toHex(),hash160:toHex(hash),address:Address.fromScript(Script.p2sh(hash)).toString(),feeSats:fee.toString()},null,2));"

Resultado: bloque 954927, cuatro operaciones en scriptSig, dos firmas, ambos bytes de sighash 0x41, HASH160 72ca...6c0a, dirección ecash:ppev...mux7f y comisión de 600 satoshis.

Qué demuestra esta operación

  • Que una salida P2SH 2-de-3 fue fondeada en eCash mainnet.
  • Que esa salida fue gastada con dos firmas y el redeemScript esperado.
  • Que existe al menos una ejecución pública compatible con el flujo documentado de Tonalli Wallet.

Qué no demuestra esta operación

  • Que todas las configuraciones m-de-n funcionen en todos los casos.
  • Que exista cobertura de recuperación, concurrencia, múltiples inputs, navegadores o dispositivos.
  • Que la función haya sido sometida a una auditoría de seguridad.
  • Que haya una campaña de pruebas automatizadas de multifirma.
  • Que tokens, NFT, RMZ o ALP estén soportados.
  • Que agentes o DAO tengan una integración autónoma terminada.

No se encontró en docs ni en src evidencia multifirma equivalente para testnet, regtest o mocknet. La única red nombrada en el registro específico es mainnet. Esta es una ausencia en el árbol revisado, no una afirmación sobre pruebas privadas no publicadas.

Tres casos prácticos

Protección personal con respaldo distribuido

Una persona conserva una llave principal, una segunda llave en otro dispositivo y una tercera bajo custodia de alguien de confianza. En 2-de-3, perder un dispositivo no bloquea necesariamente los fondos y la persona de confianza no puede gastar por sí sola.

La ventaja desaparece si dos dispositivos comparten la misma semilla, si todos los respaldos están en el mismo lugar o si el mismo malware puede comprometer dos firmantes.

Pagos de una empresa

Una empresa asigna tres firmantes: dirección, administración y operaciones. Todo pago necesita dos autorizaciones. Quien prepara una transferencia no puede ejecutarla unilateralmente y la empresa no depende de la disponibilidad permanente de una sola persona.

La bóveda no valida facturas ni proveedores. La organización todavía necesita reglas para revisar destino, monto, cambio, comisión y sustitución de firmantes.

Tesorería comunitaria

Una comunidad reparte tres llaves entre responsables y exige dos firmas para cada gasto. Ninguna persona puede mover unilateralmente los XEC.

El umbral criptográfico no demuestra que la decisión comunitaria haya sido legítima. Actas, votaciones y controles de gobernanza siguen siendo una capa organizativa distinta.

Ventajas razonables, no garantías

Menor dependencia de un único punto de fallo

Una sola llave perdida o comprometida no alcanza el umbral en 2-de-3. Es la ventaja central del esquema.

Tolerancia a la pérdida de un dispositivo

Si las otras dos llaves permanecen disponibles, todavía existe una vía de gasto. Para que sea real, las llaves deben tener semillas y respaldos independientes.

Administración compartida

Familias, empresas, colectivos y DAO pueden traducir una regla organizativa -“dos personas deben aprobar”- en una condición verificable por la red.

Separación de responsabilidades

Una persona puede preparar la propuesta, otra revisar los datos y una tercera actuar como respaldo. La separación reduce el poder unilateral, pero no reemplaza los procedimientos internos.

Distribución entre personas, equipos y lugares

Separar llaves ayuda frente a robo, incendio o pérdida local. También aumenta la complejidad y el tiempo de coordinación.

Mayor control de tesorerías

El valor no está en volver imposible un robo, sino en exigir que fallen o sean engañados varios controles independientes.

Posibilidades futuras: agentes autónomos, DAO y ADDLP

Una bóveda multifirma puede limitar el poder de gasto de un agente o de un sistema automatizado: el software podría preparar una propuesta y otro firmante independiente decidir si la autoriza. Es una arquitectura razonable para separar proponer de ejecutar.

Sin embargo, la evidencia revisada muestra un flujo manual basado en JSON y partialTxHex. No se comprobó una API de coordinación autónoma, un motor de políticas para agentes ni una integración DAO terminada. La guía menciona ADDLP futuro como “puede coordinar más adelante” y advierte que el memo genérico no sustituye una revisión específica de compatibilidad. Guía, líneas 117-129

Por ello, agentes, DAO y ADDLP deben presentarse como usos arquitectónicos posibles o futuros, no como capacidades terminadas.

Limitaciones y riesgos conocidos

La multifirma no es seguridad absoluta. No protege por sí sola contra:

  • Pérdida simultánea de suficientes llaves. En 2-de-3, perder dos puede bloquear los fondos.
  • Compromiso de dos o más firmantes. Dos llaves comprometidas alcanzan el umbral.
  • Firma de una transacción incorrecta. Dos firmas válidas también pueden autorizar un destino equivocado.
  • Respaldos mal protegidos. Guardar varias semillas en la misma nube, correo o caja reduce la independencia.
  • Ingeniería social. Un atacante puede engañar a más de un firmante.
  • Malware o dispositivos comprometidos. Una interfaz o portapapeles alterado puede cambiar direcciones y montos.
  • Configuración incorrecta. Un umbral sin margen de recuperación puede volver la bóveda frágil.
  • Falta de coordinación. Ausencia, incapacidad o pérdida de respaldos puede impedir alcanzar el quorum.
  • Propuestas obsoletas. Si un UTXO cambia de estado, puede ser necesario generar otra propuesta.

Límites específicos del commit revisado:

  • Función marcada como experimental.
  • Solo XEC puro; no UTXO con tokens.
  • Coordinación manual entre firmantes.
  • Bóvedas y propuestas guardadas localmente en el navegador. Tipos y claves de almacenamiento, líneas 35-98
  • Sin pruebas funcionales automatizadas dedicadas al flujo multifirma.
  • Sin evidencia localizada de pruebas específicas en testnet, regtest o mocknet.
  • Una sola ejecución pública 2-de-3 localizada en mainnet.
  • Scripts desconocidos generan advertencia, pero el código de broadcast no muestra un bloqueo general por cualquier output desconocido; la revisión humana sigue siendo esencial. Inspector de outputs, líneas 484-534 Broadcast, líneas 972-995

Ausencia de auditoría independiente publicada

No se localizó una auditoría de seguridad independiente publicada en el repositorio, su documentación ni la búsqueda del repositorio al 31 de julio de 2026. Esto no permite descartar una revisión privada no publicada; sí impide describir la función como auditada.

Recomendaciones prácticas

  1. Empieza con poco XEC y completa un ciclo de fondeo, propuesta, cofirma, transmisión y recuperación.
  2. Usa una semilla distinta por firmante.
  3. Separa dispositivos, personas y ubicaciones.
  4. Respalda cada semilla fuera de línea.
  5. Conserva el JSON público de la bóveda en más de un medio.
  6. Compara la dirección P2SH por un canal independiente antes de fondear.
  7. Revisa destino, monto, cambio, comisión, memo y scripts en cada dispositivo.
  8. No firmes advertencias que no entiendas.
  9. No envíes tokens a la bóveda experimental.
  10. Exige auditoría, pruebas específicas y ejercicios de recuperación antes de custodiar montos relevantes.

Conclusión

La multifirma atiende un problema real: que toda una tesorería dependa de una sola llave y de una sola persona. En una bóveda 2-de-3, una sola pérdida o un solo compromiso no basta para autorizar un gasto. Esa propiedad puede servir para respaldo personal, pagos empresariales y tesorerías comunitarias.

Tonalli Wallet tiene código público para crear, importar, fondear, inspeccionar, cofirmar y transmitir bóvedas P2SH m-de-n, y existe una operación 2-de-3 verificable en eCash mainnet. Es evidencia técnica concreta.

La proporción también importa: el proyecto marca la función como experimental, limita el MVP a XEC puro, depende de coordinación manual, no incluye pruebas funcionales automatizadas dedicadas a la multifirma y no presenta una auditoría independiente publicada.

La conclusión no es que la multifirma garantice seguridad. Es que distribuye la autoridad y reduce ciertos riesgos cuando llaves, dispositivos, respaldos y procedimientos también son independientes.

Fuentes primarias consultadas

Metodología de verificación

La revisión se fijó en xolosArmy/RMZWallet commit 9cce6d516a3dd498a9ab8a4f9cf7a38fcc4769fc y contrastó la guía docs/ecash-multisig-mvp.md, los servicios EcashMultisigService.ts, XolosWalletService.ts y ChronikClient.ts, las rutas de /multisig, package.json y el inventario de pruebas. Se ejecutaron npm run typecheck, npm run build, Vitest con 167 casos y el archivo SLP con el ejecutor de Node y tsx, además de búsquedas rg para localizar cobertura multifirma, entornos de prueba y auditorías publicadas. La evidencia on-chain se comprobó con chronik-client, ecash-lib y explorer.e.cash, reconstruyendo el scriptSig, el redeemScript, su HASH160, la dirección P2SH, los montos y la comisión. Como artefactos editoriales se revisaron el borrador original, la revisión probatoria aceptada y el Manual periodístico de eCash Magazine México. El detalle completo de afirmaciones, comandos, resultados y niveles de confianza se conserva en el anexo probatorio separado.