Interactive Smart Contract Demo
A trustee-free smart contract that lets whistleblowers prove unauthorized access to addresses a sovereign wealth fund claims to exclusively control. Walk through each step to see how cryptographic guarantees replace institutional promises.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /** * @title CustodyWhistleblower * @notice Trustee-free mechanism for proving custody misrepresentation * @dev Implements commit-reveal with rebuttal window * * This contract enforces "commission-fairness": either the * specified conditions are met and payment occurs, or neither. * The whistleblower provides only a pseudonymous address, never an identity. */ contract CustodyWhistleblower { // Reward locked in escrow (set at deployment) uint256 public immutable rewardAmount; // The fund address being challenged address public targetFundAddress; // Timing parameters uint256 public constant REVEAL_DEADLINE = 24 hours; uint256 public constant REBUTTAL_WINDOW = 48 hours; // Commitment structure struct Commitment { bytes32 commitHash; // Hash of (claimedAddress, rewardAddress, nonce) uint256 timestamp; // When commitment was made uint256 challengeBlock; // Block used for challenge generation bool revealed; bool verified; } mapping(address => Commitment) public commitments; // Challenge for signature verification bytes32 public challenge; uint256 public challengeTimestamp; // Contract state enum State { Accepting, Revealing, Rebuttal, Resolved } State public currentState; event CommitmentMade(address indexed whistleblower, bytes32 commitHash, uint256 timestamp); event ChallengeGenerated(bytes32 challenge, uint256 blockNumber); event ClaimRevealed(address indexed whistleblower, address claimedAddress); event RebuttalSuccessful(address indexed fundAddress); event RewardPaid(address indexed whistleblower, uint256 amount); constructor(address _targetFund) payable { rewardAmount = msg.value; targetFundAddress = _targetFund; currentState = State.Accepting; } /// @notice Step 1: Submit encrypted commitment function submitCommitment(bytes32 _commitHash) external { require(currentState == State.Accepting, "Not accepting"); require(commitments[msg.sender].timestamp == 0, "Already committed"); commitments[msg.sender] = Commitment({ commitHash: _commitHash, timestamp: block.timestamp, challengeBlock: block.number + 1, revealed: false, verified: false }); emit CommitmentMade(msg.sender, _commitHash, block.timestamp); } /// @notice Step 2: Generate unpredictable challenge from future block function generateChallenge() external { require(currentState == State.Accepting, "Wrong state"); require(block.number > commitments[msg.sender].challengeBlock, "Wait for block"); // Challenge derived from block data that didn't exist at commitment time challenge = keccak256(abi.encodePacked( blockhash(commitments[msg.sender].challengeBlock), msg.sender, block.timestamp )); challengeTimestamp = block.timestamp; currentState = State.Revealing; emit ChallengeGenerated(challenge, commitments[msg.sender].challengeBlock); } /// @notice Step 3 & 4: Reveal claim and prove key possession function revealAndProve( address _claimedAddress, address _rewardAddress, bytes32 _nonce, bytes calldata _signature ) external { require(currentState == State.Revealing, "Not reveal phase"); require(block.timestamp < challengeTimestamp + REVEAL_DEADLINE, "Deadline passed"); // Verify commitment matches bytes32 expectedHash = keccak256(abi.encodePacked( _claimedAddress, _rewardAddress, _nonce )); require(expectedHash == commitments[msg.sender].commitHash, "Hash mismatch"); // Verify signature of challenge proves key access require(_verifySignature(_claimedAddress, challenge, _signature), "Invalid sig"); commitments[msg.sender].revealed = true; commitments[msg.sender].verified = true; currentState = State.Rebuttal; emit ClaimRevealed(msg.sender, _claimedAddress); } /// @notice Step 5 & 6: Fund can rebut by proving they also control the address function fundRebuttal(bytes calldata _signature) external { require(currentState == State.Rebuttal, "Not rebuttal phase"); require(block.timestamp < challengeTimestamp + REVEAL_DEADLINE + REBUTTAL_WINDOW, "Window closed"); // If fund can also sign the challenge, they still control the address require(_verifySignature(targetFundAddress, challenge, _signature), "Invalid rebuttal"); currentState = State.Resolved; emit RebuttalSuccessful(targetFundAddress); // Return funds to contract owner (the fund that created the bounty) } /// @notice Step 7: If no valid rebuttal, pay the whistleblower function claimReward(address payable _rewardAddress) external { require(currentState == State.Rebuttal, "Wrong state"); require(block.timestamp >= challengeTimestamp + REVEAL_DEADLINE + REBUTTAL_WINDOW, "Window open"); require(commitments[msg.sender].verified, "Not verified"); currentState = State.Resolved; _rewardAddress.transfer(rewardAmount); emit RewardPaid(msg.sender, rewardAmount); } function _verifySignature( address _signer, bytes32 _message, bytes memory _sig ) internal pure returns (bool) { // Standard ECDSA signature verification (bytes32 r, bytes32 s, uint8 v) = _splitSignature(_sig); return ecrecover(_prefixed(_message), v, r, s) == _signer; } // ... helper functions omitted for clarity }
Experience each step of the whistleblower smart contract. Enter your own values and see how cryptographic proofs work.
The fund has been notified. They must sign the same challenge to prove they still control the address.