Skip to main content

Conditional Encryption

Quickstart​

Installation​

Solidity​

We recommend foundry for managing your Solidity dependencies.
Install the latest conditional encryption Solidity library by running forge install randa-mu/[email protected].

Typescript​

Install the latest conditional encryption library by running npm install @randamu/conditional-encryption.
This comes with Typescript types out of the box.

Encrypting a plaintext using Typescript​

By chain height​

The simplest condition is encrypting until some future block number. This can be achieved in Typescript with the following snippet:

import { ConditionalEncryption } from "@randamu/conditional-encryption"
import type { TimeCondition, ContractFieldCondition, And } from "./conditions"

import * as ethers from "ethers"

const plaintext = "hello world"
// to ensure we can use the bytes in the smart contract, we must encode them with the ETH ABI
const encodedPlaintext = ethers.utils.defaultAbiCoder.encode(["string"], [plaintext])

// here is a condition based on time
// `chainID: 31337n` is the `furnace` testnet ID as a `bigint`
const timeCondition: TimeCondition = { type: "time", chainHeight: 123456n, chainID: 31337n }

// here is a condition based on the value of some field in the smart contract
const countIncrementedCondition: ContractFieldCondition {
type: "contract_param",
address: "0x5cb3c5ba25c91d84ef5dabf4152e909795074f9958b091b010644cb9c30e3203", // the address of the contract you wish to monitor
field: "count(uint256)", // the field name and type
operator: "eq", // eq, neq, gt, gte, lt, lte -> equal, not equal, greater than, greater than or equal, less than, less than or equal
value: AbiCoder.defaultAbiCoder().encode(["uint256"], [1n]) // we need to encode the value as bytes here - make sure the type is the same as the `field`!
}

// `and` and `or` can be expressed; `and`s are 'stronger' binding than `or`s, thus x and y or z will evaluate to (x and y) or z.
const conditions: And = { type: "and", left: timeCondition, right: countIncrementedCondition }

// we now connect to an rpc or create a wallet by some means:
const wallet = new Wallet("0xsomePrivateKey")

// and create an encryption/decryption object
const encryption = new ConditionalEncryption(wallet)

// yay, we can finally create a ciphertext!
const ciphertext = await encryption.encrypt(encodedPlaintext, conditions)

The ciphertext and byte-encoded conditions are uploaded to the smart contract automatically.

note

To use a plaintext effectively inside a smart contract when decrypted, you should ensure its type matches a solidity ABI, and can be reconstructed using abi.decode().
We recommend using ethers.js to manage this, see their docs around the abi-coder.

Write a smart contract that stores and decrypts encrypted data​

First define a game object, such as a player move, that you wish to encrypt:

enum Move {
UP,
DOWN,
LEFT,
RIGHT
}

then create a contract that will store encrypted moves that have been uploaded by players:

import {ConditionalDecrypter} from "@randamu/conditional-encryption/ConditionalDecrypter.sol";

contract MyGame {
mapping(address => bytes) playerMoves;
mapping(uint256 => address) requestIDs;

function move(bytes encryptedMove, bytes conditions) external {
require(this.playerMoves[msg.sender].length == 0, "player can only move once!");
uint256 requestID = ConditionalDecrypter.register(encryptedMove, conditions);

this.requestIDs[requestID] = msg.sender;
this.playerMoves[msg.sender] = encryptedMove;
}
}

then create a contract that implements the ConditionalDecryptionReceiver interface, so the network can callback when the conditions have been met:

import {IDecryptionReceiver} from "@randamu/conditional-encryption/IDecryptionReceiver.sol";
import {ConditionalDecrypter} from "@randamu/conditional-encryption/ConditionalDecrypter.sol";

contract MyGame is IDecryptionReceiver {
mapping(address => bytes) playerMoves;
mapping(uint256 => address) requestIDs;

IDecryptionProvider decrypter = IDecryptionProvider()

// ..

function receiveDecryptedCipherText(uint256 requestID, bytes decryptedCiphertext) external {
require(this.requestIDs[requestID] != address(0), "such a request was never made");
require(this.playerMoves[this.requestIDs[requestID]].length > 0, "no such move");

bytes encryptedMove = this.playerMoves[requestIDs[requestID]];
bytes plaintextMove = ConditionalDecrypter.decrypt(encryptedMove, decryptionKey);
Move playerMove = abi.decode(plaintextMove, (Move));
// perform further game logic
}
}

Write a smart contract that decrypts multiple ciphertexts with a single key​

Conditions are created deterministically, so the same set of conditions results in the same bytes object. We can store conditions and reuse them for multiple ciphertexts.

import {ConditionalDecrypter} from "@randamu/conditional-encryption/ConditionalDecrypter.sol";

contract MyGame {
bytes conditions;
bytes[] ciphertexts;

function onReceiveDecryptionKey(uint256 requestID, bytes decryptionKey) {
for (int i = 0; i < ciphertexts.length; i++) {
bytes plaintext = ConditionalDecrypter.decrypt(this.ciphertexts[i], decryptionKey);
// do something with it
}
}
}