Testnet · mainnet not launched · testnet ZFX has no monetary value

ZeroFlux consensus v2 (private testnet)

Status: frozen for the private testnet. v2 is a hard fork of v0.1 (consensus_version: 2): it adds state_root to the header, coinbase maturity, transaction memos, the native bridge module and the RunningMax difficulty rule. The PoW function and its golden vectors are unchanged. Not frozen for any value-bearing network (MAINNET_READY = false, see RELEASE_READINESS.md).

Every rule here is implemented in crates/zfx-verify (consensus library, no GPU runtime) and crates/zfx-node/src/chain.rs (state rules). Where this document and the code disagree, the code plus tests/vectors/fp8c_v0.1.json and tests/vectors/consensus_v2.json win, and that disagreement is a bug to report.

Notation: || is byte concatenation. u32le/u64le are little-endian unsigned integers. uint256_be reads 32 bytes as a big-endian unsigned integer. SHA256 is FIPS 180-4. SHAKE256(m, n) is the first n output bytes of FIPS 202 SHAKE256. Domain strings are ASCII bytes without a terminator or length prefix. Every input that follows a domain string has a fixed length, so concatenation is unambiguous.

1. Versions and profiles

The header version field selects the PoW profile.

id name d_in H (hidden) hidden layers d_out shift_in shift_hidden MACs per attempt
1 zfx-fp8c-v0.1 64 4096 3 256 0 2 51,642,368
2 zfx-fp8c-v0.1-dev 64 256 3 64 0 0 229,376

Profile 2 exists for unit tests and fast local devnets only. A chain uses exactly one profile, fixed by its genesis config. A header whose version differs from the chain profile is invalid.

2. Block header

156 bytes without the nonce, 164 bytes with it (v2).

offset size field encoding
0 4 magic ASCII ZFX2
4 4 version u32le (profile id)
8 4 chain_id u32le
12 8 height u64le
20 32 parent block hash of parent
52 32 tx_root section 5.4
84 32 state_root section 5.6, state after applying this block
116 8 timestamp u64le, Unix seconds
124 32 target uint256 big-endian
156 8 nonce u64le

header_without_nonce is bytes 0..156. block_hash = SHA256("ZFX-block-v0.1" || header[0..164]). The v0.1 layout (magic ZFXH, 124 + 8 bytes, no state root) is kept only to reproduce the v0.1 PoW golden vectors; it is invalid on v2 chains.

3. Proof of work: ZFX-FP8C

3.1 Operand domain

D = {-16, ..., -1, 1, ..., 16}, 32 values with no zero. A 5-bit index u maps to val(u) = u + 1 for u < 16 and val(u) = 15 - u for u >= 16. A byte b maps to val(b & 31). That map is unbiased because 256 = 8 x 32.

Every value in D is exactly representable in OCP FP8 E4M3 (bias 7), AMD FP8 E4M3FNUZ (bias 8), int8, FP16, BF16 and FP32.

Exactness bound: every layer has inner dimension K <= 4096, so |sum| <= K x 16 x 16 <= 2^20 < 2^24. Each product and every partial sum is an integer of magnitude below 2^24. FP32 therefore represents every intermediate exactly, whatever the reduction order, blocking or split-K. Int32 is exact trivially. Consensus is defined on these integers. The storage format (int8, E4M3, E4M3FNUZ, float32) is an implementation choice.

3.2 Requantization

requant(y, s) = val(SBOX[((y >> s) & 0xFF)] >> 3)

Here y is a two's-complement int32, >> is an arithmetic shift, and SBOX is the AES S-box (FIPS-197 Figure 7). The S-box is used only as a fixed nonlinear byte permutation. Because it is a permutation, each of the 32 outputs comes from exactly 8 input bytes.

3.3 Inputs

challenge   = SHA256("ZFX-FP8C-v0.1" || header_without_nonce)
attempt_id  = SHA256("ZFX-attempt-v0.1" || challenge || u64le(nonce))
x0[i]       = val(SHAKE256("ZFX-FP8C-X0-v0.1" || attempt_id, d_in)[i] & 31)     i < d_in

3.4 Epoch weights

For a block at height h >= 1, anchor_height = floor((h - 1) / 32) * 32, and anchor_hash is the hash of that height's ancestor on the block's own branch. Heights 1..32 use the genesis hash.

seed           = SHA256("ZFX-FP8C-epoch-v0.1" || anchor_hash || u32le(profile_id))
W[l][j][k]     = val(SHAKE256("ZFX-FP8C-W-v0.1" || seed || u32le(l) || u32le(j), K_l)[k] & 31)

Layer l has shape N_l x K_l (output-major, one independent XOF stream per row j):

l K_l N_l requant shift
0 d_in H shift_in
1 .. n_hidden H H shift_hidden
n_hidden + 1 H d_out none (output layer)

3.5 Attempt

x = x0
for each layer l:
    y[j] = sum_k x[k] * W[l][j][k]          (exact integer, |y| < 2^24)
    if l is not the output layer: x[j] = requant(y[j], shift_l)
out = y                                     (d_out int32 values)
digest = SHA256("ZFX-result-v0.1" || challenge || u64le(nonce) || out[0] as i32le || ... || out[d_out-1] as i32le)
valid  iff uint256_be(digest) < uint256_be(header.target)

The miner submits only (work_id, nonce) to its node. Nodes and peers receive the full header and recompute the digest. No miner-supplied digest is ever trusted.

3.6 Reference vectors

tests/vectors/fp8c_v0.1.json (regenerate with zfx-research vectors):

profile epoch_seed challenge digest(nonce=0)
dev 793855bd...70f43538 79753124...01a3bdaf ed243470...2e89fdc1
v01 3d3fefe9...2f2cb73c 4e4bca37...28dbe66f 66286df2...0b1119ca

Edge case: dot([16]*4096, [16]*4096) = 1048576, whose FP32 bits are 0x49800000. Three independent implementations (Rust, C++, Python) and the Apple GPU backends reproduce every vector.

4. Difficulty and time

5. Transactions

5.1 Signatures

Messages are signed with FIPS 205 pure SLH-DSA (hedged), with context string "ZFX-v0.1". There is no elliptic-curve or RSA primitive anywhere in consensus.

5.2 P2MR addresses

leaf_hash    = SHA256("ZFX-p2mr-leaf-v0.1" || u8(scheme) || u16le(len(pk)) || pk)
node(a, b)   = SHA256("ZFX-p2mr-node-v0.1" || min(a,b) || max(a,b))       (sorted pair)
root         = binary Merkle root over leaf hashes, odd node promoted
address      = SHA256("ZFX-p2mr-addr-v0.1" || root)                        (32 bytes)
text         = "zfx1" || hex(address) || hex(SHA256("ZFX-addrck" || address)[0..4])

Spending reveals one leaf, its Merkle path (depth <= 8) and a signature. There is no key-path spend. Wallets default to two leaves: index 0 is 128s for daily use and index 1 is 192s as a backup.

5.3 Encoding and identifiers

core     = u32le(chain_id) || from(32) || to(32) || u64le(amount) || u64le(fee) || u64le(nonce)
           [|| 0x01 || u8(len memo) || memo]          (v2, only when a memo is set; UTF-8, <= 64 bytes)
message  = "ZFX-tx-v0.1" || core
witness  = u8(scheme) || u16le(len pk) || pk || u8(path len) || path(32 each) || u16le(len sig) || sig
txid     = SHA256("ZFX-txid-v0.1" || core)                  (witness excluded: non-malleable)
wtxid    = SHA256("ZFX-wtxid-v0.1" || core || witness)
coinbase = u64le(height) || to(32) || u64le(amount);  id = SHA256("ZFX-coinbase-v0.1" || coinbase)

5.4 Block body

tx_root = SHA256("ZFX-txroot-v0.1" || u32le(count) || merkle(leaves)), where the leaves are [coinbase id, wtxid_1, ..., wtxid_n, op_id_1, ..., op_id_m] (v2 appends the bridge op ids, section 7). Merkle nodes are SHA256("ZFX-merkle-v0.1" || l || r), and an odd node is promoted without duplication (avoiding CVE-2012-2459). Limits are 1,000 transactions and 4 MiB of canonical bytes per block.

5.5 State rules (account model)

5.6 State root (v2)

state_root = SHA256("ZFX-state-v2" || body), where body is: u64le(n) and, for each of the n live accounts (balance or nonce nonzero) in ascending address order, address(32) || u64le(balance) || u64le(nonce); then "|immature" and each immature entry u64le(height) || address || u64le(amount) in key order; then "|supply" || u64le(supply); then, if the bridge is enabled, "|bridge" || u32le(epoch) || members(32 each) || pending rotation (0x00, or 0x01 || keys || u64le(activate_height)) || u8(paused) || u64le(admin_nonce) || u128le(locked_total) || u128le(released_total) || u64le(deposits) || u64le(releases) || used withdrawal ids (32 each, ascending). Code: State::root in crates/zfx-verify/src/state.rs.

6. Node-to-miner API (v1)

GET /v1/work?address=zfx1... returns {api_version, work_id, chain_id, version, height, header_without_nonce, challenge, target, epoch_seed, profile, expiry}.

POST /v1/submit {work_id, nonce} returns {accepted, block_hash | error}.

Work expires when the tip changes. The node recomputes the PoW for every submission and rate-limits to 50 submissions per second per IP. Request bodies are capped at 64 KiB and P2P lines at 16 MiB. GET /v1/status returns height, target, peers and best hash. The miner checks that challenge == SHA256("ZFX-FP8C-v0.1" || header_without_nonce) before mining.

7. Native bridge module (v2)

Enabled by a bridge section in the genesis config: committee (3 SLH-DSA-SHA2-128s public keys), threshold 2, rotation_timelock_blocks, min_lock, activation_height, and the NEAR contract id.

Golden vectors for all v2 encodings and digests: tests/vectors/consensus_v2.json (regenerate only with zfx-research vectors-v2 --force).