Monedero
Claves, direcciones y envíos — más el contrato de comportamiento que todo monedero de este protocolo tiene que cumplir. Cada regla describe una forma de perder dinero que el protocolo permite, y que por tanto un monedero debe impedir.
Los comandos#
zcd wallet new --out KEYFILE # create an encrypted key file
zcd wallet address --key KEYFILE # show an address
zcd wallet balance --key KEYFILE # ask a node for a balance
zcd wallet send --key KEYFILE --to ADDR --amount N
zcd wallet sweep --key KEYFILE --to ADDR --one-shot
zcd wallet retire --key KEYFILE [--addr ADDR]
zcd ui --key KEYFILE # the same wallet in a browser
El monedero gráfico no es una segunda implementación. zcd wallet, zcd
ui y la aplicación de escritorio son tres interfaces sobre un solo
wallet/session: cada una construye ahí su certificado, y las comprobaciones de las reglas corren
dentro de esa llamada. Un monedero gráfico es por tanto estructuralmente incapaz de ser más permisivo
que la línea de comandos — no porque quien lo escribió tuviera cuidado, sino porque no hay una segunda ruta
de código en la que pudiera serlo.
Dos tipos de dirección#
| Versión | Tipo | Úsala para | ¿Reutilizable? |
|---|---|---|---|
0x01 | De un solo uso | Un único pago esperado. Deriva una nueva por cada pago que recibas. | No. Un débito la quema permanentemente. |
0x02 | Persistente | Comercios, direcciones de donación, monederos calientes, pagos de minería. | Sí, para siempre. Nunca puede entrar en el registro de gastadas. |
Un solo uso frente a persistente es una política del monedero expuesta como un bit de la dirección, no dos
libros mayores. Los monederos usan 0x01 por defecto para cada pago recibido, porque es lo que hace que un
pago no sea vinculable con el siguiente; quien quiera una cuenta usa 0x02.
Las ocho reglas#
Toda regla de abajo describe una forma de perder dinero o comisiones que el protocolo permite — deliberadamente, porque la alternativa era peor — y que por tanto un monedero debe impedir. El monedero de referencia las implementa. No se limita a documentarlas.
Regla 1 — barre celdas enteras#
Al gastar desde una dirección 0x01, mueve el saldo entero
— al beneficiario y a una dirección de cambio que controles. El certificado lleva un
MARK_SPENT de esa dirección, y después de que se aplique, toda lectura y escritura bajo la
dirección falla de forma permanente. No hay segunda transacción.
La cadena ahora suaviza esto: lo que una dirección quemada siga conteniendo se mueve a la propia
celda RefundTo del certificado al confirmarse — tanto para el saldo nativo de la dirección como para cada
celda que el propio certificado nombre. Así que barrer de menos ya no destruye los restos; se los entrega a
tu dirección de cambio.
Un certificado tiene un solo RefundTo y puede quemar direcciones de un solo uso pertenecientes a
varios firmantes, así que un residuo bajo tu celda puede entregarse a la de ellos.
Nunca cofirmes un certificado que queme una dirección de un solo uso tuya y reembolse a una
dirección que no controlas.
Lo que esta regla no puede salvar: un saldo en un activo que el certificado nunca menciona. Los activos bajo una dirección no son derivables de un slot, así que alcanzar un activo no nombrado significaría recorrer toda la tabla de celdas en la única etapa que no se paraleliza. Nombra todos los activos en el certificado que quema la dirección.
Regla 2 — RefundTo debe ser una dirección que aún puedas usar#
Debe nombrar o bien una dirección persistente que controles o bien una dirección de un solo uso
nueva que este certificado no queme. Depositarlo en una celda quemada deja varado el
resto: el fold lo quema en lugar de escribirlo en una celda que nadie puede leer, y lo informa como
refund_burned en lugar de refunded, de modo que un monedero que concilie un saldo pueda
darse cuenta.
Regla 3 — una dirección, un pago esperado#
Dos obligaciones, una en cada lado:
- Al recibir. Deriva una dirección
0x01nueva por cada pago que esperes. Nunca publiques una dos veces. No barras ni retires una dirección que hayas divulgado en los últimosttl_maxbloques salvo que aceptes que un pago en vuelo hacia ella sufrirá una omisión y se le cobrará a quien lo envió. - Al enviar. Niégate a pagar a una dirección de un solo uso que puedas ver que ya se ha abonado o gastado. Si un beneficiario te da una dirección por segunda vez, trátalo como un error, no como una comodidad.
Para cualquier cosa que se pague más de una vez — un comercio, una dirección de donaciones, un pago de
minería — usa una dirección persistente (0x02). Esta es la única
regla que elimina la exposición en lugar de acotarla.
Regla 4 — cadenas dependientes: confirma, o acepta el riesgo#
Incrementa Seq por cada certificado que dependa de uno anterior. Dentro de un mismo
bloque el fold confirma los certificados de un firmante en orden de Seq, así que una cadena dependiente en
el mismo bloque es segura. Entre bloques no lo es: difunde Seq = n+1
solo después de que Seq = n haya confirmado, o acepta que el dependiente puede confirmarse solo
y sufrir una omisión. Esto no es un defecto del protocolo — el firmante aceptó el riesgo de obsolescencia al firmar.
Regla 5 — fija el máximo con generosidad y la prioridad con honestidad#
Cada mercado toma dos precios. El máximo es una cota de solvencia: en cuanto la comisión base lo supera, el certificado es inincluible y hay que volver a firmarlo. La prioridad es lo que se le paga de verdad a un minero.
- Subir el máximo no cuesta nada en comisiones — el colchón de seguridad es gratis.
- Pero el depósito reserva
gas × max, así que el máximo acota cuánto saldo queda bloqueado por un paso del fold. Dimensiónalo contra el saldo realmente disponible. - Un certificado con un TTL largo necesita más holgura que uno con un TTL corto, porque la comisión base tiene más bloques en los que moverse.
La vía de escape para saldos pequeños es reducir la ventana, no la seguridad: firma con un TTL corto y vuelve a firmar al caducar, cambiando inmovilización por latencia. La respuesta equivocada es mantener el TTL largo y reducir el máximo, lo que hace que el certificado quede varado justo cuando el mercado se mueve — el caso para el que existía el margen.
Regla 6 — ordena los movimientos de forma canónica#
El protocolo no impone ningún orden a los movimientos de una transferencia, así que sin un orden canónico un reintento del
mismo pago lógico produce un id distinto y el monedero no puede distinguir "ya enviado" de
"enviado dos veces". wallet.Transfer ordena por activo, origen, destino y luego importe, de modo que un
reintento reproduce el id.
Un reintento no tiene que reproducir la firma: la preimagen del id excluye la lista de firmas, así que un reintento vuelto a firmar con un nonce nuevo es el mismo id y la red lo rechaza como el duplicado que es. La idempotencia es una propiedad que provee el protocolo, para todo monedero y no solo para los cuidadosos.
Regla 7 — nunca entregues una semilla#
La semilla es la clave. Cualquiera que la lea es dueño de todo lo que contienen sus dos direcciones — la de un solo uso y la persistente, que no guardan relación en la cadena pero se derivan de la misma clave.
Los archivos de clave están siempre cifrados: Argon2id sobre la frase de paso, AES-256-GCM sobre la semilla, ambos con sus parámetros guardados en el archivo para que un usuario que se quede fuera de este binario pueda recuperarla con la biblioteca estándar de cualquier lenguaje. No hay ningún flag para escribir uno sin cifrar. Las frases de paso se leen de la terminal sin eco, nunca de un flag — una frase de paso en una línea de comandos está en el historial del intérprete y en la tabla de procesos.
La escritura sostiene dos propiedades a la vez, y ambas tratan de la misma frase — perder una es perder dinero:
- Nunca sobrescribir en silencio. Un archivo de clave que ya esté en el destino se deja intacto y la escritura falla.
- Nunca dejar uno a medias. La semilla va a un archivo temporal en el mismo directorio, allí se hace fsync, y solo entonces se publica con su nombre definitivo en una única operación del sistema de archivos.
Ambas se cumplen en todos los sistemas de archivos contra los que se midió la CLI, FAT32 y exFAT incluidos — los formatos que es más probable que use una copia de seguridad USB en frío.
FAT32 y exFAT no tienen bits de permisos de Unix, así que el 0600 con el que se crea un archivo de clave
no sobrevive en ellos — deciden las opciones de montaje. En Linux, el fmask por defecto
suele dejarlo legible para todo el mundo; macOS monta esos volúmenes como
noowners, que informa de 0700 y no impone nada. Elige la frase de paso
en consecuencia. Tampoco tienen diario, así que si una publicación se interrumpe en FAT e informa de un fallo,
comprueba el destino antes de volver a ejecutarla.
Regla 8 — rechaza lo que la red va a rechazar#
Un monedero que informa de éxito para algo que la red solo puede descartar ha movido el fallo a un sitio donde el usuario nunca mirará. Dos formas de esto, ambas encontradas sobre el terreno:
- Bytes que ningún par puede decodificar. El códec es una autoridad por derecho propio, con reglas que el validador no repite. El constructor ahora afirma la propiedad directamente: lo que emita, el decodificador lo acepta.
- Una transferencia que el fold solo puede omitir. Una transferencia por encima del saldo del origen no rompe ninguna regla, así que todos los nodos la admiten — y nada rechaza a un productor que la incluya de todos modos, momento en que el fold la liquida a
skip_fee, quemada del depósito y pagada a nadie. El mal caso no es "sin efecto", es "la comisión se quemó y el valor nunca se movió".
zcd wallet send --force salta esa única negativa y ninguna otra,
porque un depósito que se espera que aterrice dentro de la ventana del TTL hace que el mismo certificado se aplique y el
monedero no puede ver el futuro. No puede hacer válido un certificado inválido; solo puede enviar uno
válido que puede sufrir una omisión — y una omisión no es gratis.
Confiar en un nodo que no ejecutas tú#
zcd y el monedero no son nodos completos. Creen lo que un nodo les dice, y una
CLI que habla RPC con un nodo que ella misma no valida tiene que confiar en algo sobre
las respuestas de ese nodo — eso no es un fallo que corregir, es lo que un monedero es. Tres mitigaciones,
cada una para una mentira concreta:
| Mitigación | Qué detecta |
|---|---|
--devnet / --testnet / --params | El operador afirma para qué red pretende firmar, y cada nodo consultado se contrasta con esa afirmación. La identidad de la red nunca se toma de un nodo por sí solo. Esto se aplica también a balance, no solo a los comandos que firman. |
--confirm-rpc NODE | Nombra un segundo nodo independiente. El saldo de cada dirección y su marca de gastada deben ser informados de forma idéntica por ambos antes de que nada continúe. Un nodo cuyo chain_id discrepe se rechaza, y un --confirm-rpc que nombre el mismo punto de acceso que --rpc se rechaza sin más — un nodo que se contrasta consigo mismo coincide consigo mismo. |
| La confirmación escrita a mano | Antes de enviar, zcd wallet sweep imprime los números exactos y exige que escribas sweep. --yes se salta solo esa pregunta, nunca las comprobaciones anteriores. |
Lista de comprobación para una implementación de monedero#
Si estás escribiendo un monedero contra este protocolo, este es el contrato:
- Gastar desde
0x01mueve el saldo entero, contando la reserva del depósito como parte de él — incluso en programas sin ningún movimiento. RETIRErechaza un objetivo que todavía tenga saldo.- El saldo que informa el nodo no se trata como incuestionable: identidad de red afirmada por el operador, una segunda fuente contrastable, y los números exactos confirmados antes de un barrido irreversible.
RefundTose valida como persistente o nueva antes de firmar.- Al recibir se deriva una dirección nueva por pago esperado; al enviar se rechaza una dirección de un solo uso ya abonada o gastada.
- Las direcciones de comercio y de pago usan
0x02por defecto. Seqse incrementa por cada certificado dependiente, y el monedero espera la confirmación antes de difundir el siguiente — o dice en voz alta que no lo hace.- Los máximos de comisión se dimensionan a partir del saldo disponible y del TTL, no se fijan en el código.
- Los movimientos se ordenan canónicamente, de modo que un reintento reproduce el id del certificado.
- Los archivos de clave están cifrados, se escriben de forma duradera y sin sobrescritura silenciosa, y las contraseñas nunca llegan a un flag.
- No se firma nada de cuya propia codificación no se pueda volver a decodificar, y no se envía nada que el saldo de origen no pueda cubrir — con cualquier excepción nombrada, estrecha y reflejada en la vista previa.