ZYCORD docs
English
ZycordDocsArchitecture

Architecture

The engineering companion to the whitepaper — how the reference node realises the design, and the decisions behind it. Explanatory, not normative.

This document is not normative

The protocol is the parameter files and the golden vectors, plus the named rules wherever they are defined; the wire specification carries the peer layer's requirements. This page explains that surface and records the decisions behind it. Where it disagrees with the normative surface, the normative surface wins and this text is corrected. The full engineering companion is docs/ARCHITECTURE.md.

Two predicates, two engines#

The life of a certificate, end to end:

 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       |
                          +-------------------+           +---------------------+

Validity is stateless and parallel: it runs once per certificate per node, before and independent of blocks. Applicability is stateful and sequential: it runs inside the fold at the certificate's committed position. The miner runs no execution; it orders hashes it has already seen validated and solves proof of work. The fold is a tight loop over an in-memory working set — compare, add, write.

Engineering principles#

Principle
P1The fold is sacred. The state-transition function lives in one pure package with no I/O, no clocks, no goroutines, no map iteration and no floating point. It is the only code whose bugs are unfixable after the fact. Everything else in the node is replaceable plumbing.
P2Determinism beats performance. Any optimisation that risks nondeterminism is rejected in consensus code. Performance belongs in the stateless pipeline, where it is safe.
P3No admin keys, no privileged RPC. There is no code path by which any key can pause, upgrade, mint or reorganise. If it is not in the fold rules, it does not exist.
P4Small consensus surface. core/ imports nothing outside the standard library. The rest of the node may use ecosystem libraries; the core may not.
P5Spec-first. The golden vectors are the protocol. The Go code is a reference implementation; an independent implementation that passes the vectors is a peer, not a fork. This is what allows the maintainer to eventually be nobody in particular.
P6Reproducible from v0.1. Pinned Go toolchain, -trimpath, pinned dependencies. Trust moves from the binary to the code, which is the only trust an anonymous author can offer.
P7One binary, gated by height. The machine, the bond operations and sequencer registration are compiled into every release and refused below their activation height — Validate requires h1_vm ≥ h1_bond and h1_vm on an epoch boundary. An era arrives because the chain reached a number, never because operators were asked to upgrade.

P3 is the one a sceptical reader should press hardest, and the treasury is where to press it: genesis contains no key and no spend path at all, so there is no privilege to hold, delegate or steal. The 3-of-5 quorum of Era 2 is pinned by a future hard fork — the same social mechanism as any other consensus change, subject to the same refusal, and able to move exactly one cell even then. A quorum that only appears if the network agrees to write it in, and that then holds one cell, is a spending rule. An admin key is one that exists before anyone consented and reaches everything.

Cryptographic primitives#

RoleChoiceWhy
SignaturesEd25519Batch verification (the GPU on-ramp), no malleability, tiny keys. Strict rules fixed at genesis: canonical encodings required for the public key and R, the public key torsion-free and not of low order, and verification cofactorless.
HashingBLAKE3Certificate and block ids, addresses, state root. Fast enough to hash at relay line rate; parallel-friendly for the epoch state root.
Proof of workRandomXCPU-optimised. The only cgo in the tree, behind a build tag, and absent from a build without it. pow_engine is in the consensus root, so a binary holding the wrong engine refuses to start rather than accepting the wrong proof.
Domain separationmandatoryEvery hash is blake3(tag ‖ payload). Signatures sign over the chain id and the consensus root, which kills replay both between networks and across two incarnations of one network.

Torsion rejection is what makes the batch path safe. A mixed-order key is not small-order, so no blocklist reaches it, and it is exactly where a cofactored batch verifier and a cofactorless single verifier disagree. With the key and R in the prime-order subgroup the two are provably equivalent — so a batch verifier may be cofactored provided it applies the same encoding and torsion rules before batching. That obligation is the price of the choice, and the batch verifier does not exist yet.

Canonical encoding and identifiers#

All consensus objects are SSZ containers: a single canonical byte encoding, no map ordering, no optional-field ambiguity, fixed offsets for cheap partial parsing, and native merkleization.

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))

The first two are different digests answering different questions, and an implementation that uses one where the other belongs is monetarily broken. The id answers has this authorization been billed; the exemplar hash answers do these bytes prove it. The two never share a key.

Signatures are outside the id's preimage because a signature is a randomized demonstration: the signer picks the nonce, every nonce yields another valid and perfectly canonical signature over the same body, and no verifier can check which was used. Were they inside, one authorization would have unboundedly many ids, each billable, each producible by any one of its required signers out of the others' authority.

The fee bid is inside the id, and that is a monetary decision

