Skip to main content

Security Model

The PlayOrbs program implements multiple layers of security to protect funds and ensure fairness.

Access Control Matrix

OperationSigner RequiredValidation
initialize_rootAuthorityOne-time, PDA constraint
join_roundPlayerKeypair signature
settle_roundAuthorityhas_one = authority
round_payoutAuthorityhas_one = authority
update_player_statsAuthorityhas_one = authority
convert_pointsPlayerAccount ownership
claim_season_poolPlayerAccount ownership
manage_liquidityAuthorityhas_one = authority

Security Mechanisms

PDA-Based Membership

Instead of on-chain roster arrays, membership is proven via PDAs:

// RoundPlayer PDA proves membership
seeds = ["rp", tier_id, round_id, player]

Benefits:

  • No array size limits
  • Efficient RPC filtering via memcmp
  • Reduced transaction size
  • Prevents roster manipulation

Idempotency Tracking

Flags prevent double-processing:

struct RoundPlayer {
payout_processed: bool, // Prevents double SOL payout
stats_applied: bool, // Prevents double points
}

struct RoundMeta {
did_emit: bool, // Prevents duplicate emission
}

struct PlayerStats {
season_pool_claimed: bool, // Prevents double claim
}

Enforcement:

require!(!round_player.payout_processed, "Already paid");
// ... process payout ...
round_player.payout_processed = true;

Vault Security

Prize pools are held in PDA-controlled vaults:

// Vault PDA
seeds = ["vault", tier_id, round_id]

// Signing for transfers
let seeds = &[b"vault", &tier_id.to_le_bytes(), &round_id.to_le_bytes(), &[bump]];
let signer = &[&seeds[..]];

Properties:

  • No private key exists
  • Only program can authorize transfers
  • Funds cannot be extracted by anyone
  • Exact payout matching required

Arithmetic Safety

All arithmetic uses safe operations:

// Saturating addition
points = points.saturating_add(kill_points);

// Checked division
let share = (points as u128)
.checked_mul(pool)
.and_then(|x| x.checked_div(total))
.ok_or(ErrorCode::MathOverflow)?;

Guarantees:

  • No overflow/underflow panics
  • Explicit error handling
  • No floating-point arithmetic

Supply Cap Enforcement

Token minting respects hard caps:

let cap_atoms = 100_000_000_000_000_000; // 100M PORB
let remaining = cap_atoms.saturating_sub(root.total_minted_orb);
let actual_mint = min(requested, remaining);

root.total_minted_orb = root.total_minted_orb.saturating_add(actual_mint);

Settlement Integrity

Cryptographic verification ensures fair outcomes:

  1. ICP Seed Generation

    • Threshold randomness from subnet nodes
    • Cannot be predicted or influenced
  2. Merkle Proof Verification

    • Seed bound to specific round
    • Tamper-evident structure
  3. ECDSA Signature

    • Proves ICP canister signed the commitment
    • Non-repudiable

Attack Vectors & Mitigations

AttackMitigation
Double-spend payoutpayout_processed flag
Double-claim pointsstats_applied flag
Emission manipulationDeterministic SHA-256
Supply inflationHard cap + tracking
Vault drainPDA-only access
Front-runningICP seed generation
Rug pullLocked LP position
Authority abuseOn-chain verification

Settlement Verification

┌─────────────────────────────────────────────────────────────────┐
│ VERIFICATION FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [1] Validate chunk ID matches round │
│ └── chunk_id == round_id / 50 │
│ │
│ [2] Verify ECDSA signature │
│ └── Recovered key == ICP canister key │
│ │
│ [3] Compute leaf hash │
│ └── SHA256("orbs-leaf" || context || seed) │
│ │
│ [4] Verify Merkle proof │
│ └── Computed root == signed root │
│ │
└─────────────────────────────────────────────────────────────────┘

Threat Model

The system is secure against:

ThreatAssumption
Seed predictionICP subnet majority honest
Seed manipulationECDSA remains secure
Seed substitutionSHA-256 remains secure
Admin abuseAdmins can't predict seeds
Replay attacksRound-ID binding prevents reuse

Trusted Components

ComponentTrust Level
Solana runtimeConsensus security
ICP subnetThreshold cryptography
Authority walletOperational security
Smart contractAudited code

Feature Flags

For testing, verification can be disabled:

#[cfg(not(feature = "seed-verification"))]
{
// Skip verification in test builds
return Ok(());
}

Warning: Production builds MUST enable seed-verification.

Next Steps