diff --git a/Scarb.lock b/Scarb.lock index b73c3911..6cd8c91c 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -31,6 +31,10 @@ dependencies = [ "snforge_std", ] +[[package]] +name = "commit_reveal" +version = "0.1.0" + [[package]] name = "components" version = "0.1.0" diff --git a/listings/advanced-concepts/commit_reveal/.gitignore b/listings/advanced-concepts/commit_reveal/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/advanced-concepts/commit_reveal/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/advanced-concepts/commit_reveal/Scarb.toml b/listings/advanced-concepts/commit_reveal/Scarb.toml new file mode 100644 index 00000000..8a6c3454 --- /dev/null +++ b/listings/advanced-concepts/commit_reveal/Scarb.toml @@ -0,0 +1,15 @@ +[package] +name = "commit_reveal" +version.workspace = true +edition.workspace = true + +[dependencies] +starknet.workspace = true + +[dev-dependencies] +cairo_test.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] diff --git a/listings/advanced-concepts/commit_reveal/src/commit_reveal.cairo b/listings/advanced-concepts/commit_reveal/src/commit_reveal.cairo new file mode 100644 index 00000000..0d57c514 --- /dev/null +++ b/listings/advanced-concepts/commit_reveal/src/commit_reveal.cairo @@ -0,0 +1,71 @@ +#[starknet::interface] +pub trait ICommitmentRevealTrait { + fn commit(ref self: T, commitment: felt252); + fn reveal(self: @T, secret: felt252) -> bool; +} + +// ANCHOR: contract +#[starknet::contract] +pub mod CommitmentRevealTraits { + use starknet::storage::{StoragePointerWriteAccess, StoragePointerReadAccess}; + use core::hash::HashStateTrait; + use core::pedersen::PedersenTrait; + + #[storage] + struct Storage { + commitment: felt252, + } + + #[abi(embed_v0)] + impl CommitmentRevealTrait of super::ICommitmentRevealTrait { + fn commit(ref self: ContractState, commitment: felt252) { + self.commitment.write(commitment); + } + + fn reveal(self: @ContractState, secret: felt252) -> bool { + let hash = PedersenTrait::new(secret).finalize(); + self.commitment.read() == hash + } + } +} +// ANCHOR_END: contract + +#[cfg(test)] +mod tests { + use starknet::SyscallResultTrait; + use super::{ + CommitmentRevealTraits, ICommitmentRevealTraitDispatcher, + ICommitmentRevealTraitDispatcherTrait + }; + + use core::hash::HashStateTrait; + use core::pedersen::PedersenTrait; + use starknet::syscalls::deploy_syscall; + + fn deploy() -> ICommitmentRevealTraitDispatcher { + let (contract_address, _) = deploy_syscall( + CommitmentRevealTraits::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + ICommitmentRevealTraitDispatcher { contract_address } + } + + #[test] + fn commit_and_reveal() { + let mut contract = deploy(); + + // ANCHOR: offchain + // Off-chain, compute the commitment hash for secret + let secret = 'My secret'; + let offchain_commitment = PedersenTrait::new(secret).finalize(); + + // Commit on-chain + contract.commit(offchain_commitment); + + // Reveal on-chain and assert the result + let reveal_result = contract.reveal(secret); + // ANCHOR_END: offchain + assert_eq!(reveal_result, true); + } +} + diff --git a/listings/advanced-concepts/commit_reveal/src/lib.cairo b/listings/advanced-concepts/commit_reveal/src/lib.cairo new file mode 100644 index 00000000..e6dc85b7 --- /dev/null +++ b/listings/advanced-concepts/commit_reveal/src/lib.cairo @@ -0,0 +1 @@ +mod commit_reveal; diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 60b89e4a..d83439a1 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -74,6 +74,7 @@ Summary - [Writing to any storage slot](./advanced-concepts/write_to_any_slot.md) - [Struct as mapping key](./advanced-concepts/struct-mapping-key.md) - [Hashing](./advanced-concepts/hashing.md) +- [Commit-Reveal](./advanced-concepts/commit-reveal.md) - [Optimisations](./advanced-concepts/optimisations/optimisations.md) diff --git a/src/advanced-concepts/commit-reveal.md b/src/advanced-concepts/commit-reveal.md new file mode 100644 index 00000000..1d5d979b --- /dev/null +++ b/src/advanced-concepts/commit-reveal.md @@ -0,0 +1,38 @@ +# Commit-Reveal + +The Commit-Reveal pattern is a fundamental blockchain pattern that enables to: +1. Commit to a value without revealing it *(commit phase)* +2. Reveal the value later to prove they knew it in advance *(reveal phase)* + +Some use cases: +- **Blind Auctions**: Bidders commit to their bids first, then reveal them after the bidding period +- **Voting Systems**: Voters commit their votes early, revealing them only after voting ends +- **Knowledge Proofs/Attestations**: Proving you knew information at a specific time without revealing it immediately +- **Fair Random Number Generation**: Players commit to random numbers that get combined later, making it harder to manipulate the outcome + +## How It Works + +1. **Commit Phase**: + - User generates a value (`secret`) + - User creates a hash of this value + - User submits only the hash on-chain (`commit`) + +2. **Reveal Phase**: + - User submits the original value (`reveal`) + - Contract verifies that the hash of the submitted value matches the previously committed hash + - If it matches then it proves that the user knew the value at the commitment time + +## Minimal commit-reveal contract: + +```cairo +{{#rustdoc_include ../../listings/advanced-concepts/commit_reveal/src/commit_reveal.cairo:contract}} +``` + +Usage example: +```cairo +{{#include ../../listings/advanced-concepts/commit_reveal/src/commit_reveal.cairo:offchain}} +``` + +Some considerations: +- The commit phase must complete before any reveals can start +- Users might choose not to reveal if the outcome is unfavorable (consider adding stake/slashing mechanics to ensure reveals)