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
👈 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
- Go to Remix: remix.ethereum.org.
- In the left sidebar, ensure Solidity Compiler and Deploy & Run Transactions are enabled.
2) Create the callback contract
- In the File Explorer, click New File → name it
DemoContract.sol. - 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
- Call
feeGenerate()and notefeeWei, the per-key fee in wei (foundmust betrue). - Expand
requestGenerate(...)and fill:id: e.g.,101transaction:0x(or any correlation bytes)
- 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.
- Click transact.
- Confirm in MetaMask: Confirm the transaction in MetaMask.
- Read results:
- In the deployed
DemoContractinstance callgetResultwith theid(e.g.101).
- In the deployed
👉 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
- Call
feeVerify()and notefeeWei, the per-key fee (foundmust betrue). - Expand
requestVerify(...)and fill:id: e.g.,102encryptedKey: paste the hex fromgetResultof the “Generate” step.
- Set Value to that
feeWei. - Click transact.
- Confirm in MetaMask: Confirm the transaction in MetaMask.
- Read results:
- In the deployed
DemoContractinstance callgetResultwith theid(e.g.102).
- In the deployed
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
- Call
feeDecrypt()and notefeeWei(foundmust betrue). - Expand
requestDecrypt(...)and fill:id: e.g.,103encryptedKey: paste the hex fromgetResultof the “Generate” step.transaction:0x
- Set Value to that
feeWei. - Click transact.
- Confirm in MetaMask: Confirm the transaction in MetaMask.
- Read results:
- In the deployed
DemoContractinstance callgetResultwith theid(e.g.103) and inspectkey.
- In the deployed
👉 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
feeWeireturned byfeeGenerate()orfeeVerify(); general generation and verification batches sendkeyCount * feeWei. Decryption sends thefeeWeireturned byfeeDecrypt()(units: wei). - Hex vs string:
encryptedKeyis bytes hex (starts with0x), 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
