Workshop Task 1
Task 1: Confidential Transactions
okay let’s go so task number one confidential transaction
I prepared the demo statler repository
Task 1: Getting Started -cloning the repo-
Let’s look at it. Go to github.com/oasisprotocol/demo-starter okay so this is a mono repo. We have two packages, one is the back end and one is the front end.
The back end is there with the contract itself and tools to deploy the contract we will use hardhat.
For those not familiar, hardhat is a smart contract management tool written in typescript in JavaScript. You can use hardhat to deploy your contract, to run tests for example, to combine the private key of your accounts used for testing, and then that private key that account will be then used in actual tests or to deploy your smart contract and similar. The front end part we we’ll look at a little later, is the browser you know part to access your data so let’s go ahead and let’s clone this repository
There’s a Code button here just go ahead and copy it and I will use my command line git
clone and this repository
okay so let’s look let’s look at the contracts.
There is one contract called the message box and the extension is sol
which means the solidity language and this message box contract looks like this
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MessageBox {
string public message;
address public author;
function setMessage(string calldata in_message) external {
message = in_message;
author = msg.sender;
}
}
so there’s a public message stored inside the contract and the author of this message and the only function is the message you want to store and the message variable inside the on chain will now contain your message and the author field on the Chain will store the sender of this message so when you submit a transaction you also provide the public key of the transaction and sign it with your key
That’s actually how the distributed Network knows which fees to take from
and who is the center of the transaction This message dot Sender is just a syntactic solidity integer which gives you access to the address of the sender so the address is derived from the public key who signed the transaction
This is the contract we are going to use we’re going to deploy and play with and the next one
Okay, we will need metamask. I suggest you open your browser and if you haven’t yet you go ahead and install the metamask extension so head to https://www.metamask.io let’s download it to Chrome
Let’s create a new wallet you will need to come up with a password and let’s not do any backup for now
Awesome, okay let’s pin in the browser so we have a nice button here at the top so this is our metamask and you can see that it also generated one account for us. This is the address of our account ZX denominates that it will be a hex value emV is the the actual address so if you go to here you can see the full address of our account and this is not related to ethereum you know metamask says it’s connected to ethereum mainet but basically the account corresponds to key pair which is stored inside your metamask extension inside your browser so you can simply switch the networks and you will have the same key pairs the same account regardless of which network you use.
We will need to add the sapphire test net because we’ll be using that to deploy our contract so let’s go ahead and add it
One way is to edit manually but I’m lazy so I will head over to our documentation where we already prepared a magic button you just click on and then the network is added so had to create dApp to sapphire and then you have this Magic Buttons here you just add so we will use test net for now and I will add it so this is the chain ID the currency symbol
To obtain test tokens for the Oasis Sapphire testnet Oasis provides what is called a faucet. Using a faucet is a common way to receive test tokens for development and testing purposes without needing to purchase them on the mainnet.
Once you’ve correctly accessed the faucet at faucet.testnet.oasis.dev and submitted your funding request by providing your Metamask wallet address. After completing the verification step to confirm you’re not a robot, the request should be processed, and you should see the test tokens deposited into your account shortly.
If you’ve entered the correct wallet address and completed the verification successfully, you should expect to see the tokens appear in your account momentarily. If you encounter any issues or delays, double-check the address you provided and ensure that the verification process is completed accurately.
Once the tokens arrive, you can proceed with testing and development on the Oasis Sapphire testnet. Remember to exercise caution and avoid deploying any production services or real assets on the testnet, as it is subject to periodic wipes and may experience unexpected issues
oh there they are cool all right
Next Step alright so now we have test tokens now we have our account we will deploy our contract to sapphire testnet so let’s go line by line.
First we need to go to our backend folder and type in the pnpm install command which downloads all all the dependencies from the network from the internet and then we will run pmpm build
This build step will compile our solidity smart contract written in the solidity language to the bytecode. The bytecode will then be executed by the ethereum virtual machine inside sapphire and it will also produce some other things like the ABI the application binary interface this is like a list of all the functions your dApp or your browser can access and call
let’s go so I’m already in the backend folder this is okay and let’s see and run pnpm install this will download all the dependencies
I have pnpm build okay pnpm build
It works so if we look at what happened inside the contracts folder we have our solidity contract and then this abi folder was generated which contains this binary interface maybe we just look at it so we can see that this set message function is here it doesn’t return anything it accepts one string. This is something your dApp compilers then take and produce a nice interface in typescript that you just type in like my message box to set message and all the magic then happens behind the scenes. The transaction gets generated, it gets broadcast, it’s waited then for a while and so on.
Hardhat config file: (in the future the github will be linked)
import { promises as fs } from ‘fs’;
import path from ‘path’;
import ‘@nomicfoundation/hardhat-ethers’;
import ‘@oasisprotocol/sapphire-hardhat’;
import ‘@typechain/hardhat’;
import canonicalize from ‘canonicalize’;
import {JsonRpcProvider} from “ethers”;
import ‘hardhat-watcher’;
import { TASK_COMPILE } from ‘hardhat/builtin-tasks/task-names’;
import { HardhatUserConfig, task } from ‘hardhat/config’;
import ‘solidity-coverage’;
const TASK_EXPORT_ABIS = ‘export-abis’;
task(TASK_COMPILE, async (_args, hre, runSuper) => {
await runSuper();
await hre.run(TASK_EXPORT_ABIS);
});
task(TASK_EXPORT_ABIS, async (_args, hre) => {
const srcDir = path.basename(hre.config.paths.sources);
const outDir = path.join(hre.config.paths.root, ‘abis’);
const [artifactNames] = await Promise.all([
hre.artifacts.getAllFullyQualifiedNames(),
fs.mkdir(outDir, { recursive: true }),
]);
await Promise.all(
artifactNames.map(async (fqn) => {
const { abi, contractName, sourceName } = await hre.artifacts.readArtifact(fqn);
if (abi.length === 0 || !sourceName.startsWith(srcDir) || contractName.endsWith(‘Test’))
return;
await fs.writeFile(`${path.join(outDir, contractName)}.json`, `${canonicalize(abi)}\n`);
}),
);
});
// Unencrypted contract deployment.
task(‘deploy’)
.setAction(async (args, hre) => {
await hre.run(‘compile’);
// For deployment unwrap the provider to enable contract verification.
const uwProvider = new JsonRpcProvider(hre.network.config.url);
const MessageBox = await hre.ethers.getContractFactory(‘MessageBox’, new hre.ethers.Wallet(accounts[0], uwProvider));
const messageBox = await MessageBox.deploy();
await messageBox.waitForDeployment();
console.log(`MessageBox address: ${await messageBox.getAddress()}`);
return messageBox;
});
// Read message from the MessageBox.
task(‘message’)
.addPositionalParam(‘address’, ‘contract address’)
.setAction(async (args, hre) => {
await hre.run(‘compile’);
const messageBox = await hre.ethers.getContractAt(‘MessageBox’, args.address);
const message = await messageBox.message();
const author = await messageBox.author();
console.log(`The message is: ${message}, author: ${author}`);
});
// Set message.
task(‘setMessage’)
.addPositionalParam(‘address’, ‘contract address’)
.addPositionalParam(‘message’, ‘message to set’)
.setAction(async (args, hre) => {
await hre.run(‘compile’);
let messageBox = await hre.ethers.getContractAt(‘MessageBox’, args.address);
const tx = await messageBox.setMessage(args.message);
const receipt = await tx.wait();
console.log(`Success! Transaction hash: ${receipt!.hash}`);
});
// Hardhat Node and sapphire-dev test mnemonic.
const TEST_HDWALLET = {
mnemonic: “test test test test test test test test test test test junk”,
path: “m/44’/60’/0’/0”,
initialIndex: 0,
count: 20,
passphrase: “”,
};
const accounts = process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : TEST_HDWALLET;
const config: HardhatUserConfig = {
networks: {
hardhat: { // https://hardhat.org/metamask-issue.html
chainId: 1337,
},
‘sapphire’: {
url: ‘https://sapphire.oasis.io’,
chainId: 0x5afe,
accounts,
},
‘sapphire-testnet’: {
url: ‘https://testnet.sapphire.oasis.dev’,
chainId: 0x5aff,
accounts,
},
‘sapphire-localnet’: { // docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/sapphire-localnet -test-mnemonic
url: ‘http://localhost:8545’,
chainId: 0x5afd,
accounts,
},
},
solidity: {
version: ‘0.8.16’,
settings: {
optimizer: {
enabled: true,
runs: 200,
},
viaIR: true,
},
},
watcher: {
compile: {
tasks: [‘compile’],
files: [‘./contracts/’],
},
test: {
tasks: [‘test’],
files: [‘./contracts/’, ‘./test’],
},
coverage: {
tasks: [‘coverage’],
files: [‘./contracts/’, ‘./test’],
},
},
mocha: {
require: [‘ts-node/register/files’],
timeout: 50_000,
},
};
export default config;
Inside the artifact we also have data for contract verification so if you want to deploy a contract and you want to verify it then this is the file you need to upload to sourcify service or ether scan and so on.
Sapphire is not currently on ether scan but supposedly for any contract verification Services out there this artifacts contracts folder is the place where you want to look for this verification material right okay so the contract is now compiled
let’s deploy it
The deployment instructions are here.
There’s an environmental variable called private key where we will need to put the private key of our account so our account in metamask we will need to export that and then the command is npx hardhat deploy and on
which network
Sapphire test net okay let’s see so let’s open our metamask and let’s
export the private key
Account Details show Private key okay okay right this is our private key
Okay so let’s try it out, private key equals x and our hex value
of the private key and then the npx hardhat deploy which network well sapphire test net
This is the address (595Cc) where our contract was deployed to and now if you want to communicate with our contract this is the address that we need to provide each time and we can now launch our Explorer and see if this is really the case
If we go to explorer.oasis.io and type in the address it says that there are no results found on mainnet which is expected but there is one result on another network and it’s on test oh that’s nice and if you click on the the address you get some nice info like who who created the contract the V account so is this really our account x v yeah and what else so there is one
transaction related to this contract the contract creation transaction obviously it does not store any tokens any nfts, there are not token transfers yet no events okay and this transaction which created the contract is here it’s in plain format it’s not encrypted you know see this gray padlock here and yeah from two is shown here and the amount the fee which we needed to pay for the contract creation the amount of gas we used
okay cool
Task 1: Confidential Transactions
What’s with this format plane?
This is oasis sapphire specific. This is not anywhere inside ethereum. It is the end to end encryption I talked about previously so the contract creation transaction in our case was not encrypted but now 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 achieved?
On the client side, for example, in our command line interface or inside the metamask you generate a key pair and then you request from the web3 Gateway the paratime public key which you will use so this is the other part of the the key which you will use to execute the transaction and this is important because if you are connected to a malicious web Gateway
it can inject you with not the paratime public key and you would encrypt it you know in a good sense that this is confidential but the web three Gateway could exploit this and steal your data so this is why you should always run your own node and your own web3 gateway.
okay so you encrypt and submitted the transaction to your web three Gateway and the the gate relays that transaction to the compute node and then the compute node requests the private key from the key manager, combines it with the public key with your public key you generated yourself and then it deres the shared secret used to decrypt your transaction and then the computation is performed, the results computed and the encrypted result is returned back to you through through the same channel and then you use your secret part of your
of your client key pair to decrypt the result.
This sounds pretty complicated but actually it’s not that difficult.
The only thing from the developer point of view you need to do is you import our Sapphire hardhat npm package and it automatically wraps the ethers Library and whichever call you do afterwards it will be encrypted and signed,
This is specifically for hardhat if you are developing front- end app then you won’t use hardhat there you would use the standard connector and you you will use this wrap function we provide
Put your your provider there usually this is metamask or Brave or something and it will get wrapped and any communication afterwards will be automatically encrypted
okay so now let’s see how setting the message and retrieving the message works. This is already part of the demo project I will just demonstrate what the code looks like and then we will run the command to set the message and retrieve the message https://github.com/oasisprotocol/demo-starter
This is the hardhat config file actually but something hardhat offers and it’s pretty nice to use are so-called tasks so one tasks we already used is the deploy task and it’s defined
here so what it does is it compiled the contract it connected to the sapphire test net in in our case because this is what we we passed in the Network parameter it got the message box typescript bindings and called the deploy function on that smart contract object and it used our unwrapped provider the UW in this case is unwrapped provider this this means this is the original you know PL text provider it’s not using encryption and then when the
message box was deployed. It just waited a few moments and showed the address.
Now let’s say we want to set message this task here so this message, this task will expect the address this is the contract address we are connecting to and the message content will compile the contract, it will obtain the existing contract so it will not deploy the contract but just connect to an existing contract and it will call this set message transaction but in this case we will use this get contract at we will not inject any special special providers as we did here we will just use the original WRAp provider as it is and this set message transaction here should be encrypted.
Then we just wait for the transaction finish and show the transaction hash okay let’s try it out
Previously we used this private key equals something npx hard deploy now we will use setMessage and then we will need to provide the contract address this one and the message itself I will say like Hello World from the sapphire 101 Workshop
okay and now hopefully the transaction will pass and if everything works fine it should also pop up on the Explorer side.
The transaction seem to pass oh here it is look so this transaction went through and it’s a contract call kind of transaction and it’s encrypted the format is encrypted and if you look at the data itself.
If you take the ordinary ethereum transaction you should see something which is decodable inside the aski hello world from the sapphire workshop and here it’s encrypted using you know
the the key I mentioned before okay let’s move on
I retrieve the message well there is this function called message
work okay awesome so this is the stored message hello world from the sapphire one-oh-one and the author is ourselves okay
We transmitted an encrypted transaction congratulations!
Now the question is how private is this?
If you watch closely in the Explorer there’s the ‘from:’ and ‘to:’ field which cannot be encrypted because the computer needs to know which smart contract it needs to communicate with so you cannot hide that.
The gas price or the gas used is also important data because you will get your gas refunded and it’s pretty obvious how much gas the contract call will use and for this reason this is an important attack Vector we also provide the gas padding calls inside of the sapphire pre-compiles so you can just pad gas up front to a specific amount and then from the external Observer view they cannot know which contract call did you exactly you know call because it’s just the address of the contract but not inside the contract what did you call because the gas
is used the for any contract call out there for example if you program it this way and of course the payload is also known, the size of the contract call so if you have one function which
has like ten parameters and the other function which has has no parameters then of course the payout of the first transaction will will be much bigger and you cannot find it basically I mean you then need to prepare that each function will have the same signature in that regard
Task 1: Smart Privacy
Now we learned that there are confidential and non-confidential transactions and the task for us is, “Can we use the unencrypted transaction to set the message as well?” because as I mentioned earlier you there are cases where you want to publicly announce that there is some transaction out there which you transmitted and everyone should be able to see it and decode it and the answer is
-yes you can do that and basically we’ll just take the code from the deploy task here. If we do this and paste it here It will use the the unencrypted version
let’s try, Here we go so there seems to be another transaction and this transaction should be plain. Here it is so welcome this is this is smart privacy and now you can you know decode this transaction and it should clearly say that yeah well hello world from Sapphire 101 it’s you know obvious here that this is a string it’s not some random binary blob all right so we’re a bit short on time so I’ll just move forward to task two