Skip to content

Security: oxy-Op/anigacha

Security

docs/security.md

Security Considerations

These notes describe the intended security properties of the native Pinocchio lottery program. They are written against the on-chain design, not the web frontend.

Selective-Reveal / Refund Grind Attack

Threat: if outcomes were computable at commit time, or if refund could escape an unfavorable settled result, a player could free-roll the lottery by refunding losses and revealing wins.

Defense: Switchboard On-Demand randomness does not exist at Play commit time. The player commits to a randomness account before the random value is available, so result-grinding is not possible at the primitive level.

Defense: RefundExpired is legal only if the Switchboard randomness account proves it never settled and the timeout elapsed. If randomness settled, refund is rejected; the only valid settlement path is Reveal.

Defense: Reveal is permissionless. Once randomness settles, any keeper can crank the reveal path and force the NFT transfer and play settlement.

Conclusion: the player has no profitable deviation. Settled randomness must reveal and can be forced by anyone; never-settled randomness plus timeout can refund.

Custody Is Verified, Not Trusted

Threat: a caller can supply arbitrary accounts and try to convince the program that they are the lottery, chunk, collection, or asset accounts.

Defense: the program validates PDA derivations, stored bumps, chunk indices, account ownership, Metaplex Core asset owner, and collection membership on-chain. Caller-supplied accounts are treated as untrusted inputs.

Minimal Admin Authority

Threat: broad admin controls can become custody risk.

Defense: lottery parameters are set at initialization. Admin cannot withdraw active pool NFTs or funds owed to committed plays. Payment reaches admin only on successful reveal; refund closes back to the stored player. No admin cleanup path exists in this version.

Modulo Bias In Randomness Reduction

Threat: mapping a 256-bit random value into [0, active_count) with modulo has a tiny bias unless the active count divides 2^256.

Defense: the program reduces all 32 bytes as a big-endian integer modulo active_count. For a maximum active pool of 62,500, the bias is bounded by one extra preimage out of a 256-bit domain, which is negligible for this use. The implementation does not use rejection sampling.

Tail-Swap Instead Of Drawn Bitmap

Threat: a drawn-bitmap pool can require retries or scans as the pool drains, because randomness can land on an already-drawn slot.

Defense: reveal uses tail-swap. The active pool remains densely packed in [0, total_nfts), removal is O(1), and the next draw never has to reject an already-drawn slot. This lowers CU and account-state complexity at the cost of not preserving deposit order.

Keeper Reveal Ordering

Threat: after several random values settle, a keeper could simulate different reveal orders. Because each reveal changes the pool length and tail-swapped layout, choosing the order after seeing randomness can change realized prizes.

Defense: Play fixes a monotonic sequence before randomness exists. Reveal and RefundExpired accept only next_settlement_sequence, so settlement follows commit order regardless of transaction priority. This introduces bounded head-of-line blocking; permissionless reveal and timeout refund are the liveness paths.

There aren't any published security advisories