Try the Decryption Oracle (Polygon or Ethereum) in Remix


What is Remix? Remix is a free, browser‑based IDE for Solidity smart contracts. You can write, compile, deploy, and call contracts directly in your browser and sign transactions with MetaMask (or another injected wallet). It’s ideal for quick trials—no local toolchain needed.


⚠️ Before you continue: Please read the Disclaimer.
By using this site, software, or contracts, you acknowledge that you have read and accepted it.

Deployment notice: The published addresses and fee panel still refer to version 2.5.7. This demo targets the 4.0.1 batch-verification/request-ID ABI and is incompatible with both those deployments and the tagged 3.0.0 service. Until a compatible deployment is published, use the local walkthrough with matching current sources.


What you’ll do

  • Deploy a tiny DemoContract contract in Remix (on Polygon PoS mainnet or Ethereum mainnet).
  • Let the DemoContract send requests to a user-deployed compatible Decryption Oracle:
    • Generate an encrypted key + hash,
    • optionally verify the encrypted key against the hash,
    • and finally decrypt it—end‑to‑end.

Note: To keep the example simple (1 file), the DemoContract serves as origin of the request to the decryption oracle and as the callback that receives the result.

Prerequisites

  • MetaMask (or a compatible wallet) in your browser.
  • Wallet connected to Polygon PoS (aka “Polygon Mainnet”) or Ethereum Mainnet with a little POL or ETH for gas.

Remark: If you do not have this already: add the MetaMask Plugin to Firefox and fund some POL or ETH to it. Ensure that you switched to the desired network.


Step‑by‑step in Remix

Try it on Remix

👈 If you click the badge, Remix will open, load DemoContract.sol and compile it;
in that case you can skip step 1), 2) and 3) and directly start at 👉 step 4) below.

1) Open Remix and select the plugins

  1. Go to Remix: remix.ethereum.org.
  2. In the left sidebar, ensure Solidity Compiler and Deploy & Run Transactions are enabled.

2) Create the callback contract

  1. In the File Explorer, click New File → name it DemoContract.sol.
  2. Copy and Paste the code below and Save.
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

/* ─────────────────────────────────────────────────────────────────────────────
 * Interfaces kept inline so the file stays single-file Remix-friendly
 * ────────────────────────────────────────────────────────────────────────────*/

interface IKeyDecryptionOracleCallback {
    struct EncryptedHashedKey {
        bytes32 keyId;
        bytes encryptedKey;
        bytes hashedKey;
    }

    event KeyReleased(address sender, uint256 requestId, bytes key);
    event DecryptionDenied(address sender, uint256 requestId);
    event EncryptedKeysVerificationCompleted(address sender, uint256 requestId, bool verified, EncryptedHashedKey[] keys, address receiverContract, bytes transaction);
    event EncryptedHashedKeysGenerated(address sender, uint256 requestId, EncryptedHashedKey[] keys, address receiverContract, bytes transaction);

    function onKeyReleased(uint256 requestId, bytes memory key) external;
    function onKeyDenied(uint256 requestId) external;
    function onEncryptedKeysVerificationCompleted(uint256 requestId, bool verified, EncryptedHashedKey[] calldata keys, address receiverContract, bytes calldata transaction) external;
    function onEncryptedHashedKeysGenerated(uint256 requestId, EncryptedHashedKey[] calldata keys, address receiverContract, bytes calldata transaction) external;
}

interface IKeyDecryptionOracle {
    struct EncryptedKey {
        bytes32 keyId;
        bytes encryptedKey;
    }

    event DecryptionRequested(address indexed sender, uint256 id, bytes encryptedKey, IKeyDecryptionOracleCallback indexed callback, bytes transaction, uint256 indexed requestId);
    event EncryptedKeysVerificationRequested(address indexed sender, uint256 id, EncryptedKey[] keys, IKeyDecryptionOracleCallback indexed callback, uint256 indexed requestId);
    event EncryptedHashedKeysGenerationRequested(address indexed sender, uint256 id, IKeyDecryptionOracleCallback indexed callback, address receiverContract, bytes transaction, bytes32[] keyIds, uint256 indexed requestId);

