Full Report
A commit-reveal scheme is a mechanism to have a secret value on chain without actually disclosing it until it's necessary. This is useful since everything on the blockchain is public. The commit is a user setting a hash with unique values to them. This hash is generated from a secret value offline, which will be provided later. The reveal is a user specifying their secret value. Of course, we check that the offline hash matches the contract generated hash to ensure that the value is the proper one. At this point, a decision can be made on whatever values we are operating on. Why is this useful? Frontrunning can be used if a user submits a transaction where they will make a bunch of money from a secret. Additionally, secrets on the blockchain are public. To do this correctly, there is a commit window and a reveal window. These should never overlap; otherwise, frontrunning will be possible. Additionally, the hash should not just be based upon the value but the address making the call; this is to prevent simple replay attacks. The rest of this article is a code implementation of how this works. Overall, interesting scheme to work around a blockchain limitation.
Analysis Summary
# Best Practices: Commit-Reveal Schemes in Solidity
## Overview
Commit-reveal schemes address the "public by default" nature of blockchain data. They provide a cryptographic mechanism to submit secret data (like votes, auction bids, or puzzle answers) without revealing the content until a specific time. This prevents front-running, where attackers observe a pending transaction and submit their own with a higher gas fee to "steal" the secret or the opportunity.
## Key Recommendations
### Immediate Actions
1. **Salt Your Hashes:** Never hash a raw value alone. Required users to include a "salt" or "seed" value (randomness) in their offline hash generation to prevent brute-force dictionary attacks.
2. **Bind to Identity:** Force the inclusion of the `msg.sender` address within the hashed commitment. This prevents "Replay Attacks" where an observer copies your hash and submits it as their own.
3. **Strict Phase Separation:** Ensure the code logic strictly separates the "Commit" window from the "Reveal" window. Use `block.number` to enforce these deadlines.
### Short-term Improvements (1-3 months)
1. **Implement Block-based Deadlines:** Use robust time-tracking (e.g., `GUESS_DURATION_BLOCKS`) rather than rely on easily manipulated timestamps.
2. **Automated State Transfers:** Configure the smart contract to automatically reject any "Reveal" attempts made before the commit deadline has passed or after the reveal window has closed.
3. **UI/UX Guidance:** Build front-end tools that help users generate their hashes and store their "salt" locally, as losing the salt results in a permanent loss of the commitment.
### Long-term Strategy (3+ months)
1. **Economic Penalties (Slashing):** Implement "stake" requirements where users lose a deposit if they commit but fail to reveal, preventing users from selectively revealing only "winning" outcomes.
2. **Gas Optimization:** Review commitment storage costs. Use `mapping(address => bytes32)` to efficiently store user commitments without iterating over large arrays.
## Implementation Guidance
### For Small Organizations
- Use established templates for commit-reveal to avoid logical errors in the window transition logic.
- Focus on clear documentation for users regarding the "Reveal" phase, as the most common failure is users forgetting to come back to reveal their secret.
### For Medium Organizations
- Implement a "Prize/Claim" logic that tracks `totalPrize` and `claimed` status to ensure the contract remains solvent and funds are distributed fairly.
- Add event logging (`emit`) for both Commit and Reveal phases to allow for off-chain monitoring and transparency.
### For Large Enterprises
- Conduct formal audits of the block-timer logic. Ensure that if a chain reorganization (reorg) occurs, the windows for commit/reveal are long enough to remain valid (e.g., set windows for 24-72 hours/16,500 blocks).
- Integrate Multi-Factor Authentication (MFA) or hardware wallet requirements for the "Creator" role to protect the contract's administrative functions.
## Configuration Examples
**Commitment Generation (Off-chain):**
javascript
// Example: Hashing secret + user address + salt
const commitment = ethers.utils.solidityKeccak256(
["uint256", "address", "uint256"],
[secretValue, userAddress, salt]
);
**Contract Timing Configuration:**
solidity
// Standard security durations
uint public constant COMMIT_PHASE = 16500; // ~3 days
uint public constant REVEAL_PHASE = 5500; // ~1 day
## Compliance Alignment
- **NIST SP 800-57:** Adheres to recommendations regarding cryptographic hash functions and secret management.
- **ISO/IEC 27001:** Supports non-repudiation and data integrity controls.
- **SWC Registry (Smart Contract Weakness Classification):** Mitigates SWC-114 (Transaction Order Dependence) and SWC-117 (Signature Malleability/Replay).
## Common Pitfalls to Avoid
- **Overlapping Windows:** Allowing commits while revealing has started enables attackers to see a reveal and then "properly" commit a winning value.
- **Hardcoding Secrets:** Never put the "answer" or "secret" in the contract code or constructor parameters.
- **Insufficient Salt:** Using a simple 1-digit salt makes it trivial for attackers to pre-calculate all possible hash outcomes.
## Resources
- **Solidity Documentation:** [docs.soliditylang[.]org]
- **SWC Registry:** [swcregistry[.]io]
- **OpenZeppelin Cryptography Libraries:** [github[.]com/OpenZeppelin/openzeppelin-contracts]