Skip to content

Consensus Entropy (RNG)

Consensus Entropy gives Xahau hooks access to per-ledger randomness derived from a validator commit/reveal scheme. It supports applications like lotteries, games, random selection, and shuffling with deterministic replay, tiered quality labels, and ledger-visible contributor accountability.

(Requires the ConsensusEntropy amendment.)

The entropy_cr_* import family is permanently scoped to this commit/reveal contract. A future entropy mechanism will use distinct Hook imports rather than changing these calls.

Each consensus round produces entropy through a three-phase pipeline:

  1. Commit — each proposing validator generates a secret and broadcasts a cryptographic commitment (sha512Half(secret, pubKey, seq)) on their first proposal
  2. Reveal — after the transaction set converges, validators broadcast their secret
  3. Inject — the agreed reveal set is mixed deterministically into a digest, carried with its tier, count, denominator, and contributor metadata by a ConsensusEntropy pseudo-transaction that is applied before every other transaction in the ledger

Not all entropy is equally strong. Every RNG-enabled ledger carries a tiered entropy input — hooks must explicitly state what quality they require:

TierNameSourceTypical Use
4Validator FullThe accepted reveal count equals every validator in the effective (post-NegativeUNL) view (EntropyCount == EntropyDenominator)Highest-assurance outcomes that must reject any selective withholding — fails closed if any active validator is absent
3Validator QuorumThe accepted reveal count reaches 80% (rounded up) of the effective validator viewOutcomes that require validator-quorum entropy
2Participant AlignedThe accepted reveal count reaches the participant floor over the original (pre-NegativeUNL) view, but not the 80% quorumDegraded-network outcomes that explicitly accept weaker entropy
1Consensus FallbackDeterministic public-input fallback, derived for this ledgerCosmetic shuffling, non-adversarial randomness

Tiers label how many validators’ reveals are in the accepted set. They are not a separate certificate of how many validators advertised the same entropy set: that alignment is the round’s proceed gate, a different calculation. The round proceeds to injection once the accepted reveal set reaches the lower of the 80% effective-view quorum and the participant floor. The participant floor is the smallest cohort whose pairwise overlap contains an honest validator when at most floor(n / 5) validators are Byzantine: floor((n + floor(n / 5)) / 2) + 1 over the original view size n, about 60%. The agreed count is then labeled in order: tier 4 if it equals a non-empty effective view, tier 3 if it reaches the quorum, tier 2 if it reaches the participant floor, otherwise tier 1.

RNG-enabled ledgers always receive either validator-derived entropy or a consensus-fallback digest. Accepted material that passes none of these tests, below both the quorum and the participant floor, falls back as a whole, with count and denominator 0 and an empty contributor bitmap. Because NegativeUNL shrinks the effective view, the quorum can sit below the participant floor: with 20 original validators and 15 effective, the quorum is 12 and the floor is 13, so 12 accepted reveals are tier 3.

Validator tiers (2 to 4) require the round’s validator view to come from the parent ledger’s UNLReport. A non-standalone node whose parent ledger has no UNLReport always labels its entropy tier 1, even if its locally configured trust set and reveal count would otherwise qualify. Standalone mode’s synthetic tier 4 (see Speculative Execution) is a testing exception, not evidence of live validator participation.

Tier 2 is intentionally weaker than Tier 3, and Hooks only accept it if they set min_tier <= 2.

Returns a random integer in [0, sides):

int64_t roll = entropy_cr_dice(6, 3, 0); // d6, validator-quorum entropy, no later strong Hook
if (roll < 0)
rollback(SBUF("Entropy unavailable"), 1);

entropy_cr_random(write_ptr, write_len, min_tier, flags)

Section titled “entropy_cr_random(write_ptr, write_len, min_tier, flags)”

Writes exactly write_len random bytes to a buffer (1..512 bytes). Entropy is generated internally in 32-byte blocks:

uint8_t buf[32];
int64_t written = entropy_cr_random(SBUF(buf), 3, 0); // 32 bytes of validator-quorum random data
if (written != sizeof(buf))
rollback(SBUF("Entropy unavailable"), 1);

Both functions return TOO_LITTLE_ENTROPY if the entropy visible to the execution is missing, stale, or below the stated tier, and LATER_STRONG_HOOK if flags does not allow a strong Hook that can run after the draw (see Later Strong Hooks). There are no defaults — hooks must explicitly choose their entropy quality and veto policy at every call site. Which input a preview or final execution sees is explained in Speculative Execution.

Returns the entropy metadata visible to this execution — tier, reveal count, and participation denominator — packed into a single non-negative value, so hooks can enforce any participation policy in their own code. It is advisory metadata, not a freshness guarantee or proof that a later draw will be admitted:

int64_t status = entropy_cr_status();
if (status < 0)
rollback(SBUF("Entropy unavailable"), 1);
uint32_t tier = ENTROPY_TIER(status);
uint32_t count = ENTROPY_COUNT(status);
uint32_t denominator = ENTROPY_DENOMINATOR(status);
// Classify the tier before any count/denominator arithmetic:
// consensus fallback is tier 1 with count = denominator = 0.
if (tier < 3)
rollback(SBUF("Validator entropy required"), 1);
// Example policy: at least 4/5 of active validators contributed.
if ((uint64_t)5 * count < (uint64_t)4 * denominator)
rollback(SBUF("Insufficient participation"), 1);
int64_t roll = entropy_cr_dice(6, 3, 0);