    function requestDecrypt(uint256 id, bytes memory encryptedKey, IKeyDecryptionOracleCallback callback, bytes memory transaction) external payable returns (uint256 requestId);
    function requestVerifyEncryptedKeys(uint256 id, EncryptedKey[] calldata keys, IKeyDecryptionOracleCallback callback) external payable returns (uint256 requestId);
    function requestGenerateEncryptedHashedKeys(uint256 id, IKeyDecryptionOracleCallback callback, address receiverContract, bytes calldata transaction, bytes32[] calldata keyIds) external payable returns (uint256 requestId);

    function fulfillDecryption(uint256 requestId, bytes memory key) external;
    function fulfillEncryptedKeysVerification(uint256 requestId, bool verified, IKeyDecryptionOracleCallback.EncryptedHashedKey[] calldata keys, address receiverContract, bytes calldata transaction) external;
    function fulfillEncryptedHashedKeysGeneration(uint256 requestId, IKeyDecryptionOracleCallback.EncryptedHashedKey[] calldata keys, address receiverContract, bytes calldata transaction) external;
}

/* ─────────────────────────────────────────────────────────────────────────────
 * DemoContract
 *  - Constructor: oracle address
 *  - 3 request methods: requestGenerate / requestVerify / requestDecrypt
 *  - 1 read method: getResult(id)
 *  - NEW: gas-free (view) fee readers via staticcall (robust if absent)
 * ────────────────────────────────────────────────────────────────────────────*/

