// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /** * @title QuantumRandomnessOracle * @dev A decentralized oracle that provides verifiable quantum randomness * to blockchain applications using a two-phase commit-reveal scheme. * * Flow: * 1. User calls requestRandomness() and pays a fee. * 2. Oracle node generates quantum randomness off-chain, computes * commitment = keccak256(abi.encodePacked(randomness)), and calls * submitCommitment(requestId, commitment). * 3. After COMMIT_REVEAL_DELAY blocks the oracle calls * fulfillRandomness(requestId, randomness) which verifies the * preimage, stores the result, and invokes the requester callback. */ contract QuantumRandomnessOracle { struct Request { address requester; uint256 fee; bytes32 commitment; // keccak256(abi.encodePacked(randomness)) uint256 blockNumber; uint256 commitBlock; // block at which commitment was submitted bool fulfilled; uint256 randomness; uint256 timestamp; } // State variables mapping(uint256 => Request) public requests; uint256 public requestCounter; address public owner; address public oracleNode; uint256 public fee; uint256 public constant FULFILLMENT_GAS_LIMIT = 200000; uint256 public constant COMMIT_REVEAL_DELAY = 2; // blocks between commit and reveal // Events event RandomnessRequested( uint256 indexed requestId, address indexed requester, uint256 feePaid ); event CommitmentSubmitted( uint256 indexed requestId, bytes32 commitment ); event RandomnessFulfilled( uint256 indexed requestId, uint256 randomness, uint256 entropyBits ); event OracleNodeUpdated(address indexed oldNode, address indexed newNode); event FeeUpdated(uint256 oldFee, uint256 newFee); // Modifiers modifier onlyOwner() { require(msg.sender == owner, "Only owner can call this function"); _; } modifier onlyOracleNode() { require(msg.sender == oracleNode, "Only oracle node can fulfill requests"); _; } /** * @dev Constructor to initialize the contract * @param _fee The fee required to request randomness * @param _oracleNode The address of the oracle node */ constructor(uint256 _fee, address _oracleNode) { owner = msg.sender; fee = _fee; oracleNode = _oracleNode; requestCounter = 0; } /** * @dev Request quantum randomness from the oracle * @return requestId The unique identifier for this request */ function requestRandomness() external payable returns (uint256 requestId) { require(msg.value >= fee, "Insufficient fee paid"); requestId = requestCounter++; requests[requestId] = Request({ requester: msg.sender, fee: msg.value, commitment: bytes32(0), blockNumber: block.number, commitBlock: 0, fulfilled: false, randomness: 0, timestamp: block.timestamp }); emit RandomnessRequested(requestId, msg.sender, msg.value); return requestId; } /** * @dev Phase 1 -- Submit a commitment (hash of the quantum randomness). * Must be called before fulfillRandomness. * @param requestId The request ID to commit for * @param commitment keccak256(abi.encodePacked(randomness)) */ function submitCommitment( uint256 requestId, bytes32 commitment ) external onlyOracleNode { Request storage request = requests[requestId]; require(request.requester != address(0), "Invalid request ID"); require(!request.fulfilled, "Request already fulfilled"); require(request.commitment == bytes32(0), "Commitment already submitted"); request.commitment = commitment; request.commitBlock = block.number; emit CommitmentSubmitted(requestId, commitment); } /** * @dev Phase 2 -- Reveal the randomness and fulfill the request. * The commitment must have been submitted at least COMMIT_REVEAL_DELAY * blocks ago. * @param requestId The request ID to fulfill * @param randomness The quantum-generated randomness value */ function fulfillRandomness( uint256 requestId, uint256 randomness ) external onlyOracleNode { Request storage request = requests[requestId]; require(!request.fulfilled, "Request already fulfilled"); require(request.requester != address(0), "Invalid request ID"); require(request.commitment != bytes32(0), "Commitment not yet submitted"); require( block.number >= request.commitBlock + COMMIT_REVEAL_DELAY, "Commit-reveal delay not elapsed" ); // Verify the preimage matches the stored commitment require( keccak256(abi.encodePacked(randomness)) == request.commitment, "Commitment mismatch" ); // Update request state request.randomness = randomness; request.fulfilled = true; // Deliver randomness to requester via callback (bool success, ) = request.requester.call{gas: FULFILLMENT_GAS_LIMIT}( abi.encodeWithSelector(this.receiveRandomness.selector, requestId, randomness) ); // If the callback fails, refund the requester if (!success) { uint256 amountToTransfer = request.fee; (bool refundSuccess, ) = request.requester.call{value: amountToTransfer}(""); require(refundSuccess, "Refund failed"); } emit RandomnessFulfilled(requestId, randomness, 256); } /** * @dev Callback function to receive randomness * @param requestId The request ID * @param randomness The randomness value */ function receiveRandomness(uint256 requestId, uint256 randomness) external { require(requests[requestId].requester == msg.sender, "Only requester can receive randomness"); } /** * @dev Withdraw accumulated fees (owner only) */ function withdrawFees() external onlyOwner { uint256 balance = address(this).balance; require(balance > 0, "No balance to withdraw"); (bool success, ) = owner.call{value: balance}(""); require(success, "Withdrawal failed"); } /** * @dev Update the oracle node address (owner only) * @param newOracleNode The new oracle node address */ function updateOracleNode(address newOracleNode) external onlyOwner { require(newOracleNode != address(0), "Invalid oracle node address"); address oldOracleNode = oracleNode; oracleNode = newOracleNode; emit OracleNodeUpdated(oldOracleNode, newOracleNode); } /** * @dev Update the request fee (owner only) * @param newFee The new fee amount */ function updateFee(uint256 newFee) external onlyOwner { uint256 oldFee = fee; fee = newFee; emit FeeUpdated(oldFee, newFee); } /** * @dev Get request details * @param requestId The request ID * @return Request struct containing all request details */ function getRequest(uint256 requestId) external view returns (Request memory) { return requests[requestId]; } /** * @dev Check if a request is fulfilled * @param requestId The request ID * @return true if fulfilled, false otherwise */ function isRequestFulfilled(uint256 requestId) external view returns (bool) { return requests[requestId].fulfilled; } /** * @dev Get the current contract balance * @return The contract's ETH balance */ function getBalance() external view returns (uint256) { return address(this).balance; } }