The ENTROPY_TIER, ENTROPY_COUNT, and ENTROPY_DENOMINATOR macros ship with the hook API headers. Common policies include tolerating a single absent validator (denominator - count <= 1), requiring a participation ratio (5*count >= 4*denominator, computed with widened arithmetic), or requiring an absolute reveal floor (count >= 20). entropy_cr_status() exposes metadata only — the entropy digest itself is never exposed to hooks; randomness is only drawn through entropy_cr_dice() and entropy_cr_random().

ApplicationSuggested min_tierAdditional policy via entropy_cr_status()
Highest-assurance (reject any withholding)4None needed — tier 4 already means every active validator contributed
Lottery / raffle3e.g. tolerate one absentee: denominator - count <= 1
Game with real stakes3e.g. participation ratio: 5*count >= 4*denominator
Degraded-network outcome that explicitly accepts weaker entropy2Application-specific
Random selection (non-financial)1None
Cosmetic shuffling1None

Several Hooks can run on one transaction. For an ordinary user transaction, the sender’s Hooks run first, then the Hooks of each strong transactional stakeholder, such as a payment’s destination. A transaction emitted by a Hook skips the sender’s Hooks but still runs the strong stakeholders’ Hooks. A strong Hook that runs after a draw can observe its effects and roll back the transaction when it dislikes the outcome, so the transaction only succeeds with outcomes that Hook accepts.

The flags argument of entropy_cr_dice() and entropy_cr_random() states which later strong Hooks a draw tolerates:

flagsA strong Hook may draw when
0It is the last strong Hook that can run in the transaction.
ENTROPY_ALLOW_SAME_ACCOUNT_STRONG_VETO (2)Every strong Hook that can run after it is installed on the same account.
ENTROPY_ALLOW_ANY_STRONG_VETO (1)Always: no restriction on later strong Hooks. The other checks still apply.

A refused draw returns LATER_STRONG_HOOK before any randomness is derived, and the Hook can defer or reject. Which Hooks can run is fixed before strong execution starts, from the Hooks whose HookOn and HookName match the transaction; hook_skip requests made during execution do not change it. Weak, callback, and again-as-weak executions have no later strong phase, so these checks always pass there.

DeploymentSuggested flags
Game Hook that must be the last strong Hook in the transaction0
Game Hook on the game’s own account, followed only by the game’s own HooksENTROPY_ALLOW_SAME_ACCOUNT_STRONG_VETO
Reusable Hook installed on accounts its author does not control (for example, a client Hook on each player’s account)0
Hook whose users trust a known code hash0
Cosmetic randomness that any later Hook may vetoENTROPY_ALLOW_ANY_STRONG_VETO

Being last in one’s own chain is not enough for flags = 0 or for ENTROPY_ALLOW_SAME_ACCOUNT_STRONG_VETO: if a strong stakeholder on another account has eligible Hooks that run later, such as a Remit destination or a URI token issuer, the draw is refused.

With flags = 0, no other strong Hook can run after a successful draw. The two opt-in flags deliberately let later Hooks within their scope veto based on the outcome. No mode stops the drawing Hook from rejecting its own transaction, or the transaction from failing for other reasons. Value-bearing applications should commit their inputs in an earlier transaction and must not let a retry select a fresh outcome.

Randomness is derived per-call from a combination of the entropy digest, ledger sequence, transaction ID, originating account, hook account, hook hash, chain position, strong/weak execution flag, callback/direct dispatch flag, and a call counter that advances on each successful draw. This means:

  • Different hooks on the same transaction use distinct derivation inputs, so their draws are independent streams (small-range results can still coincide)
  • Multiple calls within one hook use different derivation inputs
  • Same inputs always produce the same outputs (deterministic replay)

Open-ledger previews for ledger N, including simulate, use the entropy input inherited from closed ledger N-1. During final construction of N, the engine injects and applies a current-ledger ConsensusEntropy pseudo-transaction before ordinary transactions. Every normally built RNG-enabled ledger gets either validator-derived entropy or a newly derived tier-1 fallback bound to that ledger: failure to obtain validator entropy does not reuse an old seed or leave the ledger without an input, though a higher min_tier can still reject the fallback. Fresh does not mean unpredictable: the tier-1 fallback is derived deterministically from public inputs. Preview results may therefore differ from final execution, so treat them like a provisional tesSUCCESS.

Only if the current input is exceptionally unavailable, for example because its pseudo-transaction failed to apply, does entropy_cr_status() return DOESNT_EXIST and an otherwise admissible draw return TOO_LITTLE_ENTROPY. Argument and composition errors are still reported before entropy errors. Final execution never silently reuses ledger N-1’s entropy.

Standalone single-node execution injects synthetic tier-4 (validator_full) entropy with count 20, denominator 20, and a contributor bitmap with the first 20 validators marked for development. That makes min_tier=4 checks pass locally, but quiet network ledgers can still fall back to tier 1.