contract DemoContract is IKeyDecryptionOracleCallback {
    bytes32 public constant DEMO_KEY_ID = keccak256("DemoContract.generatedKey");

    enum Kind   { Unknown, Generate, Verify, Decrypt }
    enum Status { None, Pending, Generated, Verified, Released, Denied, VerificationFailed }

    struct RequestMeta {
        Kind kind;
        address requester;
        uint256 createdAt;
    }

    struct Result {
        Status status;
        // Common payload slots; only some are set depending on status/kind
        bytes key;                 // when Released
        bytes encryptedKey;        // for Generated/Verified
        bytes hashedKey;           // for Generated/Verified
        bool verified;              // explicit verification outcome
        address receiverContract;  // for Generated/Verified
        bytes transaction;         // for Generated/Verified
        uint256 updatedAt;
    }

    IKeyDecryptionOracle public immutable oracle;

    mapping(uint256 => RequestMeta) public requests;   // id → meta
    mapping(uint256 => Result)      public results;    // id → result
    mapping(uint256 => uint256) private consumerIdByRequest;
    mapping(uint256 => bool) private requestPending;

    event Requested(uint256 indexed id, Kind kind, address indexed by);
    event ResultUpdated(uint256 indexed id, Status status);

    constructor(address oracleAddress) {
        require(oracleAddress != address(0), "oracle=0");
        oracle = IKeyDecryptionOracle(oracleAddress);
    }

    /*──────────────────────────────
     * 1) Generate (id chosen by user)
     *──────────────────────────────*/
    function requestGenerate(
        uint256 id,
        bytes calldata transaction
    ) external payable returns (uint256 requestId) {
        _init(id, Kind.Generate);
        bytes32[] memory keyIds = new bytes32[](1);
        keyIds[0] = DEMO_KEY_ID;
        requestId = oracle.requestGenerateEncryptedHashedKeys{value: msg.value}(
            id,
            IKeyDecryptionOracleCallback(address(this)), // callback for the result
            address(this),                             // this contract is eligible to decrypt
            transaction,
            keyIds
        );
        _rememberRequest(requestId, id);
        emit Requested(id, Kind.Generate, msg.sender);
    }

    /*──────────────────────────────
     * 2) Verify (id chosen by user)
     *──────────────────────────────*/
    function requestVerify(
        uint256 id,
        bytes calldata encryptedKey
    ) external payable returns (uint256 requestId) {
        _init(id, Kind.Verify);
        IKeyDecryptionOracle.EncryptedKey[] memory keys = new IKeyDecryptionOracle.EncryptedKey[](1);
        keys[0] = IKeyDecryptionOracle.EncryptedKey({
            keyId: DEMO_KEY_ID,
            encryptedKey: encryptedKey
        });
        requestId = oracle.requestVerifyEncryptedKeys{value: msg.value}(
            id,
            keys,
            IKeyDecryptionOracleCallback(address(this))
        );
        _rememberRequest(requestId, id);
        emit Requested(id, Kind.Verify, msg.sender);
    }

    /*──────────────────────────────
     * 3) Decrypt (id chosen by user)
     *──────────────────────────────*/
    function requestDecrypt(
        uint256 id,
        bytes calldata encryptedKey,
        bytes calldata transaction
    ) external payable returns (uint256 requestId) {
        _init(id, Kind.Decrypt);
        requestId = oracle.requestDecrypt{value: msg.value}(
            id,
            encryptedKey,
            IKeyDecryptionOracleCallback(address(this)),
            transaction
        );
        _rememberRequest(requestId, id);
        emit Requested(id, Kind.Decrypt, msg.sender);
    }

    /*──────────────────────────────
     * 4) Read back result by id
     *──────────────────────────────*/
    function getResult(uint256 id)
    external
    view
    returns (
        Status status,
        Kind kind,
        address requester,
        bytes memory key,
        bytes memory encryptedKey,
        bytes memory hashedKey,
        bool verified,
        address receiverContract,
        bytes memory transaction,
        uint256 createdAt,
        uint256 updatedAt
    )
    {
        RequestMeta storage m = requests[id];
        Result storage r = results[id];
        return (
        r.status,
        m.kind,
        m.requester,
        r.key,
        r.encryptedKey,
        r.hashedKey,
        r.verified,
        r.receiverContract,
        r.transaction,
        m.createdAt,
        r.updatedAt
        );
    }

    /*──────────────────────────────
     * Oracle → Callback hooks
     * (guarded by onlyOracle)
     *──────────────────────────────*/

    function onKeyReleased(uint256 requestId, bytes memory key) external override onlyOracle {
        uint256 id = _takeRequest(requestId, Kind.Decrypt);
        _mustExist(id);
        Result storage r = results[id];
        r.status    = Status.Released;
        r.key       = key;
        r.updatedAt = block.timestamp;
        r.hashedKey =  abi.encodePacked(keccak256(key));
        emit ResultUpdated(id, Status.Released);
    }

    function onKeyDenied(uint256 requestId) external override onlyOracle {
        uint256 id = _takeRequest(requestId, Kind.Decrypt);
        _mustExist(id);
        Result storage r = results[id];
        r.status    = Status.Denied;
        r.updatedAt = block.timestamp;
        emit ResultUpdated(id, Status.Denied);
    }

    function onEncryptedKeysVerificationCompleted(
        uint256 requestId,
        bool verified,
        EncryptedHashedKey[] calldata keys,
        address receiverContract,
        bytes calldata transaction
    ) external override onlyOracle {
        uint256 id = _takeRequest(requestId, Kind.Verify);
        _mustExist(id);
        require(keys.length == 1, "expected one key");
        require(keys[0].keyId == DEMO_KEY_ID, "unexpected key id");
        Result storage r = results[id];
        r.status           = verified ? Status.Verified : Status.VerificationFailed;
        r.encryptedKey     = keys[0].encryptedKey;
        r.hashedKey        = keys[0].hashedKey;
        r.verified         = verified;
        r.receiverContract = receiverContract;
        r.transaction      = transaction;
        r.updatedAt        = block.timestamp;
        emit ResultUpdated(id, r.status);
    }

    function onEncryptedHashedKeysGenerated(
        uint256 requestId,
        EncryptedHashedKey[] calldata keys,
        address receiverContract,
        bytes calldata transaction
    ) external override onlyOracle {
        uint256 id = _takeRequest(requestId, Kind.Generate);
        _mustExist(id);
        require(keys.length == 1, "expected one key");
        require(keys[0].keyId == DEMO_KEY_ID, "unexpected key id");
        Result storage r = results[id];
        r.status           = Status.Generated;
        r.encryptedKey     = keys[0].encryptedKey;
        r.hashedKey        = keys[0].hashedKey;
        r.verified         = false;
        r.receiverContract = receiverContract;
        r.transaction      = transaction;
        r.updatedAt        = block.timestamp;
        emit ResultUpdated(id, Status.Generated);
    }

    /*──────────────────────────────
     * Read-only fee passthroughs (gas-free via eth_call)
     *  - Safe across oracle variants: returns (found=false, 0) if method is absent.
     *──────────────────────────────*/

    // Common selectors (computed at compile time)
    bytes4 private constant SEL_FEE_DECRYPT = bytes4(keccak256("feeDecrypt()"));
    bytes4 private constant SEL_FEE_VERIFY  = bytes4(keccak256("feeVerify()"));
    bytes4 private constant SEL_FEE_GEN     = bytes4(keccak256("feeGenerate()"));

    /// @notice Reads oracle.feeDecrypt() if present.
    /// @return found whether the function exists & returned data
    /// @return feeWei fee in wei (0 if not found)
    function feeDecrypt() external view returns (bool found, uint256 feeWei) {
        return _readUint(SEL_FEE_DECRYPT);
    }

    /// @notice Reads oracle.feeVerify() if present.
    function feeVerify() external view returns (bool found, uint256 feeWei) {
        return _readUint(SEL_FEE_VERIFY);
    }

    /// @notice Reads the oracle's per-key feeGenerate() value if present.
    function feeGenerate() external view returns (bool found, uint256 feeWei) {
        return _readUint(SEL_FEE_GEN);
    }

    /// @notice Generic helper to read a uint256 from the oracle via selector.
    ///         Useful if you add more view getters to the oracle later.
    function readOracleUint(bytes4 selector) external view returns (bool found, uint256 value) {
        return _readUint(selector);
    }

    /// @dev Low-level robust reader (no revert if function missing).
    function _readUint(bytes4 selector) internal view returns (bool, uint256) {
        (bool ok, bytes memory data) = address(oracle).staticcall(abi.encodeWithSelector(selector));
        if (!ok || data.length < 32) return (false, 0);
        return (true, abi.decode(data, (uint256)));
    }

    /*──────────────────────────────
     * Helpers
     *──────────────────────────────*/

    event IdReused(uint256 indexed id, Status previousStatus, address indexed previousRequester);

    function _rememberRequest(uint256 requestId, uint256 id) internal {
        require(!requestPending[requestId], "requestId pending");
        consumerIdByRequest[requestId] = id;
        requestPending[requestId] = true;
    }

    function _takeRequest(uint256 requestId, Kind expectedKind) internal returns (uint256 id) {
        require(requestPending[requestId], "unknown requestId");
        id = consumerIdByRequest[requestId];
        require(requests[id].kind == expectedKind, "unexpected request kind");
        delete consumerIdByRequest[requestId];
        delete requestPending[requestId];
    }

    function _init(uint256 id, Kind kind) internal {
        require(id != 0, "id=0");

        // If the id is already used, check if it has completed...
        if (requests[id].createdAt != 0) {
            Status s = results[id].status;

            // ...if still pending, do not allow reuse.
            require(s != Status.Pending, "id pending");

            emit IdReused(id, s, requests[id].requester);

            // Clear id
            delete requests[id];
            delete results[id];
        }

        // (re-)initialize
        requests[id] = RequestMeta({
            kind: kind,
            requester: msg.sender,
            createdAt: block.timestamp
        });

        results[id].status = Status.Pending;
        results[id].updatedAt = block.timestamp;
    }

    function _mustExist(uint256 id) internal view {
        require(requests[id].createdAt != 0, "unknown id");
    }

    modifier onlyOracle() {
        require(msg.sender == address(oracle), "caller != oracle");
        _;
    }

    // Allow refunds / dust from the oracle
    receive() external payable {}
}

