Devnet

Test funds only. Transactions are not real.

Winner Selection

Detailed algorithm, edge cases, and examples for how Daily Lottery winners are chosen.

Purpose

This page documents the exact winner selection rules used by the on-chain finalize_winners instruction. Auditors can reproduce winners deterministically from on-chain data.

Inputs and Canonical Ordering

Selection depends on these on-chain fields:

  • lottery.id, participants_count, total_tickets, attested_count (Lottery)
  • Per-participant: wallet, tickets_bought, proof_of_chance_hash, voted_number_of_winners flags (Participant)
  • selected_number_of_winners (Lottery, resolved from attester votes)
  • service_charge_bps (Config, for payout math)

Eligible pool = participants with tickets_bought > 0 and reveal_included == true.

Canonical ordering = sort eligible participants by wallet public key bytes ascending.

Step 1: Determine Winner Count (k)

Winner count is resolved from all attested participants:

  1. Participants submit voted_number_of_winners during attest_uploaded.
  2. Votes are weighted by tickets_bought.
  3. Highest total weight wins.
  4. Tie-breaks: earlier attested_at_unix, then smaller winner count.

finalize_winners recomputes this deterministically and stores selected_number_of_winners.

k must be <= MAX_WINNERS.

Step 2: Build Deterministic Seed (Reveal-Plaintext Draw v2)

Rule version: reveal-plaintext-draw-v2

The program builds a seed with SHA-256:

S0 = SHA256(0x494B494741495F5250445F56325F53454544 || lottery_id_le64 || eligible_count_le64 || total_revealed_tickets_le64 || poc_aggregate_hash_32)
For each participant p in canonical order:
  S_next = SHA256(S_prev || p.wallet_32 || p.tickets_bought_le64)
Final seed = S

poc_aggregate_hash is reveal-derived on-chain entropy input accumulated during upload_reveals.

Step 3: Draw Winners Without Replacement (Ticket-Weighted)

For round j = 0..k-1:

Dj = SHA256(0x494B494741495F5250445F56325F44524157 || S || j_le64)
rj = LE_u128(Dj[0..16]) mod total_remaining_tickets

Then:

  1. Walk cumulative ticket ranges in canonical order.
  2. Wallet whose range contains rj is selected.
  3. Remove selected wallet from pool.
  4. Repeat.

This keeps weighted ticket probability while preventing duplicate winners.

Step 4: Payout and Merkle Root

After winners are selected:

service_fee = total_funds * service_charge_bps / 10_000
total_payout = total_funds - service_fee
per_winner_payout = total_payout / winners_count

Merkle leaf per winner:

leaf = SHA256(index || recipient || amount)

Merkle root is stored on-chain and used by settle_payout_batch proof verification.

Edge Cases and Settlement Paths

  • No buyers (total_tickets == 0): lottery settles immediately with NoBuyersConcluded.
  • No attesters (attested_count == 0):
    • FinalizeWinners is rejected.
    • FinalizeNoAttesters refund path is required.
  • Single participant: managed via refund/special settlement flow.
  • Missing reveals after deadline:
    • If at least one reveal exists: finalization continues with reveal-included subset.
    • If zero reveals exist: refund path is used.
  • Winner cap: k cannot exceed MAX_WINNERS.

Worked Example (Compact)

Assume sorted wallets A < B < C, tickets A=2, B=5, C=3, and reveal-included commitments HA, HB, HC.

  1. Compute S0 from lottery header fields.
  2. Fold A, then B, then C into the seed chain to get final S.
  3. Round 0: D0 = SHA256(0x494B494741495F5250445F56325F44524157 || S || 0), r0 = D0[0..16] % 10.
  4. Walk ranges A:[0,2), B:[2,7), C:[7,10) to pick winner.
  5. Remove winner, recompute modulo over remaining tickets for round 1.

Anyone with the same on-chain inputs gets the same winners.

Audit Checklist

  • Confirm canonical sort order by wallet bytes.
  • Confirm reveal-included flags and rebuild eligible pool.
  • Recompute seed chain from lottery fields + poc_aggregate_hash + (wallet, tickets) tuples.
  • Recompute each round hash and modulo selection on remaining tickets.
  • Recompute merkle root from (index, recipient, amount).
Edit this pageLast updated: January 2026