An IPC subnet for programmable encryption to-the-future.
Our entry for the EthGlobal HackFS 2024 Hackathon
Practical encryption to the future can be done using a blockchain in combination with witness encryption. A plaintext can is encrypted such that the decryption key is produced when a threshold of validators sign a chosen message. This is used by protocols such as tlock1 and McFLY2.
In these protocols the message is chosen to be a block height in the future. With particular blockchains such as DRAND it is guaranteed that the validators will sign every block height and the decryption key will be automatically generated and made public. The limitation of this is it only supports encryption to the future. No additional constraints can be placed on key generation.
Delorean protocol extends this idea to allow programmable conditions for decryption key release while maintaining the same (and in some cases better) security guarantees. Users can deploy Solidity smart contracts that encode the conditions under which the network operators must generate a decryption key.
Some examples might be:
- Fund Raising
- Key is released once the contract raises some amount of funds
- Hush Money
- Key is released if the contract does not receive a periodic payment
- Dead Man Switch
- Key is released the contract it doesn't receive a 'heartbeat' transaction from a preauthrized account for a number of blocks
- Encrypted Transactions / Encrypted Mempool
- Filecoin Deal integration
- Key is released once a particular Filecoin deal is discontinued
- Price Oracle integration
- Key is released if some asset reaches a given price
Being an IPC subnet Delorean is a fully fledged EVM chain that can also message other subnets in the wider network. This means conditions could be encoded that rely on other chains (e.g Filecoin deals).
We also developed a CLI that makes it easy to encrypt and decrypt files with keys tied to deployed condition contracts. Encryption and decryption takes place fully off-chain and does not require communication with the validators.
Delorean is implemented as an IPC Subnet allowing it to be easily bootstrapped and have access to assets from parent chains (e.g. FIL). It uses CometBFT for consensus and a modified version of the FVM for execution which communicates with the consensus layer via ABCI.
The feature of CometBFT that makes the protocol possible is the vote extension feature added in ABCI v0.38. In CometBFT validators vote on blocks to be finalized. Vote extensions allow the execution layer to enforce additional data to be included for votes to be valid. In Delorean the additional data is a BLS signature over the next tag in the queue (more detail on this later). If this signature is not included then the vote is invalid and cannot be used to finalize the block. The result is that the liveness of the chain becomes coupled to the release of these signed tags and inherits the same guarantees.
Deloran adds a new actor to the FVM that stores the queue of tags and allows contracts in the FEVM to pay gas to enqueue new tags. Once a tag is added to the queue the validators MUST include a signature over it in their next vote or else they are committing a consensus violation. The block proposer combines all of the signatures from the votes into an aggregate and includes an additional transaction which writes it back to the actor state. This aggregate signature is a valid decryption key for data encrypted to this tag!
A tag is defined as:
tag = SHA256(contractAddress || arbitaryData)
It is defined this way so someone else cannot write another contract which causes your key to be released. The tag (and hence decryption key itself) is tied to the address of the contract that manages it.
We use the threshold BLS Identify Based Encryption algorithm of Gailly, Melissaris and Yolan Romailler (2023) for encryption and decryption.
Encryption is done using the tag used in combination with the validator public keys. This takes place fully-offchain and without any communication with the validators as follows:
ciphertext = encrypt(message, aggValidatorKey, tag)
A decryption key is derived from a tag as:
key = aggregate([sign(tag, key) for key in validatorKeys])
and this is what the Delorean protocol produces after the contract triggers the call to release the key.
Note
We are using the non-threshold variant of the algorithm for this prototype so all validators must sign. A threshold version could be implemented by having the validators perform a key generation ceremony or by using a modified protocol such as the one used in McFLY - Döttling et al (2023)
Delorean builds upon the IPC subnet boilerplace provided by Protocol Labs
The work conducted during the hackathon can be viewed in this diff
This includes:
- Updating the runtime to support ABCI v0.38
- Developing a custom FVM actor (Delorean actor)
- Solidity contracts to interface with actor
- Modifications to runtime to implement the Delorean protocol
- Extend CometBFT votes with signatures
- Ensure votes have valid signatures
- Have block producer commit aggregate signature to actor state
- Delorean CLI
- encrypt/decrypt
Creating conditionally decryptable data with Delorean can be done as follows:
-
Create the Solidity contract encoding the key release conditions. This should call
DeloreanAPI.enqueueTag(<tag>)
only when the conditions are met. -
Deploy this contract to the Delorean subnet chain and obtain its contract address
-
Encrypt the data locally using the Delorean CLI
echo "secret message" | delorean encrypt <contract-address> -o encrypted.txt
Under the hood this retrieves the tag from the contract and validator aggregate BLS public keys by making RPC calls to the Delorean client
-
(optional) Upload the data to a public network such as IPFS or Filecoin
-
To attempt to decrypt data run the following
cat ./encrypted.txt | delorean decrypt <contract-address> -o decrypted.txt
This will look in the state and see if the decryption key for this data has been released. If so it will decrypt it otherwise it will error.
On Linux (links and instructions for Ubuntu):
- Install system packages:
sudo apt install build-essential clang cmake pkg-config libssl-dev protobuf-compiler git curl
. - Install Rust. See instructions.
- Install cargo-make:
cargo install --force cargo-make
. - Install Docker. See instructions.
- Install Foundry. See instructions.
On MacOS:
- Install Xcode from App Store or terminal: xcode-select --install
- Install Homebrew: https://brew.sh/
- Install dependencies: brew install jq
- Install Rust: https://www.rust-lang.org/tools/install (if you have homebrew installed rust, you may need to uninstall that if you get errors in the build)
- Install Cargo make: cargo install --force cargo-make
- Install docker: https://docs.docker.com/desktop/install/mac-install/
- Install foundry: https://book.getfoundry.sh/getting-started/installation
Run make
in the root of the repo before proceeding
The demo runs against a standalone network (not a subnet) with 4 validators.
To start the testnet run the following:
cd fendermint/testing/delorean-cli/
cargo make setup-cetf && cargo make node-1-setup && cargo make node-2-setup && cargo make node-3-setup
![INFO] If you want to add the network to MetaMask the RPC is
http://localhost:8545
and the Chain ID is2555887744985227
Wait a few minutes to build the node docker images and to set up the network. It is comprised of a number of docker containers which can be viewed in the docker desktop application or by running docker ps
View the logs of one of the validator nodes by running
cargo make follow-logs
in a separate shell
Install the CLI with
cargo install --path .
Then setup our demo with the following:
cargo make register-bls-keys
cargo make deploy-demo-contract
Set env vars for the deployed contract address to use in future commands
export CONTRACT_ADDRESS="0x....."
export DELORIAN_SECRET_KEY="./test-data/keys/volvo.sk"
The encrypt command takes a stream on std-in and encrypts it. Lets pipe a message to have it encrypted
echo 'Where we are going, we dont need centralized key registries!' | delorean-cli encrypt $CONTRACT_ADDRESS -o encrypted.txt
Take a look at the encrypted output by running cat encrypted.txt
. It uses the age encryption specification to encrypt large plaintexts.
Now to trigger the decryption key generation.
Deposit at least 88 FIL to the contract to enable the key release conditions
Then call the releaseKey
method
delorean-cli call-release-keys $CONTRACT_ADDRESS
The decrypt command can now be used to retrieve the generated key from the store and decrypt our data
cat ./encrypted.txt | delorean-cli decrypt $CONTRACT_ADDRESS
The security of the protocol relies on the following. Where possible these are compared with DRAND/tlock
-
The 2/3 of the network validators have not colluded to produce decryption keys in secret
- This is the same assumption as tlock although here it is slightly better. In tlock because the tags are block heights and hence predictable if the operators ever collude they can derive all possible future keys. In Delorean they can only derive all keys for known tags.
-
The protocol depends on the underlying security of the Threshold BLS encryption scheme of 1
-
Liveness of the key release inherits the same liveness guarantees as CometBFT. That is less than 1/3 of the total weight of the validator set is malicious or faulty
This scheme only supports encryption-to-the-future without programmable conditions. The underlying cryptography used is the same and inherits the same limitations (e.g. to support a threshold of signers these signers must collaborate to produce a threshold key)
As described in the paper this scheme also only supports non-programmable encryption-to-the-future. This protocol has the benefit that the signers need never communicate and encryption to a threshold signature can still be done by providing all signing keys at encryption time. This is much more flexible and allows validators to enter and leave the network.
We intend to move to using this cryptography once an implementation is complete (which we are working on)
LIT is the most similarly featured existing protocol. It supports programmable encryption with conditions defined as Javascript code that is run by the signers of the network. This supports fetching data from supported blockchains and oracles.
Delorean differs from LIT by being an open network with a permissionless validator set, and by having decryption conditions defined in the blockchain runtime itself rather than relying on external block data providers.
Delorean becomes particularly powerful when paired with ZKPs. This would allow the published ciphertext to have accompanying proofs about its content (e.g. that it will decrypt to a private key corresponding to a public key).
That way you could be certain if the keys are released after some funding goal is met that the data will actually decrypt. Even more useful would be if this can be extended to arbitrary ZK proofs about the encrypted data itself. This would allow all kinds of interesting applications (e.g. encrypted transactions).