3) Compile

  • Open Solidity Compiler → set Compiler to 0.8.24 (or another compiler compatible with ^0.8.20).
  • Click Compile DemoContract.sol and ensure no errors.


4) Connect wallet and select environment

  • Ensure your wallet is on the desired network, either Polygon Mainnet or Ethereum Mainnet or Polygon Test Amoy or Ethereum Test Sepolia.
  • Open Deploy & Run Transactions panel.
  • Set Environment to Injected Provider – MetaMask. Approve the connection if prompted.

5) Deploy DemoContract

  • (Still in Deploy & Run Transactions panel)
  • In Contract, choose DemoContract.
  • In Deploy & Verify, add the Constructor arguments and paste an oracle address deployed from the same 4.0.1 sources. The published 2.5.7 addresses and tagged 3.0.0 ABI are incompatible with this demo.
  • Click Deploy.
  • Confirm in MetaMask: Confirm the transaction in MetaMask.

Open the Demo Contract under Deployed Contracts. You may now use its methods.


6) Generate an encrypted key through the demo wrapper

  1. Call feeGenerate() and note feeWei, the per-key fee in wei (found must be true).
  2. Expand requestGenerate(...) and fill:
    • id: e.g., 101
    • transaction: 0x (or any correlation bytes)
  3. In the Value field (top-right of the panel), input that per-key fee and set the unit to wei. This demo requests one key.
  4. Click transact.
  5. Confirm in MetaMask: Confirm the transaction in MetaMask.
  6. Read results:
    • In the deployed DemoContract instance call getResult with the id (e.g. 101).

