How to attach an encrypted file to your NFT
EFT Protocol definition
While starting FileMarket— a multifaceted platform that serves as a NFT2.0 storefront builder, cutting-edge marketplace, and a protocol for tokenizing, storing, and swapping files. At one point, we thought that it would be nice to have some private content linked to NFT to solve Ctrl+c — Ctrl+v problem. We’ve done some research and realized that there are no such technologies to have private content and the ability to transfer it while remaining hidden from the world. After that, we decided to design and implement a protocol for private NFT using encryption.
This article will describe our path to the solution and include many technical details, so one reading this article should be familiar with basic encryption concepts like symmetric and asymmetric encryption, NFT standards like ERC721 and ERC1155, and the Solidity language because there will be a lot of code!
The entire code of the FileMarket project is open source, it can be found in our Github organization. A demo version of our platform is also already available.
Basic concept
Our main goal was to make the protocol completely decentralized, which leads us to the file immutability requirement because with re-encryption file contents can be changed after minting. So, we decided to encrypt the file with any symmetric encryption and store the result. Thus, the transfer process will consist of symmetric encryption key transfer.
Of course, we can’t transfer the key in a direct way, so it also must be encrypted. We thought that asymmetric encryption suits us the best as the receiver will have his private key hidden and the sender will be able to encrypt the symmetric encryption key with the only public key of the receiver.
So, our first basic concept looks like this:
Fraud verifying
The case of confirmation of the transfer by the receiver does not require additional attention, but the case of rejection implies that something is wrong with the file. There are a few possible options:
- The encrypted file key is not valid (for example, an empty string or decrypted data is not a valid key).
- The encrypted key is valid, but the result of the decryption is not the same file that was originally encrypted. To verify this fact, the file is concatenated with its hash during encryption.
If none of these conditions are met, but the receiver declined the transfer, then this is fraud on the part of the receiver because he gained access to the encrypted file but did not accept the transfer, and this transfer could be part of some kind of transaction like the sale of NFTs.
Hence, the protocol should provide for the resolution of disputes in case of rejection of the transfer. That is, some trusted third party is needed to carry out the verification in this case. Schematically it looks like this:
Decentralized fraud verifying
Our next problem is to make the trusted third-party decentralized. To do this, the implementation technology must have the following properties:
- Have access to the data storage;
- Be able to perform encryption and decryption operations;
- With its help, the implementation of NFT must be possible.
Filecoin has all these properties:
- From FVM actors there is access to the Filecoin data storage;
- The implementation language of FVM actors — Rust makes it convenient to use encryption protocols. Using Solidity to implement encryption is difficult and inefficient — such operations will have a high cost in gas and a long execution time due to a large number of operations with the memory (you can read more about this issue in one of my previous articles);
- FEVM allows us to use Solidity implementations of NFT standards like ERC721 or ERC1155, while all encryption work will be done with FVM actors.
With Filecoin, our protocol looks like this:
Development process
Initially, we decided to divide the development into two parts:
- FVM actors for working with files and encryption;
- FEVM Solidity NFT smart contracts.
We entered the FVM Early Builders program and participated in the FEVM hackathon (unfortunately, we did not make it to the final). At the time of the start of development (August 2022), FVM was not yet fully completed, so we decided that we should implement Solidity smart contracts that we will deploy in Polygon and a Rust server that would listen to the blockchain and wait for fraud reports, after which it would conduct decryption attempt. This Rust server will be able to call the smart contract methods responsible for resolving disputes. The choice of the Rust language here is not accidental, it is needed in order to make it easier for us to convert this server into a FVM actor in the future.
So, the Polygon version with the Rust server looks like this:
And we decided to pack Solidity smart contracts into a standard and define them as interfaces. We named this standard Encrypted File Token or EFT. Next, we will consider the standard in more detail.
Encrypted File Token Standard
So, the main requirements for our standard:
- It must implement the token transfer pipeline.
- It must be possible to use an FVM actor for fraud verification when the result of the check is obtained in a single call and use a plain EVM smart contract (managed by a Rust server) as a source of dispute resolution.
- It must be possible to trigger some events when a transfer is canceled or completed. This is necessary to create other high-level applications that interact with the EFT standard, since passing an EFT cannot be an atomic operation.
To fulfill these requirements, we define 3 interfaces:
IEncryptedFileToken
— main standard for Encrypted NFT.IFraudDecider
— interface for fraud decider contract. It’s required to make this interface for the possibility of using FVM-actor and EVM contract (for Polygon version of our system), which will be called from the Rust server.IEncryptedFileTokenCallbackReceiver
— interface for contracts to implement for the ability to make some actions on transfer cancellation of finish.
Let’s start with IEncryptedFileTokenCallbackReceiver
as it is the simplest standard.
The IFraudDecider
interface is defined so that EFT instances can interact with both the FVM actor and to interact with the Polygon smart contract managed by our backend. To do this, the fraud check function returns two boolean values — the first determines whether a decision was made, and the second takes the value true if there is indeed a fraud act.
And finally, the token standard itself. We have omitted some obvious comments like “MUST revert if the token doesn’t exist” in this Gist to shorten it. Also, block with ERC721 transfers and instructions to make them revert always was omitted. Full interface definition and reference implementation can be found in our repository.
Examples
For a better understanding, let’s take a look at a few important examples: the implementation of IFraudDecider in our current version, which is controlled by our Rust server, and the implementation of the simplest exchange contract for selling EFT.
IFraudDecider
FraudDeciderWeb2
— A contract that implements the IFraudDecider
interface. It has a special lateDecision
method that is called from our Rust server. It is called when an attempt has been made to decrypt a file, and the verdict is passed in the arguments.
EFT Exchange
FileMarketExchange
— an example of a simple exchange for selling EFT. An order is created and at that moment an EFT transfer is created to lock the token (the creation of any other transfers will be impossible until the order completion of cancellation). Using the fulfillOrder
function, the buyer fixes his address for receiving EFT and locks the funds, and the functions of the IEncryptedFileTokenCallbackReceiver
interface allow the exchange contract to unlock the funds upon successful or unsuccessful completion of the transfer.
What’s next?
After the MVP version, our two main goals are:
- To implement our protocol on FVM/FEVM.
- To build developer tools for easy usage of our EFT standard.
We will continue development and are excited to share results in future parts of this article series. Thank you for reading and may the force be with you!
Useful links: