Protocol Architecture
NOXY internals: transaction envelope, DAG consensus with checkpoint finality, state commitments, cryptographic primitives, and validator lifecycle.
Protocol Architecture
NOXY is a post-quantum Layer 1 blockchain built around a canonical transaction envelope, a Merkle-committed ledger, and a DAG consensus core with checkpoint finality. This page documents what is implemented in the Rust codebase.
Hashing
All consensus-critical digests use BLAKE3 with domain separation:
NOXY-L0/v0.1/<domain>\0 || data
Examples of domain strings used in the codebase:
| Domain | Used for |
|--------|----------|
| tx-signing | Transaction signing digest |
| tx-root | Merkle root over transaction hashes |
| state-root | Merkle root over state key-value leaves |
| event-root | Merkle root over event hashes |
Transaction envelope
Every user action is submitted as a TransactionEnvelope:
TransactionEnvelope {
version: u16 // wire version
chain_id: ChainId // must match node's chain_id
account_id: AccountId // 32-byte account identifier
nonce: u64 // per-account sequence number
fee: {
fee_asset_id: AssetId
max_fee: u64
witness_byte_fee_limit: u64
}
payload: TransactionPayload
auth: {
key_id: KeyId
algorithm_id: SignatureAlgorithmId
signature: bytes // ML-DSA-44 signature
optional_hybrid_signature: bytes? // optional Ed25519 second signature
}
}
The signing digest is computed over the canonical encoding of the envelope body (everything except auth.signature).
Transaction types
| Type | Description |
|------|-------------|
| CreateAccount | Create a new account with an initial ML-DSA-44 key |
| RegisterKey | Register an additional key on an existing account |
| Transfer | Transfer a fungible asset between accounts |
| RegisterValidator | Register a validator record bound to an operator account |
| Batch | Sequence of BatchAction items in one atomic envelope |
| ActivateValidator | (Admin) Schedule validator activation at a given epoch |
| DeactivateValidator | (Admin) Schedule validator deactivation |
| RegisterServiceDefinition | Register a service definition |
| RegisterServiceChain | Register a service chain |
| SubmitCheckpoint | Submit a checkpoint |
| SubmitMessageEnvelope | Cross-chain message envelope |
Batch is the composable variant: it wraps a Vec<BatchAction> where each action is one of RegisterKey, RegisterValidator, Transfer, ActivateValidator, DeactivateValidator, and others.
Consensus
Consensus is a single pipeline with three stages; there are no consensus modes to select in config.
- Availability. A DAG availability layer collects transaction batches and certifies them across the validator set.
- Ordering. An ordering layer sequences certified batches into one continuous transaction log.
- Finality. Checkpoint certificates make the ordered prefix durably final.
Once a transaction is covered by a checkpoint certificate it is final: there are no forks, no probabilistic settlement, and no reorgs. There is no view-based pacemaker to tune; ordering runs continuously rather than in leader-driven rounds.
Equivocation is handled inside the availability layer by an anti-equivocation collector. It is a liveness mechanism, not a punishment mechanism; there is no slashing.
Blocks and checkpoints
Batches and checkpoints commit Merkle roots over transactions, state, and events. A checkpoint pins a position in the ordered transaction log together with the state that results from executing everything up to that position:
Checkpoint {
height: u64 // checkpoint height
ordered_sequence_end: u64 // end of the ordered prefix this checkpoint covers
state_root: Hash32 // state commitment after executing the covered prefix
ordered_log_root: Hash32 // Merkle root over the ordered transaction log
}
A checkpoint certificate over these fields, signed by the validator set, makes the covered prefix durably final.
State
State is committed to a sparse Merkle tree (SMT) over 256-bit BLAKE3 paths. Tree commits are batched per checkpoint window: each checkpoint's state_root covers all state changes since the previous checkpoint.
Any account, balance, or transaction is provable against a committed checkpoint with a compact proof. Transaction proofs are served over the public RPC:
GET /v0/tx/{hash}/proof?level=inclusion|finality|checkpoint
At finality and above the response adds the checkpoint and its certificate (checkpoint_hex, checkpoint_cert_hex), so the proof chains up to a validator-signed checkpoint.
Persistence is atomic: state writes, tree nodes, and indexes land in RocksDB atomically per commit batch.
Cryptography
ML-DSA-44
Account keys are ML-DSA-44 (FIPS 204). Transaction envelopes are signed with the account's primary key. Additional keys can be registered via RegisterKey. ML-DSA-44 public keys are approximately 1312 bytes; signatures approximately 2420 bytes.
Strict hybrid
Consensus certificates are signed with a hybrid scheme: ML-DSA-44 plus Ed25519. Verification requires both signatures to pass; there is no fallback to one signature alone.
Validator lifecycle
Candidate → Active → Inactive | Exiting → Exited
| Status | Description |
|--------|-------------|
| Candidate | Registered but not yet scheduled for activation |
| Active | In the current active set, certifying batches and signing checkpoints |
| Inactive | Deactivated, not in active set |
| Exiting | Scheduled for removal at active_until_epoch |
| Exited | Fully removed from the validator set |
A validator record binds:
operator_account_id: the account that owns lifecycle actionspq_consensus_key_id: ML-DSA-44 key for consensus certificate signaturesclassical_consensus_key_id: Ed25519 key paired with it for the hybrid schemevoting_power: the validator's weight when certifyingactive_from_epoch/active_until_epoch: epoch-bounded membership
The active set at each height is derived from committed state, not from local config.
Epoch transitions
Epochs advance deterministically based on height. At each epoch boundary:
- The execution layer derives a new active set from the current validator records.
- Validators scheduled for activation at this epoch move from
Candidate→Active. - Validators with a matching
active_until_epochmove toExitingorInactive.