Skip to content Skip to footer

Task 1: Confidential Transactions

Let’s look how the encrypted transactions would look because the end to end encryption is something special in that regard.

You need to have a part of the key on your side you have  the other part of the key on the other side and when you combine the keys together  you get the shared secret used to encrypt or decrypt the transaction and the interesting thing is that with neither two pairs, the secret key will never travel across the network so no man of the middle attacks will be possible.  

How is this archived?

On the client side, for instance, using a command-line interface or MetaMask, you generate a key pair and request the ParaTime public key from the Web3 Gateway. This public key is essential, as it serves as the other half needed to execute your transaction. However, it’s crucial to ensure the integrity of the Web3 Gateway you’re using. A malicious gateway could provide you with a fake ParaTime public key, tricking you into encrypting your transaction in a way that seems secure but actually exposes your data to exploitation. This is why it’s highly recommended to run your own node and Web3 Gateway.

Once your transaction is encrypted and submitted to the Web3 Gateway, it relays the transaction to a compute node. The compute node then requests the private key from the key manager, combines it with the public key you generated, and derives a shared secret. This shared secret is used to decrypt your transaction, after which the computation is performed. The results are encrypted and sent back to you through the same channel, and you use the private part of your client key pair to decrypt the results.

While this process may seem complex, it’s quite straightforward for developers. All you need to do is import the Sapphire Hardhat npm package, which automatically wraps the Ethers library. Any subsequent calls you make will be encrypted and signed.

For front-end applications, you wouldn’t use Hardhat. Instead, you’d rely on a standard connector and our provided wrap function. Simply pass your provider (typically MetaMask, Brave, or similar) to the function, and it will wrap the provider. From that point onward, all communications will be automatically encrypted.

Now, let’s take a look at how setting and retrieving a message works. This functionality is already included in the demo project, so I’ll demonstrate the code and run the commands to set and retrieve the message. You can find the demo project here: Oasis Protocol Demo Starter.
For reference, here is the Hardhat configuration file: hardhat.config.ts.
One great feature Hardhat provides is the use of tasks, which are quite convenient.

For example, we’ve already used the deploy task, which is defined in the project.

Here’s how it works:

  • – It compiles the smart contract.
  • – Connects to the Sapphire testnet, as specified in the network parameter.
  • – Retrieves the TypeScript bindings for the MessageBox contract.
  • – Calls the deploy function on the smart contract object using the unwrapped provider (uw). The unwrapped provider is a plaintext provider that doesn’t use encryption in this case

Once the MessageBox contract is deployed, the task waits a moment and then outputs the contract’s address.

Now, let’s look at how to set a message using a specific task. TXhis task requires two inputs: the contract address (to connect to an existing contract) and the message content.

Here’s how it works:

  • The task doesn’t deploy a new contract but connects to an existing one by retrieving it using its address.
  • It compiles the contract and uses the getContractAt method to interact with it. Unlike the previous task, we don’t inject any special providers here—we simply use the original wrapped provider as is.
  • The setMessage transaction is then called, and in this case, the transaction is encrypted.
  • Finally, we wait for the transaction to complete and display the transaction hash.

Let’s try it out!

The transaction should also show in the explorer:

This is a contract call transaction and it is encrypted.

We can also retrieve the message using a function named message:

We transmitted an encrypted transaction. Congratulations!  

Now, the question arises: how private is this process?

If you examine the transaction details on the Explorer, you’ll notice that certain fields, such as the from and to fields, cannot be encrypted. This is because the network needs to know the sender and the target smart contract to route the transaction correctly, so this information is always visible.

Similarly, the gas price and gas used are also exposed. These details are crucial for the system to refund gas costs and are easily observable. Because of this, gas usage becomes a potential attack vector. To mitigate this, Sapphire provides gas padding functionality through pre-compiles. Using these, you can pad the gas usage to a fixed amount, making it harder for external observers to deduce which contract call was made. While the contract address remains visible, the specific details of the function call within the contract can be obscured.

However, there are other considerations to keep in mind:

To enhance privacy, you can design your contract so that all functions have similar payload sizes or signatures, minimizing the information that external observers can infer.
The payload size is also observable. For example, if one function in the contract takes ten parameters and another takes none, the transaction payload for the first will be noticeably larger.