Skip to main content

Timelock Encryption

Quickstart​

Installation​

Solidity​

We recommend foundry for managing your Solidity dependencies.

Install the latest timelock encryption Solidity library by running forge install blocklock-solidity.

If you're using Hardhat, you can also install it by running npm install blocklock-solidity.

Additional documentation for blocklock-solidity including supported blockchain networks and the proxy smart contract address for the blocklockSender.sol contract to call for on-chain timelock encryption requests can be found within the GitHub repository.

Typescript​

Install the latest timelock encryption library by running npm install blocklock-js.

This comes with the smart contract Typescript bindings out of the box and features which designed to simplify the process of generating encrypted data off-chain for the dcipher network.

It enables developers to securely encrypt data tied to a user-specified future chain height. The encrypted data can then be used to create on-chain timelock encryption requests in smart contracts. Once the specified chain height is mined, the user’s smart contract will automatically receive the decryption keys used for performing on-chain decryption.

Additional documentation for blocklock-js can be found within the GitHub repository.

Ciphertext creation off-chain and on-chain timelock encryption request​

Example: Encrypting a uint256 (4 ETH) for Decryption 2 Blocks Later​

This example demonstrates encrypting a uint256 value and sending it to a user smart contract that implements the createTimelockRequest function. In a different use case, e.g. sealed bid auction, this could be refactored into a sealedBid function. The example user smart contract source code can be found here.

First we connect to an RPC with a wallet, and create an instance of Blocklock and the user's smart contract through which the on-chain timelock encryption request will be made. This is so that the user's contract address receives the decryption key in a callback from the decryptionSender contract when the decryption block number has been mined.

import { ethers, getBytes } from "ethers";
import { Blocklock, SolidityEncoder, encodeCiphertextToSolidity } from "blocklock-js";
import { MockBlocklockReceiver__factory } from "../types"; // Users' solidity contract TypeScript binding

async function main() {
const rpc = new ethers.JsonRpcProvider(rpcAddr)
// User wallet
const wallet = new ethers.Wallet("your-private-key", rpc);
// Blocklockjs instance
const blocklockjs = new Blocklock(wallet, "blocklockSender contract proxy address");
// Initialise user contract
const mockBlocklockReceiver = MockBlocklockReceiver__factory.connect("user blocklcok receiver contract address", wallet);

Next step will be to perform the encryption and call the function in the user's smart contract (e.g., a sealed bid auction contract) that will make the on-chain timelock encryption request.

  // Set block height (current block + 2)
const blockHeight = BigInt(await ethers.provider.getBlockNumber() + 2);

// Value to encrypt (4 ETH as uint256)
const msg = ethers.utils.parseEther("4");

// Encode the uint256 value
const encoder = new SolidityEncoder();
const msgBytes = encoder.encodeUint256(msg);
const encodedMessage = getBytes(msgBytes);

// Encrypt the encoded message
const ciphertext = blocklockjs.encrypt(encodedMessage, blockHeight);

// Call `createTimelockRequest` on the user's contract
const tx = await mockBlocklockReceiver
.connect(wallet)
.createTimelockRequest(blockHeight, encodeCiphertextToSolidity(ciphertext));
const receipt = await tx.wait(1);

if (!receipt) {
throw new Error("Transaction has not been mined");
}

console.log("Timelock request created!");
}

main().catch((error) => {
console.error("Error:", error);
});

The blocklock-js library also supports encoding of other Solidity data types, e.g., string, bytes, bytes32, int256, address, array and struct.

note

If you're encrypting something that will be decrypted in a smart contract, 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.

Decryption on-chain​

By leveraging the blocklock-solidity library, developers can implement time-based data unlocking mechanisms securely within their smart contracts.

Importing​

To use this library in your project, import the required files into your contract and use the proxy contract address for BlocklockSender in the constructor as the blocklockContract parameter. All the smart contract addresses for supported blockchain networks can be found in the blocklock-solidity GitHub repository README file.

// Import the Types library for managing ciphertexts
import {TypesLib} from "blocklock-solidity/src/libraries/TypesLib.sol";
// Import the AbstractBlocklockReceiver for handling timelock decryption callbacks
import {AbstractBlocklockReceiver} from "blocklock-solidity/src/AbstractBlocklockReceiver.sol";

Example Usage of blocklock-solidity On-Chain for uint256 Ciphertext Decryption​

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {TypesLib} from "blocklock-solidity/src/libraries/TypesLib.sol";
import {AbstractBlocklockReceiver} from "blocklock-solidity/src/AbstractBlocklockReceiver.sol";

contract MockBlocklockReceiver is AbstractBlocklockReceiver {
uint256 public requestId;
TypesLib.Ciphertext public encryptedValue;
uint256 public plainTextValue;

constructor(address blocklockContract) AbstractBlocklockReceiver(blocklockContract) {}

function createTimelockRequest(uint256 decryptionBlockNumber, TypesLib.Ciphertext calldata encryptedData)
external
returns (uint256)
{
// Create timelock request
requestId = blocklock.requestBlocklock(decryptionBlockNumber, encryptedData);
// Store the Ciphertext
encryptedValue = encryptedData;
return requestId;
}

function receiveBlocklock(uint256 requestID, bytes calldata decryptionKey)
external
override
onlyBlocklockContract
{
require(requestID == requestId, "Invalid request id");
// Decrypt stored Ciphertext with the decryption key
plainTextValue = abi.decode(blocklock.decrypt(encryptedValue, decryptionKey), (uint256));
}
}

By default, blocklock provides a decryption key to you/your contract to perform the on-chain decryption yourself.

To decrypt in Typescript, you can perform the following:

const plaintext = await blocklockjs.decryptWithId(id)

where id is the requestID returned from the on-chain timelock encryption call blocklock.requestBlocklock(decryptionBlockNumber, encryptedData); in the user smart contract above.

Another Example: Write a smart contract that stores and decrypts encrypted moves for players of an on-chain game​

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 {TypesLib} from "blocklock-solidity/src/libraries/TypesLib.sol";
import {AbstractBlocklockReceiver} from "blocklock-solidity/src/AbstractBlocklockReceiver.sol";

contract MyGame {
mapping(address => TypesLib.Ciphertext) playerMoves;
mapping(uint256 => address) requestIDs;

function move(TypesLib.Ciphertext calldata encryptedMove) external {
require(this.playerMoves[msg.sender].v.length == 0, "player can only move once!");
// this move will be encrypted for the next 10 blocks
uint256 requestID = Blocklock.requestBlocklock(block.number + 10, encryptedMove);

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

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

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

Using Blocklock.decrypt automatically verifies the decrypt key for you, so you don't have to! (it's in fact a signature - learn more about the power of threshold signatures in a presentation from our CSO).