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.
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
}
}
}