👉 This will show you the encrypted key and the hash of the key - (encryptedKey, hashedKey, receiverContract, transaction). The demo wrapper submits a one-element keyIds array to the batch-oriented requestGenerateEncryptedHashedKeys API.

Note on getResult: If the returned status is 1, the result is still pending. Just try again a few seconds later. In rare cases it can take longer (minutes).


7) Optional: Verify a previously generated key

  1. Call feeVerify() and note feeWei, the per-key fee (found must be true).
  2. Expand requestVerify(...) and fill:
    • id: e.g., 102
    • encryptedKey: paste the hex from getResult of the “Generate” step.
  3. Set Value to that feeWei.
  4. Click transact.
  5. Confirm in MetaMask: Confirm the transaction in MetaMask.
  6. Read results:
    • In the deployed DemoContract instance call getResult with the id (e.g. 102).

The wrapper submits a one-element role-tagged batch to requestVerifyEncryptedKeys. Only accept the result when the explicit verified field is true.

👉 This will show you the contract that is eligible to receive the decryption (receiverContract) and the transaction (transaction) that are associated with the key and the hash of the key (hashedKey), without exposing the decrypted key.


8) Decrypt a previously generated key

  1. Call feeDecrypt() and note feeWei (found must be true).
  2. Expand requestDecrypt(...) and fill:
    • id: e.g., 103
    • encryptedKey: paste the hex from getResult of the “Generate” step.
    • transaction: 0x
  3. Set Value to that feeWei.
  4. Click transact.
  5. Confirm in MetaMask: Confirm the transaction in MetaMask.
  6. Read results:
    • In the deployed DemoContract instance call getResult with the id (e.g. 103) and inspect key.

👉 This will show you the decrypted key (key), for which its hash equals the hash returned by generate (hashedKey).


Common pitfalls

  • Wrong network: ensure correct wallet: Ethereum (1) mainnet or Polygon (137) mainnet or Amoy (80002).
  • No value sent: This one-key demo sends the per-key feeWei returned by feeGenerate() or feeVerify(); general generation and verification batches send keyCount * feeWei. Decryption sends the feeWei returned by feeDecrypt() (units: wei).
  • Hex vs string: encryptedKey is bytes hex (starts with 0x), not a UTF‑8 string.

Safety tips

  • Start with tiny test values and a throwaway account.
  • Never paste secrets in Remix or comments.
  • Verify contract addresses on an explorer before interacting.

Last updated: 2026‑08‑30