Arquitectura
El acompañante de ingeniería del whitepaper — cómo el nodo de referencia realiza el diseño, y las decisiones que hay detrás. Explicativo, no normativo.
El protocolo son los archivos de parámetros y los vectores de referencia, más las reglas nombradas allí donde estén definidas; la especificación de red lleva los requisitos de la capa entre pares. Esta página explica esa superficie y registra las decisiones que hay detrás. Donde discrepe con la superficie normativa, gana la superficie normativa y este texto se corrige. El compañero de ingeniería completo es docs/ARCHITECTURE.md.
Dos predicados, dos motores#
La vida de un certificado, de principio a fin:
wallet network every full node
+------------+ gossip +--------------+ +---------------------+
| build cert | ---------> | cert topic | ------------> | STATELESS PIPELINE |
| (reads, | | (TLS gossip) | | V1..V8, batch sigs, |
| writes, | +--------------+ | native re-exec |
| sigs, seq, | | -> mark VALID |
| deposit) | +----------+----------+
+------------+ | mempool
v
miner (any node) +---------------------+
+-------------------+ block | FOLD (sequential) |
| assemble ordered | --------> | F-rules per cert: |
| hash list + bodies| gossip | APPLY / SKIP / DROP |
| + RandomX solve | | -> new state |
+-------------------+ +---------------------+
La validez es sin estado y paralela: se ejecuta una vez por certificado y por nodo, antes de los bloques y con independencia de ellos. La aplicabilidad es con estado y secuencial: se ejecuta dentro del fold, en la posición confirmada del certificado. El minero no ejecuta nada; ordena hashes que ya ha visto validados y resuelve el proof of work. El fold es un bucle apretado sobre un conjunto de trabajo en memoria — comparar, sumar, escribir.
Principios de ingeniería#
| Principio | |
|---|---|
| P1 | El fold es sagrado. La función de transición de estado vive en un único paquete puro sin E/S, sin relojes, sin gorutinas, sin iteración de mapas y sin coma flotante. Es el único código cuyos fallos no tienen arreglo a posteriori. Todo lo demás en el nodo es fontanería reemplazable. |
| P2 | El determinismo gana al rendimiento. Cualquier optimización que arriesgue indeterminismo se rechaza en el código de consenso. El rendimiento corresponde a la tubería sin estado, donde es seguro. |
| P3 | Sin claves de administración, sin RPC privilegiado. No existe ninguna ruta de código por la que una clave pueda pausar, actualizar, acuñar o reorganizar. Si no está en las reglas del fold, no existe. |
| P4 | Superficie de consenso pequeña. core/ no importa nada fuera de la biblioteca estándar. El resto del nodo puede usar bibliotecas del ecosistema; el núcleo no. |
| P5 | La especificación primero. Los vectores de referencia son el protocolo. El código Go es una implementación de referencia; una implementación independiente que pase los vectores es un par, no un fork. Esto es lo que permite que el mantenedor acabe siendo nadie en particular. |
| P6 | Reproducible desde la v0.1. Cadena de herramientas de Go fijada, -trimpath, dependencias fijadas. La confianza pasa del binario al código, que es la única confianza que un autor anónimo puede ofrecer. |
| P7 | Un solo binario, abierto por altura. La máquina, las operaciones de depósito y el registro de secuenciadores se compilan en cada versión y se rechazan por debajo de su altura de activación — Validate exige h1_vm ≥ h1_bond y exige que h1_vm caiga en un límite de época. Una era llega porque la cadena alcanzó un número, nunca porque se pidiera a los operadores que actualizaran. |
P3 es el que un lector escéptico debería apretar más, y la tesorería es donde apretarlo: la génesis no contiene ninguna clave ni ninguna vía de gasto, así que no hay privilegio que retener, delegar o robar. El quórum 3 de 5 de la Era 2 lo fija un hard fork futuro — el mismo mecanismo social que cualquier otro cambio de consenso, sujeto al mismo rechazo, y capaz de mover exactamente una celda incluso entonces. Un quórum que solo aparece si la red acepta escribirlo y que entonces sostiene una celda es una regla de gasto. Una clave de administración es una que existe antes de que nadie consintiera y alcanza todo.
Primitivas criptográficas#
| Rol | Elección | Por qué |
|---|---|---|
| Firmas | Ed25519 | Verificación por lotes (la rampa hacia la GPU), sin maleabilidad, claves diminutas. Reglas estrictas fijadas en el génesis: codificaciones canónicas obligatorias para la clave pública y R, la clave pública libre de torsión y no de orden bajo, y verificación sin cofactor. |
| Hashing | BLAKE3 | Ids de certificado y de bloque, direcciones, raíz de estado. Lo bastante rápido para hacer hash a la velocidad de la retransmisión; amigable con el paralelismo para la raíz de estado de época. |
| Proof of work | RandomX | Optimizado para CPU. El único cgo del árbol, tras una etiqueta de compilación, y ausente de una compilación sin ella. pow_engine está en la raíz de consenso, así que un binario con el motor equivocado se niega a arrancar en lugar de aceptar la prueba equivocada. |
| Separación de dominios | obligatoria | Todo hash es blake3(tag ‖ payload). Las firmas firman sobre el chain id y la raíz de consenso, lo que mata la repetición tanto entre redes como entre dos encarnaciones de una misma red. |
El rechazo de torsión es lo que hace segura la vía por lotes. Una clave de orden mixto no es de orden pequeño, así que ninguna lista de bloqueo la alcanza, y es exactamente donde un verificador por lotes con cofactor y un verificador individual sin cofactor discrepan. Con la clave y R en el subgrupo de orden primo, los dos son demostrablemente equivalentes — así que un verificador por lotes puede usar cofactor siempre que aplique las mismas reglas de codificación y torsión antes de agrupar. Esa obligación es el precio de la elección, y el verificador por lotes aún no existe.
Codificación canónica e identificadores#
Todos los objetos de consenso son contenedores SSZ: una única codificación canónica en bytes, sin orden de mapas, sin ambigüedad de campos opcionales, desplazamientos fijos para un análisis parcial barato, y merkleización nativa.
cert_id = blake3("zcd/certid/v1" || ssz(certificate with an empty signature list))
cert_exemplar = blake3("zcd/cert/v1" || ssz(certificate))
block_id = blake3("zcd/block/v1" || ssz(header))
Los dos primeros son resúmenes distintos que responden a preguntas distintas, y una implementación que use uno donde corresponde el otro está rota en términos monetarios. El id responde a si esta autorización ya ha sido cobrada; el hash del ejemplar responde a si estos bytes lo demuestran. Los dos nunca comparten clave.
Las firmas quedan fuera de la preimagen del id porque una firma es una demostración aleatorizada: el firmante elige el nonce, cada nonce produce otra firma válida y perfectamente canónica sobre el mismo cuerpo, y ningún verificador puede comprobar cuál se usó. Si estuvieran dentro, una autorización tendría ilimitados ids, cada uno cobrable, cada uno producible por cualquiera uno de sus firmantes requeridos al margen de la autoridad de los demás.
Una puja fuera del id sería una puja que cualquiera en tránsito podría reescribir — inflada para quemar el saldo del firmante a través de la comisión base, o puesta a cero para dejar el certificado fuera de todo bloque. Lo que paga un certificado es parte de lo que su firmante autorizó, así que se resume y se firma. Un cambio de codificación posterior que "saque la comisión del cuerpo firmado por flexibilidad de retransmisión" parecería una optimización y sería un vector de robo.
La regla que se sigue: analiza, no valides dos veces. La decodificación impone la forma canónica, así que un objeto decodificado es estructuralmente válido por construcción y los motores de reglas nunca vuelven a comprobar la forma.
El modelo de celdas#
Una celda es el valor en un slot. Los valores de celda son enteros sin signo de 256 bits almacenados en big-endian en 32 bytes. Las celdas ausentes se leen como cero — cero es ausencia, lo cual es un requisito de consenso y no una comodidad de implementación: mantiene la raíz de estado como función del estado y no de la historia que lo produjo.
Por separado, el protocolo mantiene un registro de direcciones gastadas: un conjunto de consenso permanente de direcciones de un solo uso cuya autoridad de firma se ha quemado.
Addr = version || blake3("zcd/addr/v1" || version || payload)[:31]
| Versión | Tipo | Autorización de débito |
|---|---|---|
0x01 | usuario de un solo uso | Firma del propietario; cualquier certificado que debite debe llevar además un MARK_SPENT explícito. Después de que se aplique, toda lectura y escritura bajo la dirección falla para siempre. |
0x02 | usuario persistente | Firma del propietario; reutilizable para siempre. Nunca puede entrar en el registro de gastadas. |
0x03 | activo | Gobernado por las celdas de autoridad inmutables del activo. |
0x00 | protocolo | Solo el fold: baliza de época, celdas de comisión base, anillo de madurez del coinbase, celda de tesorería. |
0x04 | reservado — celda de valor oculto (Era S) | Inalcanzable en la Era 0. |
0x04 se reserva ahora en lugar de asignarse después, porque un valor
oculto debe poder distinguirse de uno corriente por su dirección: un compromiso de Pedersen
y un saldo de 256 bits son ambos 32 bytes, así que un delta con guarda dirigido por error o malicia a un
slot de compromiso haría que el fold sumara un entero a la codificación de un punto de curva — una aritmética
que pasa todas las comprobaciones y deja una celda que nadie podrá gastar jamás. Esta tabla se congela en la génesis,
así que el byte se reclama aquí y se deja inalcanzable.
La entrada del registro nunca se compacta. Los valores de celda bajo una dirección gastada pueden podarse tras el horizonte de deshacer, pero la entrada que registra la dirección como gastada es lo que impide que se resucite. Es estado de consenso de solo anexión, unos 33 bytes por dirección, para siempre — el problema abierto que el protocolo reconoce, compartido estructuralmente con todo diseño de conjunto de anuladores.
Validez sin estado, y la ley de facturación#
Las reglas V se ejecutan sobre cada certificado, en paralelo, antes de la admisión al mempool y durante la verificación de bloques, y no requieren estado alguno: forma canónica y chain id; cada firma verificando sobre la raíz de firma; autorización derivable solo del certificado; que los reads declarados sean iguales a lo que el programa deriva; y el destino del reembolso comprobado contra lo que el propio certificado quema.
La ley de facturación del sistema es una sola frase, y es lo que hay que retener:
Una firma, como mucho un cobro, nunca en una posición que su firmante no pudiera evitar.
Esta especificación añade un término al vocabulario del whitepaper: descarte, un no-evento no cobrado. Un certificado que llega a la aplicación con su depósito ya consumido se descarta — no se cobra, no se marca como visto, puede reenviarse contra un depósito nuevo — así que los usuarios honestos no pierden nada por carreras en su propia celda de depósito.
Estructura del repositorio#
zycord/
spec/ parameter sets, golden vectors, library images <- THE PROTOCOL
core/ consensus-critical; standard library only (P4)
types/ crypto/ ssz/ u256/ state/ validity/ fold/ params/ genesis/
cevm/ the certificate-adapted EVM; vendored interpreter, pure Go
stdlib/ the pre-deployed library, its addresses and code hashes
pow/randomx/ the mainnet engine: vendored C++, cgo, behind a build tag
node/ storage/ chain/ verify/ mempool/ miner/ p2p/ sync/ rpc/ stratum/
wallet/ key management, certificate builders (reference; not consensus)
contracts/ the reference contracts, in Solidity
sim/ simulator, fuzz harnesses, differential refold, chaos soak
cmd/ zycordd, zcd
desktop/ the wallet in a native window — a separate Go module
docs/ architecture, protocol, operating guide, whitepaper
Las flechas de dependencia apuntan solo hacia dentro — node → core,
wallet → core, nunca al revés — y nada dentro de core/
alcanza fuera de core/ y la biblioteca estándar. Una excepción, nombrada e impuesta:
core/pow/randomx es el único cgo del árbol, y solo compila bajo su etiqueta de
compilación, así que toda compilación sin la etiqueta sigue siendo solo de biblioteca estándar y no necesita cadena de herramientas de C. CI
ejecuta la comprobación que lo impone, porque la comprobación de terceros busca rutas de módulo con grep y cgo no
tiene ninguna — lo que dejó la regla sin que nadie la impusiera hasta que se añadió.
Cómo se prueba#
- Vectores de referencia. Cada regla de fold, de bloque y de validez tiene casos positivos y negativos como
(pre-state, block) → (post-state | invalid, outcomes, fees). La batería es el contrato de compatibilidad para implementaciones independientes. - La batería de perjuicio. Reinclusión de certificados aplicados y omitidos, inclusión caducada, inclusión con puja insuficiente, cadenas dependientes bajo barajado del proponente, ciclos de quema y reembolso, tormentas de crédito de terceros, carreras en el límite del tope de acuñación.
- Basadas en propiedades. Determinismo del fold bajo permutación del orden del proponente, conmutatividad de los deltas, tolerancia a ABA, y conservación — incluidos los depósitos en vuelo, el anillo de madurez y la celda de tesorería, ya que una comprobación de conservación que omita la tesorería informa de que cada bloque crea valor.
- Diferencial. Una segunda implementación del fold deliberadamente ingenua, escrita buscando la obviedad y no la velocidad, sometida a fuzzing contra la real. Una divergencia bloquea la publicación.
- Simulación adversaria. Tormentas de omisiones, carreras por vaciar depósitos, mineros que rellenan con descartes, tortura de reorganizaciones, manipulación de marcas de tiempo, escenarios de retransmisión con eclipse ligero. Las configuraciones de escenario se versionan; las ejecuciones son reproducibles por semilla.
- Concurrencia, deliberadamente. Todo componente que toque más de una gorutina tiene una prueba que es concurrente, con la forma que el proceso usa de verdad.
-racesobre una batería de una sola gorutina no mide nada e informa de éxito. - La prueba de remojo caótica. Procesos de nodo reales sobre sockets reales tras un proxy que inyecta latencia, jitter, pérdidas y particiones, con nodos matados por
SIGKILLal azar. Esta es la superficie que encontró la carrera de datos que toda la batería de-racese perdió.
La génesis es un artefacto, no una ceremonia#
zcd genesis emite el bloque de génesis — estado vacío, celdas de baliza inicializadas,
registro de gastadas vacío, ninguna asignación de ningún tipo — y su id. El lanzamiento
anunciado se compromete, semanas antes, con la etiqueta de código, el hash de parámetros, el id de la génesis y la
hora de lanzamiento. Cualquiera puede reconstruir los cuatro en milisegundos, desde el código fuente, en cualquier máquina.
No hay nada más en lo que confiar.