A bid outside the id would be a bid anyone in transit could rewrite — inflated to burn the signer's balance through the base fee, or zeroed to keep the certificate out of every block. What a certificate pays is part of what its signer authorized, so it is hashed and it is signed. A later encoding change that "moves the fee out of the signed body for relay flexibility" would look like an optimisation and would be a theft vector.

The rule that follows: parse, don't validate twice. Decoding enforces canonical form, so a decoded object is structurally valid by construction and the rule engines never re-check shape.

The cell model#

A cell is the value at a slot. Cell values are unsigned 256-bit integers stored big-endian in 32 bytes. Absent cells read as zero — zero is absence, which is a consensus requirement rather than an implementation convenience: it keeps the state root a function of the state rather than of the history that produced it.

Separately, the protocol keeps a spent-address registry: a permanent consensus set of one-shot addresses whose signing authority has been burned.

Addr = version || blake3("zcd/addr/v1" || version || payload)[:31]
VersionKindDebit authorization
0x01one-shot userOwner signature; any debiting certificate must also carry an explicit MARK_SPENT. After it applies, every read and write under the address fails forever.
0x02persistent userOwner signature; reusable forever. Can never enter the spent registry.
0x03assetGoverned by the asset's immutable authority cells.
0x00protocolFold-only: epoch beacon, base-fee cells, coinbase maturity ring, treasury cell.
0x04reserved — hidden-value cell (Era S)Unreachable in Era 0.

0x04 is reserved now rather than allocated later, because a hidden value must be distinguishable from an ordinary one by its address: a Pedersen commitment and a 256-bit balance are both 32 bytes, so a guarded delta aimed by error or malice at a commitment slot would have the fold add an integer to a curve-point encoding — arithmetic that passes every check and leaves a cell nobody can ever spend. This table is frozen at genesis, so the byte is claimed here and left unreachable.

The registry entry is never compacted. Cell values under a spent address may be pruned after the undo horizon, but the entry that records the address as spent is what stops it being resurrected. It is append-only consensus state, about 33 bytes per address, forever — the protocol's honest open problem, shared structurally with every nullifier-set design.

Stateless validity, and the billing law#

The V-rules run on every certificate, in parallel, before mempool admission and during block verification, and require zero state: canonical form and chain id; every signature verifying over the signing root; authorization derivable from the certificate alone; the declared reads equalling what the program derives; and the refund destination checked against what the certificate itself burns.

The system's billing law is one sentence, and it is the thing to hold on to:

One signature, at most one bill, never at a position its signer could not avoid.

This spec adds one term to the whitepaper's vocabulary: drop, a non-billed non-event. A certificate reaching application with its deposit already consumed is dropped — not billed, not marked seen, free to resubmit against a fresh deposit — so honest users lose nothing to races on their own deposit cell.

Repository layout#

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

Dependency arrows point inward only — node → core, wallet → core, never the reverse — and nothing inside core/ reaches outside core/ and the standard library. One exception, named and enforced: core/pow/randomx is the only cgo in the tree, and it compiles only under the build tag, so every build without the tag is still standard-library-only and needs no C toolchain. CI runs the check that enforces it, because the third-party check greps module paths and cgo has none — which left the rule enforced by nobody until it was added.

How it is tested#

  • Golden vectors. Every fold, block and validity rule has positive and negative cases as (pre-state, block) → (post-state | invalid, outcomes, fees). The suite is the compatibility contract for independent implementations.
  • The griefing suite. Re-inclusion of applied and skipped certificates, expired inclusion, under-bid inclusion, dependent chains under proposer shuffling, burn-and-refund cycles, third-party credit storms, mint-cap boundary races.
  • Property-based. Fold determinism under proposer-order permutation, delta commutativity, ABA tolerance, and conservation — including deposits in flight, the maturity ring and the treasury cell, since a conservation check that omits the treasury reports every block as creating value.
  • Differential. A deliberately naive second fold implementation, written for obviousness rather than speed, fuzzed against the real one. Divergence is a release blocker.
  • Adversarial simulation. Skip storms, deposit-drain races, drop-stuffing miners, reorg torture, timestamp manipulation, eclipse-lite relay scenarios. Scenario configs are committed; runs are reproducible by seed.
  • Concurrency, deliberately. Every component more than one goroutine touches has a test that is concurrent, in the shape the process actually uses. -race on a single-goroutine suite measures nothing and reports success.
  • The chaos soak. Real node processes over real sockets behind a proxy injecting latency, jitter, loss and partitions, with nodes killed by SIGKILL at random. This is the surface that found the data race the entire -race suite missed.

Genesis is an artifact, not a ceremony#

zcd genesis emits the genesis block — empty state, beacon cells initialised, empty spent registry, no allocations of any kind — and its id. The announced launch commits, weeks in advance, to the code tag, the parameter hash, the genesis id, and the launch time. Anyone can rebuild all four in milliseconds, from source, on any machine. There is nothing else to trust.