This document outlines the security considerations, trust assumptions, and risk mitigations for the Checkmate-Escrow smart contracts on Stellar Soroban.
- Oracle Trust Assumptions
- Oracle Compromise
- Admin Key Risks
- Re-initialization Protection
- Pause Mechanism
- Known Limitations
- Deployment Security
- Operational Security
The oracle system is a critical component that bridges off-chain chess game results to on-chain payouts. The following trust assumptions and mitigations apply:
Trusted Oracle: The oracle address configured during contract initialization is trusted to:
- Submit accurate game results from Lichess/Chess.com APIs
- Only submit results for valid, completed matches
- Not submit fraudulent or manipulated results
- Platform API Verification: The oracle must verify results against official Lichess/Chess.com APIs before submission
- Game ID Validation: Each
game_idcan only be used once (enforced on-chain viaDuplicateGameIderror) - Match State Validation: Results can only be submitted for matches that exist and are in the correct state
- Admin Oversight: Oracle admin can pause the oracle contract if suspicious activity is detected
- Result Immutability: Once submitted, results cannot be changed or overwritten
| Attack Vector | Protection |
|---|---|
| Oracle submits wrong winner | Platform API verification, game ID uniqueness |
| Oracle submits for non-existent match | On-chain validation (MatchNotFound error) |
| Oracle submits before deposits complete | State validation (NotFunded error) |
| Oracle key compromise | Admin can pause contract, rotate oracle address |
| Front-running result submission | Atomic result submission with proper sequencing |
If an attacker compromises the private key of the authorized oracle, they gain the ability to call the submit_result function as the oracle. The attacker can bypass the off-chain platform API verification checks entirely and submit fraudulent or manipulated match outcomes.
By submitting malicious results, the attacker can force the escrow contract to release locked funds to an arbitrary address, causing a fraudulent payout. This allows them to drain active escrows for matches that are currently funded but not yet settled.
- Contract Pausing: The escrow admin can immediately call
pauseto halt match resolution and prevent the malicious oracle from submitting further results. - Oracle Rotation: The escrow admin can rotate the compromised oracle key to a secure new address by calling the
update_oracleadmin function in the escrow contract. - Audit Trail / Event Emission: The
update_oraclefunction emits anoracle_upevent (with the topicadmin) containing theold_oracleandnew_oracleaddresses as data. This provides a clear, verifiable on-chain history of oracle rotation for indexing and alerting services.
To prevent and mitigate oracle compromise in production environments, the following hardening measures are recommended:
- Hardware Security Modules (HSM): Store the oracle private key in an HSM (e.g., AWS KMS or similar secure enclave) to prevent key extraction.
- Multi-signature Oracle: Transition from a single-key oracle to a multi-signature oracle or a consensus of multiple independent oracles, requiring multiple signatures before a result is considered valid on-chain.
Both contracts have admin addresses with elevated privileges. Compromise of admin keys represents a significant risk.
- Pause/Unpause: Can halt all contract operations (
create_match,deposit,submit_result) - Admin Rotation: Can change the admin address to a new one
- Emergency Control: Can intervene in case of detected issues
- Pause/Unpause: Can halt result submissions (and block
delete_resultwhile paused) - Admin Rotation: Can change the oracle admin address
- Result Submission: Can submit results directly (bypassing automated oracle)
- Result Deletion: Can remove a stored result via
delete_result. This is an irreversible on-chain action — see Result Deletion Policy for risks and expected use.
- Multi-sig Recommendation: Use multi-signature wallets for admin addresses in production
- Key Separation: Use different admin addresses for escrow and oracle contracts
- Cold Storage: Store admin keys offline when not in use
- Monitoring: Monitor admin operations via contract events
- Timelock: Consider implementing timelocks for critical admin operations
| Scenario | Impact | Mitigation |
|---|---|---|
| Escrow admin key stolen | Contract can be paused, funds locked | Immediate pause, admin rotation |
| Oracle admin key stolen | Incorrect results can be submitted | Pause oracle, rotate admin, dispute results |
| Both keys compromised | Complete contract takeover | Emergency pause, contract migration |
Both contracts implement strict re-initialization protection to prevent takeover after deployment.
// In initialize() function
if env.storage().instance().has(&DataKey::Oracle) {
panic!("Contract already initialized");
}- Single Initialization:
initialize()can only be called once per contract - Deployer Authorization: Only the account that deployed the contract can initialize it
- State Persistence: Initialization state is stored permanently in contract storage
- Front-run Prevention: Eliminates the window where observers could initialize with malicious parameters
- Immutable Configuration: Oracle and admin addresses cannot be changed via re-initialization
- Deployment Atomicity: Deploy + initialize should be done in the same transaction
Prior to fix [#216], contracts had no deployer guard, allowing front-running attacks where malicious actors could initialize contracts with their own admin/oracle addresses immediately after deployment.
The current contract surface uses a deliberate mix of explicit panics and typed errors:
initialize()in bothEscrowContractandOracleContractcontains an explicitpanic!("Contract already initialized")path when initialization is attempted a second time.- All other public contract operations return
Result<_, Error>and map failures to typed contract errors such asUnauthorized,MatchNotFound,ContractPaused,InvalidGameId, andAlreadySubmitted. - Authorization failures from
require_auth()are surfaced as contract errors through the Soroban runtime, not as Rust panics. get_result()returnsError::ResultNotFoundfor missing entries rather than panicking.
For client implementations, the preferred strategy is to use generated try_ wrappers where available. These wrappers convert contract error outcomes into host-language Result values and make it possible to handle failure paths explicitly.
Clients and test code should prefer try_ variants for contract invocation whenever possible:
- Use
try_initializeinstead ofinitializeto safely detect duplicate initialization without triggering a hard contract panic. - Use
try_submit_result,try_create_match,try_deposit, and similar wrappers so that authorization and state validation failures can be inspected programmatically. - When
try_wrappers are unavailable, ensure the host-side caller explicitly handles the contract error code produced by failed invocations.
This document should be revisited after issues #1 and #2 land to capture any contract or API surface changes that affect panic/error semantics and migration steps.
Both contracts implement emergency pause functionality for rapid response to security incidents.
Blocked Operations When Paused:
create_match()- New matches cannot be createddeposit()- Players cannot fund existing matchessubmit_result()- Oracle cannot submit results
Allowed Operations When Paused:
get_match()- Read-only operations continuecancel_match()- Players can still cancel pending matchesexpire_match()- Timeout-based expiration still works
Blocked Operations When Paused:
submit_result()- Result submissions are blocked
Allowed Operations When Paused:
has_result()- Read-only result checking continuesget_result()- Result retrieval continues
- Admin-Only Control: Only admin can pause/unpause
- Event Emission: Pause/unpause operations emit events for monitoring
- State Persistence: Pause state survives contract upgrades
- Granular Control: Different pause behaviors for different operations
- Detection: Monitor for suspicious activity (unusual transaction patterns, oracle delays)
- Immediate Pause: Admin pauses affected contract(s)
- Investigation: Analyze the incident while contract is paused
- Resolution: Either unpause after false alarm or migrate to new contract
- Post-mortem: Update security measures based on lessons learned
- No Native Token Support: Only supports Stellar assets (XLM, USDC), not native tokens
- Fixed Timeout: Match expiration timeout is hardcoded (~24 hours)
- No Partial Withdrawals: Players cannot withdraw partial stakes
- Single Oracle: Only one oracle address per escrow contract
- API Dependency: Relies on external Lichess/Chess.com APIs remaining available
- Rate Limiting: Subject to platform API rate limits
- Game Format: Only supports standard chess games, not variants
- Real-time Delay: Results submitted after games complete, not in real-time
- Stellar Network: Subject to Stellar network outages or congestion
- Soroban Limits: Bounded by Soroban resource limits (CPU, memory, storage)
- Ledger TTL: Contract state expires if not extended (~30 days)
- Admin Trust: Admin keys must be kept secure (no on-chain enforcement)
- Oracle Centralization: Single point of failure for result verification
- No Upgrade Path: No built-in contract upgrade mechanism
- Event Monitoring: Security depends on off-chain monitoring of contract events
- Verify contract bytecode matches audited source
- Test deployment on testnet with same parameters
- Prepare multi-sig admin addresses
- Set up monitoring for contract events
- Plan emergency response procedures
- Atomic Deploy+Init: Deploy and initialize in same transaction to prevent front-running
- Address Validation: Verify all addresses (oracle, admin) before deployment
- Network Verification: Double-check target network (mainnet vs testnet)
- Backup Keys: Ensure admin keys are backed up securely
# Verify initialization
stellar contract invoke --id $ESCROW_ID -- get_admin
stellar contract invoke --id $ORACLE_ID -- get_admin
# Test basic functionality
stellar contract invoke --id $ESCROW_ID -- is_paused- Hardware Security Modules (HSM): Use HSMs for admin key storage
- Key Rotation: Regularly rotate admin and oracle keys
- Access Controls: Limit who has access to operational keys
- Backup Procedures: Secure, tested key recovery procedures
Critical Events to Monitor:
- Contract pause/unpause events
- Admin address changes
- Unusual transaction patterns
- Oracle submission delays
- API failures
Recommended Alerts:
- Multiple failed result submissions
- Unexpected contract state changes
- Large stake amounts
- Rapid match creation
- Detection: Automated monitoring detects anomalies
- Assessment: Security team evaluates threat level
- Containment: Pause contracts if necessary
- Recovery: Restore normal operations or migrate
- Lessons Learned: Update security measures
- Quarterly security reviews
- Annual third-party audits
- Penetration testing of oracle infrastructure
- Code review of contract upgrades
For security-related issues or concerns:
- Critical Vulnerabilities: Report immediately via security@checkmate-escrow.com
- General Security Questions: security@checkmate-escrow.com
- Bug Bounty: See our bug bounty program at bounty.checkmate-escrow.com
- v1.0.0: Initial security documentation
- Last Updated: May 28, 2026 /home/farouq/Desktop/Checkmate-Escrow/docs/security.md