Interactive Smart Contract Demo

Custody Misrepresentation Whistleblower Mechanism

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.

WhistleblowerReward.sol

Solidity ^0.8.19
// 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
}

Walk Through the Mechanism

Experience each step of the whistleblower smart contract. Enter your own values and see how cryptographic proofs work.

1. Commit
2. Challenge
3. Reveal
4. Verify
5. Rebuttal
6. Resolve
1
Secret Commitment
Lock in your claim before anyone can react
Why this matters: You're about to prove that you have access to a private key the sovereign wealth fund claims only they control. But first, you submit an encrypted commitment so no one can front-run your claim or figure out which address you're targeting.
2
Challenge Generation
Contract creates unpredictable proof requirement
Why this matters: The contract generates a random challenge using blockchain data that didn't exist when you committed. This prevents you from using an old stolen signature—you must prove you have key access right now.
3
Reveal and Prove
Show your claim and sign the challenge
Why this matters: Now you reveal what you committed to and prove you actually have the key. Signing the challenge doesn't move any funds—it just proves access. This is the cryptographic equivalent of "I have the key to this door, watch me unlock it."
4
Verification
Contract checks everything matches
Why this matters: The contract now runs two critical checks: (1) Does your reveal hash to the same value you committed? (2) Is your signature valid for the challenge? Both must pass. No human judges involved—pure math.
5
Rebuttal Window
The fund has 48 hours to prove they also have the key
Why this matters: This defeats gaming by employees. If you legitimately work for the fund and have routine key access, the fund can easily rebut by providing their own signature. No payout if both parties can sign.
Rebuttal Window Status
OPEN — 48:00:00 remaining

The fund has been notified. They must sign the same challenge to prove they still control the address.

6
Resolution
Automatic, trustless outcome