diff --git a/.gitmodules b/.gitmodules index b1c953dd35..510d75b006 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ [submodule "external/cli"] path = external/cli url = https://github.com/oasisprotocol/cli +[submodule "external/sapphire-paratime"] + path = external/sapphire-paratime + url = https://github.com/oasisprotocol/sapphire-paratime diff --git a/README.md b/README.md index 5abf8856b4..6b8cf79550 100644 --- a/README.md +++ b/README.md @@ -179,23 +179,24 @@ number in the comment which added this redirection for future reference, if major rewrite is to happen and the developers would need more context around the redirection. -### Diagrams +### Images -There are three kinds of image assets currently used in the docs. +There are three kinds of image assets used in the docs. -1. Screenshots, photos, non-technical figures go into `images/` folder of the - respected top-level chapter. +1. Screenshots, photos, non-technical figures go into `images/` folder + on the part-level (i.e. `docs/dapp/images`). External repositories may use + own images in their respective folder. 2. [Mermaid](https://mermaid-js.github.io) diagrams (preferred tool for sequence diagrams, flowcharts and other technical material) live in - `diagrams/` folder of the respected top-level chapter. Diagram sources - reside in the .mmd files. To generate .svg which can be used in the + `diagrams/` folder of the respective part or the external repository. Diagram + sources reside in the .mmd files. To generate .svg which can be used in the markdown run `yarn diagrams`. Both .svg and .mmd files should be stored in git. CI will check that they are always in sync. 3. Other diagrams which cannot be designed with Mermaid are drawn in [diagrams.net](https://diagrams.net) and then stored locally inside `images/` folder along other images. When exporting the diagram, don't forget to tick the "Include a copy of my diagram" checkbox so that the - diagram source will be stored along in the .svg file so you will + diagram source will be stored along in the .svg file and you will be able to edit it in the future. ## Vocabulary diff --git a/docs/dapp/images/sapphire/gasless-gsn-flow.jpg b/docs/dapp/images/sapphire/gasless-gsn-flow.jpg deleted file mode 100644 index 4a3683560e..0000000000 Binary files a/docs/dapp/images/sapphire/gasless-gsn-flow.jpg and /dev/null differ diff --git a/docs/dapp/images/sapphire/gasless-on-chain-signer.svg b/docs/dapp/images/sapphire/gasless-on-chain-signer.svg deleted file mode 100644 index 6ca4a197c6..0000000000 --- a/docs/dapp/images/sapphire/gasless-on-chain-signer.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
1.
πŸ”’ call
makeProxyTransaction
(nonce, gasPrice, request, rsv)
1....
3.
Β πŸ”’eth.sendTransaction(tx)
3....
Gasless Client
(Node.js, Metamask, Oasis CLI)
Gasless Client...
5.
addr.call(tx)
5....
On-Chain Proxy
contract
On-Chain Proxy...
DAOv1
DAOv1
4.
decrypt tx
4....
Trust
Trust
2.
verify tx
2....
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/dapp/images/sapphire/hardhat-boilerplate-frontend1.png b/docs/dapp/images/sapphire/hardhat-boilerplate-frontend1.png deleted file mode 100644 index 8d08559908..0000000000 Binary files a/docs/dapp/images/sapphire/hardhat-boilerplate-frontend1.png and /dev/null differ diff --git a/docs/dapp/images/sapphire/hardhat-boilerplate-frontend2.png b/docs/dapp/images/sapphire/hardhat-boilerplate-frontend2.png deleted file mode 100644 index 49a293bba2..0000000000 Binary files a/docs/dapp/images/sapphire/hardhat-boilerplate-frontend2.png and /dev/null differ diff --git a/docs/dapp/images/sapphire/hardhat-boilerplate-frontend3.png b/docs/dapp/images/sapphire/hardhat-boilerplate-frontend3.png deleted file mode 100644 index 16c4f944fb..0000000000 Binary files a/docs/dapp/images/sapphire/hardhat-boilerplate-frontend3.png and /dev/null differ diff --git a/docs/dapp/images/sapphire/sourcify1.png b/docs/dapp/images/sapphire/sourcify1.png deleted file mode 100644 index b6dba793af..0000000000 Binary files a/docs/dapp/images/sapphire/sourcify1.png and /dev/null differ diff --git a/docs/dapp/images/sapphire/sourcify2.png b/docs/dapp/images/sapphire/sourcify2.png deleted file mode 100644 index 42b201dfe2..0000000000 Binary files a/docs/dapp/images/sapphire/sourcify2.png and /dev/null differ diff --git a/docs/dapp/images/sapphire/sourcify3.png b/docs/dapp/images/sapphire/sourcify3.png deleted file mode 100644 index ac32713f68..0000000000 Binary files a/docs/dapp/images/sapphire/sourcify3.png and /dev/null differ diff --git a/docs/dapp/sapphire/addresses.md b/docs/dapp/sapphire/addresses.md deleted file mode 100644 index 6fd24eb2bf..0000000000 --- a/docs/dapp/sapphire/addresses.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -description: List of Standard Contract Addresses ---- - -# Standard Contract Addresses - -| Name | Mainnet Address | Testnet Address | Verify | Source | -|--------------|--------------------------------------------|--------------------------------------------|------------------------------------------------------------------|---------------------------------| -| [Multicall V3][multicall] | `0xcA11bde05977b3631167028862bE2a173976CA11` | - | [Mainnet][multicall-verify-mainnet] | [Multicall3.sol][multicall-source] | -| Wrapped ROSE | `0x8Bc2B030b299964eEfb5e1e0b36991352E56D2D3` | `0xB759a0fbc1dA517aF257D5Cf039aB4D86dFB3b94` | [Mainnet][wrose-verify-mainnet], [Testnet][wrose-verify-testnet] | [WrappedROSE.sol][wrose-source] | - -[multicall-source]: https://github.com/mds1/multicall/blob/main/src/Multicall3.sol -[multicall-verify-mainnet]: https://sourcify.dev/#/lookup/0xcA11bde05977b3631167028862bE2a173976CA11 -[multicall]: https://multicall3.com/ - -[wrose-source]: https://github.com/oasisprotocol/sapphire-paratime/blob/main/contracts/contracts/WrappedROSE.sol -[wrose-verify-mainnet]: https://sourcify.dev/#/lookup/0x8Bc2B030b299964eEfb5e1e0b36991352E56D2D3 -[wrose-verify-testnet]: https://sourcify.dev/#/lookup/0xB759a0fbc1dA517aF257D5Cf039aB4D86dFB3b94 diff --git a/docs/dapp/sapphire/addresses.md b/docs/dapp/sapphire/addresses.md new file mode 120000 index 0000000000..8ef4fd10cf --- /dev/null +++ b/docs/dapp/sapphire/addresses.md @@ -0,0 +1 @@ +../../../external/sapphire-paratime/docs/addresses.md \ No newline at end of file diff --git a/docs/dapp/sapphire/authentication.md b/docs/dapp/sapphire/authentication.md deleted file mode 100644 index 341f1b2b50..0000000000 --- a/docs/dapp/sapphire/authentication.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -description: Authenticate users with your confidential contracts ---- - -# View-Call Authentication - -User impersonation on Ethereum and other "Transparent EVMs" isn't a problem -because **everybody** can see **all** data however the Sapphire confidential -EVM prevents contracts from revealing confidential information to the wrong -party (account or contract) - for this reason we cannot allow arbitrary -impersonation of any `msg.sender`. - -In Sapphire, there are four types of contract calls: - - 1. Contract to contract calls (also known as *internal calls*) - 2. Unauthenticted view calls (queries using `eth_call`) - 3. Authenticated view calls (signed queries) - 4. Transactions (authenticated by signature) - -Intra-contract calls always set `msg.sender` appropriately, if a contract calls -another contract in a way which could reveal sensitive information, the calling -contract must implement access control or authentication. - -By default all `eth_call` queries used to invoke contract functions have the -`msg.sender` parameter set to `address(0x0)`. In contrast, authenticated calls are -signed by a keypair and will have the `msg.sender` parameter correctly initialized -(more on that later). Also, when a transaction is -submitted it is signed by a keypair (thus costs gas and can make state updates) -and the `msg.sender` will be set to the signing account. - -## Sapphire Wrapper - -The [@oasisprotocol/sapphire-paratime][sp-npm] Ethereum provider wrapper -`sapphire.wrap` function will **automatically end-to-end encrypt calldata** when -interacting with contracts on Sapphire, this is an easy way to ensure the -calldata of your dApp transactions remain confidential - although the `from`, -`to`, and `gasprice` parameters are not encrypted. - -[sp-npm]: https://www.npmjs.com/package/@oasisprotocol/sapphire-paratime - -:::tip Unauthenticated calls and Encryption - -Although the calls may be unauthenticated, they can still be encrypted! - -::: - -However, if the Sapphire wrapper has been attached to a signer then subsequent -view calls via `eth_call` will request that the user sign them (e.g. a -MetaMask popup), these are called **signed queries** meaning `msg.sender` will be -set to the signing account and can be used for authentication or to implement -access control. This may add friction to the end-user experience and can result -in frequent pop-ups requesting they sign queries which wouldn't normally require -any interaction on Transparent EVMs. - -Let's see how Sapphire interprets different contract calls. Suppose the -following solidity code: - -```solidity -contract Example { - address owner; - constructor () { - owner = msg.sender; - } - function isOwner () public view returns (bool) { - return msg.sender == owner; - } -} -``` - -In the sample above, assuming we're calling from the same contract or account -which created the contract, calling `isOwner` will return: - - * `false`, for `eth_call` - * `false`, with `sapphire.wrap` but without an attached signer - * `true`, with `sapphire.wrap` and an attached signer - * `true`, if called via the contract which created it -* `true`, if called via transaction - -## Caching Signed Queries - -When using signed queries the blockchain will be queried each time, however -the Sapphire wrapper will cache signatures for signed queries with the same -parameters to avoid asking the user to sign the same thing multiple times. - -Behind the scenes the signed queries use a "leash" to specify validity conditions -so the query can only be performed within a block and account `nonce` range. -These parameters are visible in the EIP-712 popup signed by the user. Queries -with the same parameters will use the same leash. - -## Daily Sign-In with EIP-712 - -One strategy which can be used to reduce the number of transaction signing -prompts when a user interacts with contracts via a dApp is to use -[EIP-712][eip-712] to "sign-in" once per day (or per-session), in combination -with using two wrapped providers: - -[eip-712]: https://eips.ethereum.org/EIPS/eip-712 - - 1. Provider to perform encrypted but unauthenticated view calls - 2. Another provider to perform encrypted and authenticated transactions (or view calls) - - The user will be prompted to sign each action. - -The two-provider pattern, in conjunction with a daily EIP-712 sign-in prompt -ensures all transactions are end-to-end encrypted and the contract can -authenticate users in view calls without frequent annoying popups. - -The code sample below uses an `authenticated` modifier to verify the sign-in: - -```solidity -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -struct SignatureRSV { - bytes32 r; - bytes32 s; - uint256 v; -} - -contract SignInExample { - bytes32 public constant EIP712_DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - string public constant SIGNIN_TYPE = "SignIn(address user,uint32 time)"; - bytes32 public constant SIGNIN_TYPEHASH = keccak256(bytes(SIGNIN_TYPE)); - bytes32 public immutable DOMAIN_SEPARATOR; - - constructor () { - DOMAIN_SEPARATOR = keccak256(abi.encode( - EIP712_DOMAIN_TYPEHASH, - keccak256("SignInExample.SignIn"), - keccak256("1"), - block.chainid, - address(this) - )); - } - - struct SignIn { - address user; - uint32 time; - SignatureRSV rsv; - } - - modifier authenticated(SignIn calldata auth) - { - // Must be signed within 24 hours ago. - require( auth.time > (block.timestamp - (60*60*24)) ); - - // Validate EIP-712 sign-in authentication. - bytes32 authdataDigest = keccak256(abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - keccak256(abi.encode( - SIGNIN_TYPEHASH, - auth.user, - auth.time - )) - )); - - address recovered_address = ecrecover( - authdataDigest, uint8(auth.rsv.v), auth.rsv.r, auth.rsv.s); - - require( auth.user == recovered_address, "Invalid Sign-In" ); - - _; - } - - function authenticatedViewCall( - SignIn calldata auth, - ... args - ) - external view - authenticated(auth) - returns (bytes memory output) - { - // Use `auth.user` instead of `msg.sender`! - } -} -``` - -With the above contract code deployed, let's look at the frontend dApp and how -it can request the user to sign-in using EIP-712. You may wish to add additional -parameters which are authenticated such as the domain name. The following code -example uses Ethers: - -```typescript -const time = new Date().getTime(); -const user = await eth.signer.getAddress(); - -// Ask user to "Sign-In" every 24 hours. -const signature = await eth.signer._signTypedData({ - name: "SignInExample.SignIn", - version: "1", - chainId: import.meta.env.CHAINID, - verifyingContract: contract.address -}, { - SignIn: [ - { name: 'user', type: "address" }, - { name: 'time', type: 'uint32' }, - ] -}, { - user, - time: time -}); -const rsv = ethers.utils.splitSignature(signature); -const auth = {user, time, rsv}; -// The `auth` variable can then be cached. - -// Then in the future, authenticated view calls can be performed by -// passing auth without further user interaction authenticated data. -await contract.authenticatedViewCall(auth, ...args); -``` diff --git a/docs/dapp/sapphire/authentication.md b/docs/dapp/sapphire/authentication.md new file mode 120000 index 0000000000..912a6e5c27 --- /dev/null +++ b/docs/dapp/sapphire/authentication.md @@ -0,0 +1 @@ +../../../external/sapphire-paratime/docs/authentication.md \ No newline at end of file diff --git a/docs/dapp/sapphire/browser.md b/docs/dapp/sapphire/browser.md deleted file mode 100644 index 20ab2f9a1a..0000000000 --- a/docs/dapp/sapphire/browser.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -description: Writing Sapphire dApp for browser and Metamask ---- - -# Browser Support - -Confidential Sapphire dApps work in web browsers by wrapping the -Ethereum provider such as Metamask to enable signing and encrypting -calls and transactions. - -Let's begin with the [Hardhat boilerplate]. As mentioned on their website -the boilerplate provides the following: - -- The Solidity contract implementing an ERC-20 token -- Tests for the entire functionality of the contract -- A minimal React front-end to interact with the contract using ethers.js - -Go ahead and clone the original [Hardhat boilerplate repo]. Move to the checked -out folder and **apply the Sapphire-specific changes** to `hardhat.config.js` -as [described in the quickstart][quickstart]. - -Next, install dependencies. The boilerplate project uses -[pnpm], but `yarn` and `npm` will also work with some modifications -around workspaces: - -```shell npm2yarn -npm install -npm install -D @oasisprotocol/sapphire-paratime -``` - -Now, you can deploy the contract on the Testnet with the private key of the -account holding some [TEST tokens]: - -```shell -PRIVATE_KEY="0x..." npx hardhat run scripts/deploy.js --network sapphire-testnet -``` - -This will compile the contract and deploy it on the Testnet. In addition -to the quickstart steps, **the contract address and ABI will also automatically -be copied over to the `frontend/src/contracts` folder so that the frontend can -access them!** - -:::warning - -The contract in the Hardhat boilerplate is ERC-20-compatible and emits the -`transfer` event. If your wish to preserve confidentiality, you can comment -out [line 66]. Read [the guide](guide.mdx#contract-logs) to learn more. - -::: - -## Signing Sapphire Calls and Transactions in Browser - -Now, let's explore the frontend of our dApp. Begin by moving into the -`frontend` folder and install dependencies: - -```shell npm2yarn -npm install -npm install -D @oasisprotocol/sapphire-paratime -``` - -The main frontend logic is stored in `frontend/src/components/Dapp.js`. Apply -the following changes: - -```diff title="frontend/src/components/Dapp.js" ---- a/hardhat-boilerplate/frontend/src/components/Dapp.js -+++ b/hardhat-boilerplate/frontend/src/components/Dapp.js -@@ -2,6 +2,7 @@ - - // We'll use ethers to interact with the Ethereum network and our contract - import { ethers } from "ethers"; -+import * as sapphire from '@oasisprotocol/sapphire-paratime'; - - // We import the contract's artifacts and address here, as we are going to be - // using them with ethers -@@ -22,7 +23,7 @@ - // This is the Hardhat Network id that we set in our hardhat.config.js. - // Here's a list of network ids https://docs.metamask.io/guide/ethereum-provider.html#properties - // to use when deploying to other networks. --const HARDHAT_NETWORK_ID = '1337'; -+const HARDHAT_NETWORK_ID = '23295'; // Sapphire Testnet - - // This is an error code that indicates that the user canceled a transaction - const ERROR_CODE_TX_REJECTED_BY_USER = 4001; -@@ -225,14 +226,20 @@ - - async _initializeEthers() { - // We first initialize ethers by creating a provider using window.ethereum -- this._provider = new ethers.providers.Web3Provider(window.ethereum); -+ this._provider = sapphire.wrap(new ethers.providers.Web3Provider(window.ethereum)); - -- // Then, we initialize the contract using that provider and the token's -- // artifact. You can do this same thing with your contracts. -+ // Then, we initialize two contract instances: -+ // - _token: Used for eth_calls (e.g. balanceOf, name, symbol) -+ // - _tokenWrite: Used for on-chain transactions (e.g. transfer) - this._token = new ethers.Contract( - contractAddress.Token, - TokenArtifact.abi, -- this._provider.getSigner(0) -+ this._provider, -+ ); -+ this._tokenWrite = new ethers.Contract( -+ contractAddress.Token, -+ TokenArtifact.abi, -+ sapphire.wrap(new ethers.providers.Web3Provider(window.ethereum).getSigner()) - ); - } - -@@ -294,7 +301,7 @@ - - // We send the transaction, and save its hash in the Dapp's state. This - // way we can indicate that we are waiting for it to be mined. -- const tx = await this._token.transfer(to, amount); -+ const tx = await this._tokenWrite.transfer(to, amount); - this.setState({ txBeingSent: tx.hash }); - - // We use .wait() to wait for the transaction to be mined. This method -@@ -360,8 +367,8 @@ - return true; - } - -- this.setState({ -- networkError: 'Please connect Metamask to Localhost:8545' -+ this.setState({ -+ networkError: 'Please connect to Sapphire ParaTime Testnet' - }); - - return false; -``` - -Beside the obvious change to the chain ID and wrapping ethers.js objects with the -Sapphire wrapper you can notice that we initialized **two** contract -instances: - -- `this._token` object will be used for unsigned eth calls. This is the - Hardhat method of [setting `from` transaction field to all zeros] - [guide-transaction-calls] in order to avoid Metamask signature popups. - Although the call is unsigned, **it is still encrypted** with the - corresponding runtime key to preserve confidentiality. -- `this._tokenWrite` object will be used for signed calls and transactions. The - user will be prompted by Metamask for the signature. Both β€” calls and - transactions β€” will be encrypted. - -## Trying it - -Start the frontend by typing: - -```shell npm2yarn -npm run start -``` - -If all goes well the web server will spin up and your browser should -automatically open `http://localhost:3000`. - -![Hardhat boilerplate frontend](../images/sapphire/hardhat-boilerplate-frontend1.png) - -Go ahead and connect the wallet. If you haven't done it yet, you will have -to add the [Sapphire ParaTime Testnet network to your Metamask] -[sapphire-testnet]. Once connected, the frontend will make an unsigned call to -the `balanceOf` view and show you the amount of `MHT`s in your selected -Metamask account. - -![MHT balance of your account](../images/sapphire/hardhat-boilerplate-frontend2.png) - -Next, let's transfer some `MHT`s. Fill in the amount, the address and hit the -*Transfer* button. Metamask will show you the popup to sign and submit the -transfer transaction. Once confirmed, Metamask will both **sign and encrypt** the transaction. - -![Sign and encrypt the transfer transaction](../images/sapphire/hardhat-boilerplate-frontend3.png) - -Once the transaction is processed, you will get a notification from Metamask -and the balance in the dApp will be updated. - -:::tip - -If you commented out `emit Transfer(...)`, the transfer of `MHT`s would have -been completely confidential. In the example above, the [following transaction] -[block explorer] was generated. Go ahead and check your transaction on the -block explorer too, to make sure no sensitive data was leaked! - -::: - -Congratulations, you successfully implemented your first truly confidential -dApp which runs in the browser and wraps Metamask to both sign and encrypt the -transactions! - -Should you have any questions or ideas to share, feel free to reach out to us -on [discord and other social media channels][social-media]. - -:::info Example - -You can download a full working example from the [Sapphire ParaTime examples] -repository. - -::: - -[block explorer]: https://testnet.explorer.sapphire.oasis.dev/tx/0x3303dea5d48291d1564cad573f21fc71fcbdc2b862e17e056287fd9207e3bc53 -[guide-transaction-calls]: guide.mdx#transactions--calls -[Hardhat boilerplate repo]: https://github.com/NomicFoundation/hardhat-boilerplate -[Hardhat boilerplate]: https://hardhat.org/tutorial/boilerplate-project -[Hardhat tutorial]: https://hardhat.org/tutorial -[line 66]: https://github.com/NomicFoundation/hardhat-boilerplate/blob/13bd712c1285b2de572f14d20e6a750ae08565c0/contracts/Token.sol#L66 -[quickstart]: quickstart.mdx#add-the-sapphire-testnet-to-hardhat -[sapphire-testnet]: ./README.mdx#testnet -[Sapphire ParaTime examples]: https://github.com/oasisprotocol/sapphire-paratime/tree/main/examples/hardhat-boilerplate -[social-media]: ../../get-involved/README.md#social-media-channels -[pnpm]: https://pnpm.io -[TEST tokens]: quickstart.mdx#get-some-sapphire-testnet-tokens diff --git a/docs/dapp/sapphire/browser.md b/docs/dapp/sapphire/browser.md new file mode 120000 index 0000000000..1b1dfca35c --- /dev/null +++ b/docs/dapp/sapphire/browser.md @@ -0,0 +1 @@ +../../../external/sapphire-paratime/docs/browser.md \ No newline at end of file diff --git a/docs/dapp/sapphire/gasless.md b/docs/dapp/sapphire/gasless.md deleted file mode 100644 index 775690d504..0000000000 --- a/docs/dapp/sapphire/gasless.md +++ /dev/null @@ -1,388 +0,0 @@ ---- -description: Submitting transactions without paying for fees ---- - -# Gasless Transactions - -When you submit a transaction to a blockchain, you need to pay certain fee -(called *gas* in Ethereum jargon). Since only the transactions with the highest -fee will be included in the block, this mechanism effectively prevents denial -of service attacks on the network. On the other hand, paying for gas requires -from the user that they have certain amount of blockchain-native tokens -available in their wallet which may not be feasible. - -In this chapter we will learn how the user signs and sends their transaction to -a *relayer*. The relayer then wraps the original signed transaction into a new -*meta-transaction* (see [ERC-2771] for details), signs it and pays for the -necessary transaction fees. When the transaction is submitted the on-chain -recipient contract decodes the meta-transaction, verifies both signatures and -executes the original transaction. - -Oasis Sapphire supports two transaction relaying methods: The **on-chain -signer** exposes the Oasis-specific contract state encryption functionality -while the **gas station network** method is a standardized approach known in -other blockchains as well. - -:::caution - -The gas station network implementation on Sapphire is still in early beta. Some -features such as the browser support are not fully implemented yet. - -::: - -[ERC-2771]: https://eips.ethereum.org/EIPS/eip-2771 - -## On-Chain Signer - -The on-chain signer is a smart contract which receives the user's transaction, -checks whether the transaction is valid, wraps it into a meta-transaction -(which includes paying for the transaction fee) and returns it back to the user -in [EIP-155] format. These steps are executed as a confidential call. Finally, -the user submits the generated transaction to the network. - -![Diagram of the On-Chain Signing](../images/sapphire/gasless-on-chain-signer.svg) - -### EIP155Signer - -To sign a transaction, the Sapphire's `EIP155Signer` library bundled along the -`@oasisprotocol/sapphire-contract` package comes with the following helper which -returns a raw, RLP-encoded, signed transaction ready to be broadcast: - -```solidity -function sign(address publicAddress, bytes32 secretKey, EthTx memory transaction) internal view returns (bytes memory); -``` - -`publicAddress` and `secretKey` are the signer's address and their private key -used to sign a meta-transaction (and pay for the fees). We will store these -sensitive data inside the encrypted smart contract state together with the -signer's `nonce` field in the following struct: - -```solidity -struct EthereumKeypair { - address addr; - bytes32 secret; - uint64 nonce; -} -``` - -The last `transaction` parameter in the `sign()` function is the transaction -encoded in a format based on [EIP-155]. This can either be the original user's -transaction or a meta-transaction. - -### Gasless Proxy Contract - -The following snippet is a complete *Gasless* contract for wrapping the user's -transactions (`makeProxyTx()`) and executing them (`proxy()`). The signer's -private key containing enough balance to cover transaction fees should be -provided in the constructor. - -```solidity -import {EIP155Signer} from "@oasisprotocol/sapphire-contracts/contracts/EIP155Signer.sol"; - -struct EthereumKeypair { - address addr; - bytes32 secret; - uint64 nonce; -} - -struct EthTx { - uint64 nonce; - uint256 gasPrice; - uint64 gasLimit; - address to; - uint256 value; - bytes data; - uint256 chainId; -} - -// Proxy for gasless transaction. -contract Gasless { - EthereumKeypair private kp; - - function setKeypair(EthereumKeypair memory keypair) external payable { - kp = keypair; - } - - function makeProxyTx(address innercallAddr, bytes memory innercall) - external - view - returns (bytes memory output) - { - bytes memory data = abi.encode(innercallAddr, innercall); - - // Call will invoke proxy(). - return - EIP155Signer.sign( - kp.addr, - kp.secret, - EIP155Signer.EthTx({ - nonce: kp.nonce, - gasPrice: 100_000_000_000, - gasLimit: 250000, - to: address(this), - value: 0, - data: abi.encodeCall(this.proxy, data), - chainId: block.chainid - }) - ); - } - - function proxy(bytes memory data) external payable { - (address addr, bytes memory subcallData) = abi.decode( - data, - (address, bytes) - ); - (bool success, bytes memory outData) = addr.call{value: msg.value}( - subcallData - ); - if (!success) { - // Add inner-transaction meaningful data in case of error. - assembly { - revert(add(outData, 32), mload(outData)) - } - } - kp.nonce += 1; - } -} -``` - -:::tip - -The snippet above only runs on Sapphire Mainnet, Testnet or Localnet. -`EIP155Signer.sign()` is not supported on other EVM chains. - -::: - -### Simple Gasless Commenting dApp - -Let's see how we can implement on-chain signer for a gasless commenting dApp -like this: - -```solidity -contract CommentBox { - string[] public comments; - - function comment(string memory commentText) external { - comments.push(commentText); - } -} -``` - -Then, the TypeScript code on a client side for submitting a comment in a gasless -fashion would look like this: - -```typescript -const CommentBox = await ethers.getContractFactory("CommentBox"); -const commentBox = await CommentBox.deploy(); -const Gasless = await ethers.getContractFactory("Gasless"); -const gasless = await Gasless.deploy(); - -// Set the keypair used to sign the meta-transaction. -await gasless.setKeypair({ - addr: "70997970C51812dc3A010C7d01b50e0d17dc79C8", - secret: Uint8Array.from(Buffer.from("59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", 'hex')), - nonce: 0, -}); - -const innercall = commentBox.interface.encodeFunctionData('comment', ['Hello, free world!']); -const tx = await gasless.makeProxyTx(commentBox.address, innercall); - -const plainProvider = new ethers.providers.JsonRpcProvider(ethers.provider.connection); -const plainResp = await plainProvider.sendTransaction(tx); - -const receipt = await ethers.provider.waitForTransaction(plainResp.hash); -if (!receipt || receipt.status != 1) throw new Error('tx failed'); -``` - -:::info Example - -You can download a complete on-chain signer example based on the above snippets -from the [Sapphire ParaTime examples] repository. - -::: - -[Sapphire ParaTime examples]: - https://github.com/oasisprotocol/sapphire-paratime/tree/main/examples/onchain-signer - -### Gasless Proxy in Production - -The snippets above have shown how the on-chain signer can generate and sign a -meta-transaction for arbitrary transaction. In production environment however, -you must consider the following: - -#### Confidentiality - -Both the inner- and the meta-transaction are stored on-chain unencrypted. Use -`Sapphire.encrypt()` and `Sapphire.decrypt()` call on the inner-transaction with -an encryption key generated and stored inside a confidential contract state. - -#### Gas Cost and Gas Limit - -The gas cost and the gas limit in our snippet were hardcoded inside the -contract. Ideally the gas cost should be dynamically adjusted by an oracle and -the gas limit determined based on the type of transactions. **Never let gas cost -and limit to be freely defined by the user, since they can drain your relayer's -account.** - -#### Allowed Transactions - -Your relayer will probably be used for transactions of a specific contract only. -One approach is to store the allowed address of the target contract and **only -allow calls to this contract address**. - -#### Access Control - -You can either whitelist specific addresses of the users in the relayer contract -or implement the access control in the target contract. In the latter case, the -relayer's `makeProxyTx()` should simulate the execution of the inner-transaction -and generate the meta-transaction only if it inner-transaction succeeded. - -#### Multiple Signers - -Only one transaction per block can be relayed by the same signer since the order -of the transactions is not deterministic and nonces could mismatch. To overcome -this, relayer can randomly pick a signer from the **pool of signers**. When the -transaction is relayed, don't forget to reimburse the signer of the transaction! - -:::info Example - -All the above points are considered in the [Demo Voting dApp][demo-voting]. -You can explore the code and also try out a deployed gasless version of the -voting dApp on the [Oasis Playground site][demo-voting-playground]. The access -control list is configured so that anyone can vote on any poll and only poll -creators can close the poll. - -::: - -[demo-voting]: https://github.com/oasisprotocol/demo-voting -[demo-voting-playground]: https://playground.oasis.io/demo-voting -[dao-opl]: ../opl/host.md -[EIP-155]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md - -## Gas Station Network - -[Gas Station Network](https://docs.opengsn.org) (GSN) was adapted to work with -Sapphire in a forked `@oasislabs/opengsn-cli` package. The diagram below -illustrates a flow for signing a transaction by using a GSN[^1]. - -![Diagram of the Gas Station Network Flow](../images/sapphire/gasless-gsn-flow.jpg) - -[^1]: The GSN flow diagram is courtesy of [OpenGSN documentation][opengsn-docs]. - -[opengsn-docs]: https://github.com/opengsn/docs - -### Package Install - -Starting with an empty folder, let us install the -[Oasis fork of the GSN command line tool](https://github.com/oasislabs/gsn) by -using the following commands: - -```shell npm2yarn -npm init -npm install -D @oasislabs/opengsn-cli -``` - -Next, we will export our hex-encoded private key (**without** the leading `0x`) -for deploying the gas station network as an environment variable: - -```shell -export PRIVATE_KEY=... -``` - -### Deploy GSN - -Deploy GSN relaying contracts along with the test paymaster using a -test token. Use the address of your account as `--burnAddress` and -`--devAddress` parameters: - -```shell -npx gsn deploy --network sapphire-testnet --burnAddress 0xfA3AC9f65C9D75EE3978ab76c6a1105f03156204 --devAddress 0xfA3AC9f65C9D75EE3978ab76c6a1105f03156204 --testToken true --testPaymaster true --yes --privateKeyHex $PRIVATE_KEY -``` - -After the command finishes successfully, you should find the addreses of -deployed contracts at the end: - -``` - Deployed TestRecipient at address 0x594cd6354b23A5200a57355072E2A5B15354ee21 - - RelayHub: 0xc4423AB6133B06e4e60D594Ac49abE53374124b3 - RelayRegistrar: 0x196036FBeC1dA841C60145Ce12b0c66078e141E6 - StakeManager: 0x6763c3fede9EBBCFbE4FEe6a4DE6C326ECCdacFc - Penalizer: 0xA58A0D302e470490c064EEd5f752Df4095d3A002 - Forwarder: 0x59001d07a1Cd4836D22868fcc0dAf3732E93be81 - TestToken (test only): 0x6Ed21672c0c26Daa32943F7b1cA1f1d0ABdbac66 - Paymaster (Default): 0x8C06261f58a024C958d42df89be7195c8690008d -``` - - -### Start GSN Relay Server - -Now we are ready to start our own relay server by using the following command. -Use the newly deployed: - -- `RelayHub` address for `--relayHubAddress`, -- `TestToken` address for `--managerStakeTokenAddress`, -- address of your account for `--owner-address` - -```shell -npx gsn relayer-run --relayHubAddress 0xc4423AB6133B06e4e60D594Ac49abE53374124b3 --managerStakeTokenAddress 0x6Ed21672c0c26Daa32943F7b1cA1f1d0ABdbac66 --ownerAddress '0xfA3AC9f65C9D75EE3978ab76c6a1105f03156204' --ethereumNodeUrl 'https://testnet.sapphire.oasis.dev' --workdir . -``` - -### Fund and Register GSN Relay Server - -The first thing is to fund your relay server so that it has enough native -tokens to pay for others' transactions. Let's fund the paymaster with -**5 tokens**. Use the `RelayHub` and `Paymaster` addresses for `--hub` -and `--paymaster` values: - -```shell -npx gsn paymaster-fund --network sapphire-testnet --hub 0xc4423AB6133B06e4e60D594Ac49abE53374124b3 --paymaster 0x8C06261f58a024C958d42df89be7195c8690008d --privateKeyHex $PRIVATE_KEY --amount 5000000000000000000 -``` - -You can check the balance of the paymaster by running: - -```shell -npx gsn paymaster-balance --network sapphire-testnet --hub 0xc4423AB6133B06e4e60D594Ac49abE53374124b3 --paymaster 0x8C06261f58a024C958d42df89be7195c8690008d -``` - -Next, we need to register the relay server with the your desired `relayUrl` by -staking the `token` the relayHub requires. - -```shell -npx gsn relayer-register --network sapphire-testnet --relayUrl 'http://localhost:8090' --token 0x6Ed21672c0c26Daa32943F7b1cA1f1d0ABdbac66 --wrap true --privateKeyHex $PRIVATE_KEY -``` - -After this step, your relay server should be ready to take incoming relay -requests and forward them to the relay hub on Sapphire Testnet. - -### Send Testing Relayed Requests: - -We can test whether a relayed request can be forwarded and processed correctly. -Scroll up to find the GSN deployment response and use the following parameters: -- `Forwarder` as `--to`, -- `Paymaster` as `--paymaster`, -- your account address as `--from` - -Parameters matching our deployment would be: - -```shell -npx gsn send-request --network sapphire-testnet --abiFile 'node_modules/@oasislabs/opengsn-cli/dist/compiled/TestRecipient.json' --method emitMessage --methodParams 'hello world!' --to 0x594cd6354b23A5200a57355072E2A5B15354ee21 --paymaster 0x8C06261f58a024C958d42df89be7195c8690008d --privateKeyHex $PRIVATE_KEY --from 0xfA3AC9f65C9D75EE3978ab76c6a1105f03156204 --gasLimit 150000 --gasPrice 100 -``` - -:::info - -More detailed explanations of these GSN commands and parameters can be found on -the [upstream OpenGSN website](https://docs.opengsn.org/javascript-client/gsn-helpers.html). - -::: - -### Writing a GSN-enabled Smart Contract - -First, install the OpenGSN contracts package: - -```shell npm2yarn -npm install -D @opengsn/contracts@3.0.0-beta.2 -``` - -Then follow the remainder of the steps from the -[upstream OpenGSN docs](https://docs.opengsn.org/contracts/#receiving-a-relayed-call). diff --git a/docs/dapp/sapphire/gasless.md b/docs/dapp/sapphire/gasless.md new file mode 120000 index 0000000000..1ae10d0a76 --- /dev/null +++ b/docs/dapp/sapphire/gasless.md @@ -0,0 +1 @@ +../../../external/sapphire-paratime/docs/gasless.md \ No newline at end of file diff --git a/docs/dapp/sapphire/guide.mdx b/docs/dapp/sapphire/guide.mdx deleted file mode 100644 index b7689b5aae..0000000000 --- a/docs/dapp/sapphire/guide.mdx +++ /dev/null @@ -1,390 +0,0 @@ ---- -description: Guide to creating secure dApps on Sapphire ---- - -import DocCard from '@theme/DocCard'; -import {findSidebarItem} from '@site/src/sidebarUtils'; - -# Guide - -This page mainly describes the differences between Sapphire and Ethereum -since there are a number of excellent tutorials on developing for Ethereum. -If you don't know where to begin, the [Hardhat tutorial], [Solidity docs], and -[Emerald dApp tutorial] are great places to start. You can continue following -this guide once you've set up your development environment and have deployed -your contract to a non-confidential EVM network (e.g., Ropsten, Emerald). - - -[Hardhat tutorial]: https://hardhat.org/tutorial -[Solidity docs]: https://docs.soliditylang.org/en/v0.8.15/solidity-by-example.html -[Emerald dApp tutorial]: ../emerald/writing-dapps-on-emerald.mdx - -## Oasis Consensus Layer and Sapphire ParaTime - -The Oasis Network consists of the consensus layer and a number of ParaTimes. -ParaTimes are independent replicated state machines that settle transactions -using the consensus layer (to learn more, check the [Oasis Network Overview] -[overview chapter]). Sapphire is a ParaTime which implements the Ethereum -Virtual Machine (EVM). - -The minimum and also expected block time in Sapphire is **6 seconds**. Any -Sapphire transaction will require at least this amount of time to be executed, -and probably no more. - -ParaTimes, Sapphire included, are not allowed to directly access your tokens stored -in consensus layer accounts. You will need to _deposit_ tokens from your consensus -account to Sapphire. Consult the [How to transfer ROSE into an EVM ParaTime] -[how-to-deposit-rose] chapter to learn more. - - -[overview chapter]: ../../general/oasis-network/README.mdx -[how-to-deposit-rose]: ../../general/manage-tokens/how-to-transfer-rose-into-paratime.mdx -[Testnet faucet]: https://faucet.testnet.oasis.dev/ - -## Testnet and Mainnet - -Sapphire is deployed on both Testnet and Mainnet. The Testnet should be -considered unstable software and may have its state wiped at any time. As -the name implies, only use the Testnet for testing unless you're testing how -angry your users get when state is wiped. - -:::danger Never deploy production services on Testnet - -Because Testnet state can be wiped in the future, you should **never** deploy a -production service on the Testnet! Just don't do it! - -Also note that while the Testnet does use actual TEEs, due to experimental -software and different security parameters, **confidentiality of Sapphire on the -Testnet is not guaranteed** -- all transactions and state published on the -Sapphire Testnet should be considered public. - -::: - -:::tip - -For testing purposes, visit our [Testnet faucet] to obtain some TEST which you -can then use on the Sapphire Testnet to pay for gas fees. The faucet supports -sending TEST both to your consensus layer address or to your address inside the -ParaTime. - -::: - -[network-parameters]: ../../node/mainnet/README.md -[Testnet]: ../../node/testnet/README.md - -## Sapphire vs Ethereum - -Sapphire is generally compatible with Ethereum, the EVM, and all of the -user and developer tooling that you already use. There are a few breaking changes, -but we think that you'll like them: - -* Contract state is only visible to the contract that wrote it. With respect - to the contract API, it's as if all state variables are declared as `private`, but - with the further restriction that not even full nodes can read the values. Public or - access-controlled values are provided instead through explicit getters. -* Transactions and calls are end-to-end encrypted into the contract. Only the caller - and the contract can see the data sent to/received from the ParaTime. This ends up - defeating most of the utility of block explorers, however. -* The `from` address using of calls is derived from a signature attached to the call. - Unsigned calls have their sender set to the zero address. This allows contract authors - to write getters that release secrets to authenticated callers, but without - requiring a transaction to be posted on-chain. - -In addition to confidentiality, you get a few extra benefits including the ability to generate private -entropy, and make signatures on-chain. An example of a dApp that uses both is a HSM contract -that generates an Ethereum wallet and signs transactions sent to it via transactions. - -Otherwise Sapphire is like Emerald, which is like a fast, cheap Ethereum. - -## Integrating Sapphire - -Once ROSE tokens are [deposited into Sapphire], it should be painless for users to begin -using dApps. To achieve this ideal user experience, we have to modify the dApp a little, -but it's made simple by our compatibility library, [@oasisprotocol/sapphire-paratime]. - -There are compatibility layers in other languages, which may be found in [the repo]. - - -[deposited into Sapphire]: ../../general/manage-tokens/how-to-transfer-rose-into-paratime.mdx -[@oasisprotocol/sapphire-paratime]: https://www.npmjs.com/package/@oasisprotocol/sapphire-paratime -[the repo]: https://github.com/oasisprotocol/sapphire-paratime/tree/main/clients - -## Writing Secure dApps - -### Wallets - -Sapphire is compatible with popular self-custodial wallets including MetaMask, -Ledger, Brave, and so forth. You can also use libraries like Web3.js and Ethers -to create programmatic wallets. In general, if it generates secp256k1 signatures, -it'll work just fine. - -### Languages & Frameworks - -Sapphire is programmable using any language that targets the EVM, such as Solidity -or Vyper. If you prefer to use an Ethereum framework like Hardhat or Foundry, you -can also use those with Sapphire; all you need to do is set your Web3 gateway URL. -You can find the details of the Oasis Sapphire Web3 gateway -[here](/dapp/sapphire#web3-gateway). - -### Transactions & Calls - -![Client, Key Manager, Compute Node diagram](../../general/images/architecture/client-km-compute.svg) - -The figure above illustrates the flow of a confidential smart contract -*transaction* executed on the Sapphire ParaTime. - -Transactions and calls must be encrypted and signed for maximum security. -You can use the [@oasisprotocol/sapphire-paratime] JS package to make your life -easy. It'll handle cryptography and signing for you. - -You should be aware that taking actions based on the value of private data may -leak the private data through side channels like time spent and gas use. If you -need to branch on private data, you should in most cases ensure that both -branches exhibit similar time/gas and storage patterns. - -You can also make confidential smart contract *calls* on Sapphire. If you -use `msg.sender` for access control in your contract, the call **must be -signed**, otherwise `msg.sender` will be zeroed. On the other hand, set the -`from` address to all zeros, if you want to avoid annoying signature popups in -the user's wallet for calls that do not need to be signed. The JS library will -do this for you. - -:::note - -Inside the smart contract code, there is no way of knowing whether the -client's call data were originally encrypted or not. - -::: - -
- Detailed confidential smart contract transaction flow on Sapphire -
- -
-
- -
- Detailed confidential smart contract call flow on Sapphire -
- -
-
- -### Contract State - -The Sapphire state model is like Ethereum's except for all state being encrypted -and not accessible to anyone except the contract. The contract, executing in an -active (attested) Oasis compute node is the only entity that can request its -state encryption key from the Oasis key manager. Both the keys and values of the -items stored in state are encrypted, but the size of either is *not* hidden. You -app may need to pad state items to a constant length, or use other obfuscation. -Observers may also be able to infer computation based on storage access patterns, -so you may need to obfuscate that, too. See [Security chapter] for more -recommendations. - -[Security chapter]: ./security.md#storage-access-patterns - -:::danger Contract state leaks a fine-grained access pattern - -Contract state is backed by an encrypted key-value store. However, the trace of -encrypted records is leaked to the compute node. As a concrete example, an ERC-20 -token transfer would leak which encrypted record is for the sender's account -balance and which is for the receiver's account balance. Such a token would be -traceable from sender address to receiver address. Obfuscating the storage access -patterns may be done by using an ORAM implementation. - -::: - -Contract state may be made available to third parties through logs/events, or -explicit getters. - -### Contract Logs - -Contract logs/events (e.g., those emitted by the Solidity `emit` keyword) -are exactly like Ethereum. Data contained in events is *not* encrypted. -Precompiled contracts are available to help you encrypt data that you can -then pack into an event, however. - -:::danger Unmodified contracts may leak state through logs - -Base contracts like those provided by OpenZeppelin often emit logs containing -private information. If you don't know they're doing that, you might undermine -the confidentiality of your state. As a concrete example, the ERC-20 spec -requires implementers to emit an `event Transfer(from, to, amount)`, which is -obviously problematic if you're writing a confidential token. What you can -do instead is fork that contract and remove the offending emissions. - -::: - -## Contract Verification - -[Sourcify] is the preferred service for the [verification of smart -contracts][ethereum-contract-verify] deployed on Sapphire. Make sure you have -the **address of each deployed contract** available (your deployment scripts -should report those) and the **contracts JSON metadata file** generated when -compiling contracts (Hardhat stores it inside the `artifacts/build-info` folder -and names it as a 32-digit hex number). If your project contains multiple -contracts, you will need to verify each contract separately. - -:::danger Contract deployment encryption - -**Do not deploy your contract with an encrypted contract deployment transaction, -if you want to verify it.** For example, if your `hardhat.config.ts` -or deployment script contains `import '@oasisprotocol/sapphire-hardhat'` or -`import '@oasisprotocol/sapphire-paratime'` lines at the beginning, you should -comment those out for the deployment. - -Verification services will try to match the contract deployment transaction code -with the one in the provided contract's metadata and since the transaction was -encrypted with an ephemeral ParaTime key, the verification service will not be -able to decrypt it. Some services may extract the contract's bytecode from the -chain directly by calling `eth_getCode` RPC, but this will not work correctly -for contracts with immutable variables. - -::: - -To verify a contract deployed on Sapphire Mainnet or Testnet: - -1. Visit the [Sourcify] website and hit the "VERIFY CONTRACT" button. - - ![Sourcify website](../images/sapphire/sourcify1.png) - -2. Upload the contracts JSON metadata file. - - ![Sourcify: Upload metadata JSON file](../images/sapphire/sourcify2.png) - - :::tip Store your metadata files - - For production deployments, it is generally a good idea to **archive your - contract metadata JSON file** since it is not only useful for the - verification, but contains a copy of all the source files, produced bytecode, - an ABI, compiler and other relevant contract-related settings that may be - useful in the future. Sourcify will store the metadata file for you and will - even make it available via IPFS, but it is still a good idea to store it - yourself. - - ::: - -3. Sourcify will decode the metadata and prepare a list of included contracts on - the right. Enter the address of the specific contract and select the "Oasis - Sapphire" or "Oasis Sapphire Testnet" chain for the Mainnet or Testnet - accordingly. If your contract assigns any immutable variables in the - constructor, you will also need to correctly fill those out under the "More - Inputs (optional)" panel. Finally, click on the "Verify" button. - - ![Sourcify: Verify contract](../images/sapphire/sourcify3.png) - -4. If everything goes well, you will get a ** *Perfect match* notice and your - contract is now verified**. Congratulations! - -In case of a *Partial match*, the contracts metadata JSON differs from the one -used for deployment although the compiled contract bytecode matched. Make sure -the source code `.sol` file of the contract is the same as the one used during the -deployment (including the comments, variable names and source code file -names) and use the same version of Hardhat and solc compiler. - -You can also explore other verification methods on Sourcify by reading the -[official Sourcify contract verification instructions][sourcify-contract-verify]. - -[Sourcify]: https://sourcify.dev/ -[hardhat-example]: https://github.com/oasisprotocol/sapphire-paratime/tree/main/examples/hardhat -[sourcify-contract-verify]: https://docs.sourcify.dev/docs/how-to-verify/ -[ethereum-contract-verify]: https://ethereum.org/en/developers/docs/smart-contracts/verifying/ - -## Running a Private Oasis Network Locally - -For convenient development and testing of your dApps the Oasis team prepared -the [ghcr.io/oasisprotocol/sapphire-dev][sapphire-dev] Docker image which brings you a -complete Oasis stack to your desktop. The Localnet Sapphire instance **mimics -confidential transactions**, but it does not run in a trusted execution -environment nor does it require Intel's SGX on your computer. The network is -isolated from the Mainnet or Testnet and consists of: - -- single Oasis validator node with 1-second block time and 30-second epoch, -- single Oasis client node, -- three compute nodes running Oasis Sapphire, -- single key manager node, -- PostgreSQL instance, -- Oasis Web3 gateway with transaction indexer and enabled Oasis RPCs, -- helper script which populates initial test accounts for you. - -To run the image, execute: - -```sh -docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/sapphire-dev -``` - -After a while, the tool will show you something like this: - -``` -sapphire-dev 2023-02-28-git84730b2 (oasis-core: 22.2.6, sapphire-paratime: 0.4.0, oasis-web3-gateway: 3.2.0-git84730b2) - -Starting oasis-net-runner with sapphire... -Starting postgresql... -Starting oasis-web3-gateway... -Bootstrapping network and populating account(s) (this might take a minute)... - -Available Accounts -================== -(0) 0x75eCF0d4496C2f10e4e9aF3D4d174576Ee9010E2 (100 ROSE) -(1) 0x903a7dce5a26a3f4DE2d157606c2191740Bc4BC9 (100 ROSE) -(2) 0xF149ad5CBFfD92ba84F5784106f6Cb071A32a1b8 (100 ROSE) -(3) 0x2315F40C1122400Df55483743B051D2997ef0a62 (100 ROSE) -(4) 0xf6FdcacbA93A428A07d27dacEf1fBF25E2C65B0F (100 ROSE) - -Private Keys -================== -(0) 0x160f52faa5c0aecfa26c793424a04d53cbf23dcad5901ce15b50c2e85b9d6ca7 -(1) 0x0ba685723b47d8e744b1b70a9bea9d4d968f60205385ae9de99865174c1af110 -(2) 0xfa990cf0c22af455d2734c879a2a844ff99bd779b400bb0e2919758d1be284b5 -(3) 0x3bf225ef73b1b56b03ceec8bb4dfb4830b662b073b312beb7e7fec3159b1bb4f -(4) 0xad0dd7ceb896fd5f5ddc76d56e54ee6d5c2a3ffeac7714d3ef544d3d6262512c - -HD Wallet -================== -Mnemonic: bench remain brave curve frozen verify dream margin alarm world repair innocent -Base HD Path: m/44'/60'/0'/0/%d - -WARNING: The chain is running in ephemeral mode. State will be lost after restart! - -Listening on http://localhost:8545 and ws://localhost:8546 -``` - -Those familiar with local dApp environments will find the output above similar -to `geth --dev` or `ganache-cli` commands or the `geth-dev-assistant` npm -package. [sapphire-dev] will spin up a private Oasis Network locally, generate -and populate test accounts and make the following Web3 endpoints available for -you to use: -- `http://localhost:8545` -- `ws://localhost:8546` - -:::tip - -If you prefer using the same mnemonics each time (e.g. for testing purposes) -or to populate just a single wallet, use `-to` flag and pass the mnemonics or -the wallet addresses. For example - -```sh -docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/sapphire-dev -to "bench remain brave curve frozen verify dream margin alarm world repair innocent" -docker run -it -p8545:8545 -p8546:8546 ghcr.io/oasisprotocol/sapphire-dev -to "0x75eCF0d4496C2f10e4e9aF3D4d174576Ee9010E2,0xbDA5747bFD65F08deb54cb465eB87D40e51B197E" -``` - -::: - -:::danger - -[sapphire-dev] runs in ephemeral mode. Any smart contract and wallet balance -will be lost after you quit the Docker container! - -::: - -[sapphire-dev]: https://github.com/oasisprotocol/oasis-web3-gateway/pkgs/container/sapphire-dev - -## See also - - - diff --git a/docs/dapp/sapphire/guide.mdx b/docs/dapp/sapphire/guide.mdx new file mode 120000 index 0000000000..872eeaa164 --- /dev/null +++ b/docs/dapp/sapphire/guide.mdx @@ -0,0 +1 @@ +../../../external/sapphire-paratime/docs/guide.mdx \ No newline at end of file diff --git a/docs/dapp/sapphire/images b/docs/dapp/sapphire/images new file mode 120000 index 0000000000..dbac58ba8e --- /dev/null +++ b/docs/dapp/sapphire/images @@ -0,0 +1 @@ +../../../external/sapphire-paratime/docs/images \ No newline at end of file diff --git a/docs/dapp/sapphire/precompiles.md b/docs/dapp/sapphire/precompiles.md deleted file mode 100644 index 1c09d2c27a..0000000000 --- a/docs/dapp/sapphire/precompiles.md +++ /dev/null @@ -1,317 +0,0 @@ ---- -description: Additional Sapphire precompiles for encryption and confidentiality ---- - -# Precompiles - -In addition to the standard EVM precompiles, Sapphire provides a number -of further cryptography-related ones to make some operations easier and -cheaper to perform: x25519 key derivation, Deoxys-II-based encryption -and decryption, signing key generation, message digest signing and -verification. - -These can be called in the same way as other precompiles by dispatching -calls to specific well-known contract addresses, as described below. - -Input parameters should be packed into a contiguous memory region with -each chunk of data padded to 32 bytes as usual. The recommended way to -construct parameter byte sequences in Solidity is with `abi.encode` and -`abi.decode`, which will transparently handle things like putting -`bytes` lengths in the correct position. - -## Library - -While it is possible to call the precompiles directly using Yul or, for -example, `abi.encode` and `abi.decode` in Solidity, we recommend always -using the `contracts/Sapphire.sol` wrapper library for a more comfortable -experience. The examples below are written against it. The library is provided -by the `@oasisprotocol/sapphire-contracts` npm package. - -```shell npm2yarn -npm install -D @oasisprotocol/sapphire-contracts -``` - -Then, you can use the wrapper library inside your `.sol` contract file as -follows: - -```solidity -pragma solidity ^0.8.13; - -import "@oasisprotocol/sapphire-contracts/contracts/Sapphire.sol"; - -contract Test { - constructor() {} - function test() public view returns (bytes32) { - return Sapphire.deriveSymmetricKey("public key as bytes32", "private key as bytes32"); - } -} -``` - -Feel free to discover other convenient libraries for Solidity inside the -`contracts/` folder of the -[Oasis Sapphire repository](https://github.com/oasisprotocol/sapphire-paratime)! - -## Generating Pseudo-Random Bytes - -* Precompile address: `0x0100000000000000000000000000000000000001` -* Parameters: `uint num_bytes, bytes pers` -* Gas cost: 10,000 minimum plus 240 per output word plus 60 per word of - the personalization string. - -Generate `num_bytes` pseudo-random bytes, with an optional personalization -string (`pers`) added into the hashing algorithm to increase domain separation -when needed. - -```solidity -bytes memory randomPad = Sapphire.randomBytes(64, ""); -``` - -### Implementation Details - -:::danger Prior to 0.6.0 -All view queries and simulated transactions (via `eth_call`) would receive the -same entropy in-between blocks if they use the same `num_bytes` and `pers` parameters. -If your contract requires confidentiality you should generate a secret in the constructor -to be used with view calls: - -```solidity -Sapphire.randomBytes(64, abi.encodePacked(msg.sender, this.perContactSecret)); -``` -::: - -The mode (e.g. simulation or 'view call' vs transaction execution) is fed to TupleHash (among other -block-dependent components) to derive the "key id", which is then used to derive a per-block VRF key -from epoch-ephemeral entropy (using KMAC256 and cSHAKE) so a different "key id" will result in a -unique per-block VRF key. This per-block VRF key is then used to create the per-block root RNG which -is then used to derive domain-separated (using Merlin transcripts) per-transaction random RNGs which -are then exposed via this precompile. The KMAC, cSHAKE and TupleHash algorithms are SHA-3 derived functions -defined in [NIST Special Publication 800-185](https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf). - -## X25519 Key Derivation - -* Precompile address: `0x0100000000000000000000000000000000000002` -* Parameters: `bytes32 public_key, bytes32 private_key` -* Gas cost: 100,000 - -### Example - -```solidity -bytes32 publicKey = ... ; -bytes32 privateKey = ... ; -bytes32 symmetric = Sapphire.deriveSymmetricKey(publicKey, privateKey); -``` - -## Deoxys-II Encryption - -* Encryption precompile address: `0x0100000000000000000000000000000000000003` -* Decryption precompile address: `0x0100000000000000000000000000000000000004` -* Parameters: `bytes32 key, bytes32 nonce, bytes text_or_ciphertext, bytes additional_data` -* Gas cost: 50,000 minimum plus 100 per word of input - -### Example - -```solidity -bytes32 key = ... ; -bytes32 nonce = ... ; -bytes memory text = "plain text"; -bytes memory ad = "additional data"; -bytes memory encrypted = Sapphire.encrypt(key, nonce, text, ad); -bytes memory decrypted = Sapphire.decrypt(key, nonce, encrypted, ad); -``` - -## Signing Keypairs Generation - -* Precompile address: `0x0100000000000000000000000000000000000005` -* Parameters: `uint method, bytes seed` -* Return value: `bytes public_key, bytes private_key` -* Gas cost: method-dependent base cost, see below - -The available methods are items in the `Sapphire.SigningAlg` enum. Note, -however, that the generation method ignores subvariants, so all three -ed25519-based are equivalent, and all secp256k1 & secp256r1 based methods are -equivalent. `Sr25519` is not available and will return an error. - -### Gas Cost -* Ed25519: 1,000 gas - * `0` (`Ed25519Oasis`) - * `1` (`Ed25519Pure`) - * `2` (`Ed25519PrehashedSha512`) -* Secp256k1: 1,500 gas. - * `3` (`Secp256k1Oasis`) - * `4` (`Secp256k1PrehashedKeccak256`) - * `5` (`Secp256k1PrehashedSha256`) -* Secp256r1: 4,000 gas - * `7` (`Secp256r1PrehashedSha256`) - -### Public Key Format - - * Ed25519: 32 bytes - * Secp256k1 & Secp256r1: 33 bytes, compressed format (0x02 or 0x03 prefix, then 32 byte X coordinate) - -### Example - -Using the Sapphire library: - -```solidity -bytes memory seed = hex"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; -bytes memory publicKey; -bytes memory privateKey; -(publicKey, privateKey) = Sapphire.generateSigningKeyPair(Sapphire.SigningAlg.Ed25519Pure, seed); -``` - -## Message Signing - -* Precompile address: `0x0100000000000000000000000000000000000006` -* Parameters: `uint method, bytes private_key, bytes context_or_digest, bytes message` -* Gas cost: see below for the method-dependent base cost, plus 8 gas per 32 bytes of context and message except digest. - -The `context_or_digest` and `messages` parameters change in meaning slightly -depending on the method requested. For methods that take a context in addition -to the message you must pass the context in the `context_or_digest` parameter -and use `message` as expected. For methods that take a pre-existing hash of the -message, pass that in `context_or_digest` and leave `message` empty. -Specifically the `Ed25519Oasis` and `Secp256k1Oasis` variants take both a -context and a message (each are variable length `bytes`), the context serves as -a domain separator. - -### Signing Algorithms - -* `0` (`Ed25519Oasis`) - * 1,500 gas - * variable length context and message -* `1` (`Ed25519Pure`) - * 1,500 gas - * empty context, variable length message -* `2` (`Ed25519PrehashedSha512`) - * 1,500 gas - * pre-existing SHA-512 hash (64 bytes) as context, empty message -* `3` (`Secp256k1Oasis`) - * 3,000 gas - * variable length context and message -* `4` (`Secp256k1PrehashedKeccak256`) - * 3,000 gas - * pre-existing hash (32 bytes) as context, empty message -* `5` (`Secp256k1PrehashedSha256`) - * 3,000 gas - * pre-existing hash (32 bytes) as context, empty message -* `7` (`Secp256r1PrehashedSha256`) - * 9,000 gas - * pre-existing hash (32 bytes) as context, empty message - -### Example - -Using the Sapphire library: - -```solidity -Sapphire.SigningAlg alg = Sapphire.SigningAlg.Ed25519Pure; -bytes memory pk; -bytes memory sk; -(pk, sk) = Sapphire.generateSigningKeyPair(alg, Sapphire.randomBytes(32, "")); -bytes memory signature = Sapphire.sign(alg, sk, "", "signed message"); -``` - -## Signature Verification - -* Precompile address: `0x0100000000000000000000000000000000000007` -* Parameters: `uint method, bytes public_key, bytes context_or_digest, bytes message, bytes signature` - -The `method`, `context_or_digest` and `message` parameters have the same meaning -as described above in the Message Signing section. - -### Gas Cost - -The algorithm-specific base cost below, with an additional 8 gas per 32 bytes of -`context` and `message` for the `Ed25519Oasis`, `Ed25519Pure` and `Secp256k1Oasis` algorithms. - -* Ed25519: 2,000 gas - * `0` (`Ed25519Oasis`) - * `1` (`Ed25519Pure`) - * `2` (`Ed25519PrehashedSha512`) -* Secp256k1: 3,000 gas - * `3` (`Secp256k1Oasis`) - * `4` (`Secp256k1PrehashedKeccak256`) - * `5` (`Secp256k1PrehashedSha256`) -* Secp256r1: 7,900 gas - * `7` (`Secp256r1PrehashedSha256`) - -### Example - -Using the Sapphire library: - -```solidity -Sapphire.SigningAlg alg = Sapphire.SigningAlg.Secp256k1PrehashedKeccak256; -bytes memory pk; -bytes memory sk; -bytes memory digest = abi.encodePacked(keccak256("signed message")); -(pk, sk) = Sapphire.generateSigningKeyPair(alg, Sapphire.randomBytes(32, "")); -bytes memory signature = Sapphire.sign(alg, sk, digest, ""); -require( Sapphire.verify(alg, pk, digest, "", signature) ); -``` - -## SHA-512 - - * Precompile address: `0x0100000000000000000000000000000000000101` - * Parameters: `bytes input_data` - -Hash the input data with SHA-512, according to [NIST.FIPS.180-4] - -:::warning SHA-512 is vulnerable to length-extension attacks - -The SHA-512/256 variant (below) is not vulnerable to [length-extension attacks]. -Length extension attacks are relevant if, among other things, you are computing -the hash of a secret message or computing merkle trees. - -::: - -[length-extension attacks]: https://en.wikipedia.org/wiki/Length_extension_attack - -### Gas Cost - -* 115 gas, then 13 gas per word - -### Example - -```solidity -bytes memory result = sha512(abi.encodePacked("input data")); -``` - - -## SHA-512/256 - - * Precompile address: `0x0100000000000000000000000000000000000102` - * Parameters: `bytes input_data` - -Hash the input data with SHA-512/256, according to [NIST.FIPS.180-4] - -[NIST.FIPS.180-4]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf - -### Gas Cost - - * 115 gas, then 13 gas per word - -### Example - -```solidity -bytes32 result = sha512_256(abi.encodePacked("input data")); -``` - - -## Subcall - - * Precompile address: `0x0100000000000000000000000000000000000102` - * Parameters: `string method, bytes cborEncodedParams` - -Subcall performs an Oasis SDK call. This allows Sapphire contracts to interact -with the Consensus layer and other modules supported by the SDK. For more -information about the specific modules and their available calls see the Oasis -SDK [source code]. - -### Gas Cost - -Varies per operation, refer to the oasis-sdk [source code]. - -### Example - -TODO: an example - -[source code]: https://github.com/oasisprotocol/oasis-sdk/tree/main/runtime-sdk/src/modules diff --git a/docs/dapp/sapphire/precompiles.md b/docs/dapp/sapphire/precompiles.md new file mode 120000 index 0000000000..9d4de035b3 --- /dev/null +++ b/docs/dapp/sapphire/precompiles.md @@ -0,0 +1 @@ +../../../external/sapphire-paratime/docs/precompiles.md \ No newline at end of file diff --git a/docs/dapp/sapphire/quickstart.mdx b/docs/dapp/sapphire/quickstart.mdx deleted file mode 100644 index 76dd35f4a2..0000000000 --- a/docs/dapp/sapphire/quickstart.mdx +++ /dev/null @@ -1,207 +0,0 @@ -import DocCard from '@theme/DocCard'; -import {findSidebarItem} from '@site/src/sidebarUtils'; - -# Quickstart - -

- -

- -In this tutorial, you will build and deploy a unique dApp that requires -confidentiality to work. By the end of the tutorial, you should feel -comfortable setting up your Eth development environment to target Sapphire, -and know how and when to use confidentiality. - -The expected completion time of this tutorial is 15 minutes. - -:::info Sunsetting Truffle - -Per Consensys [announcement], Oasis will no longer support Truffle as of -2023-10-05 and encourage immediate [migration] to Hardhat. Please see our -repository for the archived Truffle [tutorial] and the deprecated [example]. - -::: - -[announcement]: https://consensys.io/blog/consensys-announces-the-sunset-of-truffle-and-ganache-and-new-hardhat -[migration]: https://trufflesuite.com/docs/truffle/how-to/migrate-to-hardhat/ -[tutorial]: https://github.com/oasisprotocol/docs/blob/2f4a1a3c217b82687ab9440bf051762ae369ed45/docs/dapp/sapphire/quickstart.mdx -[example]: https://github.com/oasisprotocol/sapphire-paratime/tree/3a85e42e6c1cc090c28a521cf7df6353aa8a30c8/examples/truffle - - -## Create a Sapphire-Native dApp - -Porting an existing Eth app is cool, and will provide benefits such as -protection against MEV. -However, starting from scratch with confidentiality in mind can unlock some -really novel dApps and provide a [higher level of security]. - -One simple-but-useful dApp that takes advantage of confidentiality is a -[dead person's switch] that reveals a secret (let's say the encryption key to a -data trove) if the operator fails to re-up before too long. -Let's make it happen! - -[higher level of security]: guide.mdx#writing-secure-dapps -[dead person's switch]: https://en.wikipedia.org/wiki/Dead_man%27s_switch - -### Init a new Hardhat project - -We're going to use Hardhat, but Sapphire should be compatible with your dev -environment of choice. Let us know if things are not as expected! - -1. Make & enter a new directory -2. `npx hardhat@~2.16.0 init` then create a TypeScript project. -3. Add [`@oasisprotocol/sapphire-hardhat`] as dependency: - - ```shell npm2yarn - npm install -D @oasisprotocol/sapphire-hardhat - ``` - -4. Install `@nomicfoundation/hardhat-toolbox`, TypeScript and other peer - dependencies required by HardHat. - -### Add the Sapphire Testnet to Hardhat - -Open up your `hardhat.config.ts` and drop in these lines. - -```diff -diff --git a/hardhat.config.ts b/hardhat.config.ts -index 414e974..49c95f9 100644 ---- a/hardhat.config.ts -+++ b/hardhat.config.ts -@@ -1,8 +1,19 @@ - import { HardhatUserConfig } from "hardhat/config"; -+import '@oasisprotocol/sapphire-hardhat'; - import "@nomicfoundation/hardhat-toolbox"; - - const config: HardhatUserConfig = { - solidity: "0.8.17", -+ networks: { -+ 'sapphire-testnet': { -+ // This is Testnet! If you want Mainnet, add a new network config item. -+ url: "https://testnet.sapphire.oasis.dev", -+ accounts: process.env.PRIVATE_KEY -+ ? [process.env.PRIVATE_KEY] -+ : [], -+ chainId: 0x5aff, -+ }, -+ }, - }; - - export default config; -``` - -By importing `@oasisprotocol/sapphire-hardhat` at the top of the config file, -**any network config entry corresponding to the Sapphire's chain ID will -automatically be wrapped with Sapphire specifics for encrypting and signing the -transactions**. - -### Get some Sapphire Testnet tokens - -Now for the fun part. We need to configure the Sapphire network and get some tokens. -Hit up the one and only [Oasis Testnet faucet] and select "Sapphire". -Submit the form and be on your way. - -[Oasis Testnet faucet]: https://faucet.testnet.oasis.dev - -### Get the Contract - -This is a Sapphire tutorial and you're already a Solidity expert, so let's not -bore you with explaining the gritty details of the contract. -Start by pasting [Vigil.sol] into `contracts/Vigil.sol`. - -While you're there, also place [run-vigil.ts] into `scripts/run-vigil.ts`. -We'll need that later. - -[Vigil.sol]: https://github.com/oasisprotocol/sapphire-paratime/blob/main/examples/hardhat/contracts/Vigil.sol -[run-vigil.ts]: https://github.com/oasisprotocol/sapphire-paratime/blob/main/examples/hardhat/scripts/run-vigil.ts - -#### Vigil.sol, the interesting parts - -The key state variables are: - -```solidity - SecretMetadata[] public _metas; - bytes[] private _secrets; -``` - -* `_metas` is marked with `public` visibility, so despite the state itself being - encrypted and not readable directly, Solidity will generate a getter that will - do the decryption for you. -* `_secrets` is `private` and therefore truly secret; only the contract can - access the data contained in this mapping. - -And the methods we'll care most about are - -* `createSecret`, which adds an entry to both `_metas` and `_secrets`. -* `revealSecret`, which acts as an access-controlled getter for the data - contained with `_secrets`. Due to trusted execution and confidentiality, the - only way that the secret will get revealed is if execution proceeds all the - way to the end of the function and does not revert. - -The rest of the methods are useful if you actually intended to use the contract, -but they demonstrate that developing for Sapphire is essentially the same as for -Ethereum. -You can even write tests against the Hardhat network and use Hardhat plugins. - -### Run the Contract - -And to wrap things up, we'll put `Vigil` through its paces. -First, let's see what's actually going on. - -After deploying the contract, we can create a secret, check that it's not -readable, wait a bit, and then check that it has become readable. -Pretty cool if you ask me! - -Anyway, make it happen by running - -```shell -PRIVATE_KEY="0x..." npx hardhat run scripts/run-vigil.ts --network sapphire-testnet -``` - -And if you see something like the following, you'll know you're well on the road -to deploying confidential dApps on Sapphire. - -``` -Vigil deployed to: 0x74dC4879B152FDD1DDe834E9ba187b3e14f462f1 -Storing a secret in 0x13125d868f5fb3cbc501466df26055ea063a90014b5ccc8dfd5164dc1dd67543 -Checking the secret -failed to fetch secret: reverted: not expired -Waiting... -Checking the secret again -The secret ingredient is brussels sprouts -``` - -## All done! - -Congratulations, you made it through the Sapphire tutorial! If you have any -questions, please check out the [guide] and join the discussion on the -[#sapphire-paratime Discord channel][social-media]. - -Best of luck on your future forays into confidentiality! - -:::info Example - -Visit the Sapphire ParaTime repository to download the [Hardhat][hardhat-example] -example of this quickstart. - -::: - -:::info Example - -If your project involves building a web frontend, we recommend that you check -out the official [Oasis starter] files. - -[Oasis starter]: https://github.com/oasisprotocol/demo-starter - -::: - -## See also - - - - - -[social-media]: ../../get-involved/README.md#social-media-channels -[guide]: guide.mdx -[hardhat-example]: https://github.com/oasisprotocol/sapphire-paratime/tree/main/examples/hardhat -[`@oasisprotocol/sapphire-hardhat`]: https://www.npmjs.com/package/@oasisprotocol/sapphire-hardhat diff --git a/docs/dapp/sapphire/quickstart.mdx b/docs/dapp/sapphire/quickstart.mdx new file mode 120000 index 0000000000..b57943dc32 --- /dev/null +++ b/docs/dapp/sapphire/quickstart.mdx @@ -0,0 +1 @@ +../../../external/sapphire-paratime/docs/quickstart.mdx \ No newline at end of file diff --git a/docs/dapp/sapphire/security.md b/docs/dapp/sapphire/security.md deleted file mode 100644 index e9f869e050..0000000000 --- a/docs/dapp/sapphire/security.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -description: "Secure dApps: Recipes for Confidentiality" ---- - -# Security - -This page is an ongoing work in progress to support confidential smart contract -development. At the moment we address safeguarding storage variable access -patterns and provide best practices for more secure orderings of error checking -to prevent leaking contract state. - -## Storage Access Patterns - -You can use a tool such as [hardhat-tracer] to examine the base EVM state -transitions under the hood. - -```shell npm2yarn -npm install -D hardhat-tracer -``` - -and add `hardhat-tracer` to your `config.ts` file, - -```typescript -import "hardhat-tracer" -``` - -in order to test and show call traces. - -```shell -npx hardhat test --vvv --opcodes SSTORE,SLOAD -``` - -You can also trace a particular transaction, once you know its hash. - -```shell -npx hardhat trace --hash 0xTransactionHash -``` - -For both [gas] usage and confidentiality purposes, we **recommend using -non-unique data size**. E.g. 64-byte value will still be distinct from a -128-byte value. - -:::caution Inference based on access patterns - -`SSTORE` keys from one transaction may be linked to `SLOAD` keys of another -transaction. - -::: - -## Order of Operations - -When handling errors, gas usage patterns not only can reveal the code path -taken, **but sometimes the balance of a user as well** (in the case of a diligent -attacker using binary search). - -```solidity -function transferFrom(address who, address to, uint amount) - external -{ - require( balances[who] >= amount ); - require( allowances[who][msg.sender] >= amount ); - // ... -} -``` - -Modifying the order of error checking can prevent the accidental disclosure of -balance information in the example above. - -```solidity -function transferFrom(address who, address to, uint amount) - external -{ - require( allowances[who][msg.sender] >= amount ); - require( balances[who] >= amount ); - // ... -} -``` - -## Gas Padding - -To prevent leaking information about a particular transaction, Sapphire -provides a [precompile] for dApp developers to **pad the amount of gas used -in a transaction**. - -```solidity -contract GasExample { - bytes32 tmp; - - function constantMath(bool doMath, uint128 padGasAmount) external { - if (doMath) { - bytes32 x; - - for (uint256 i = 0; i < 100; i++) { - x = keccak256(abi.encodePacked(x, tmp)); - } - - tmp = x; - } - - Sapphire.padGas(padGasAmount); - } -} -``` - -Both contract calls below should use the same amount of gas. Sapphire also -provides the precompile to return the gas [used] by the current transaction. - -```typescript -await contract.constantMath(true, 100000); -await contract.constantMath(false, 100000); -``` - -[gas]: https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html -[hardhat-tracer]: https://www.npmjs.com/package/hardhat-tracer -[precompile]: https://api.docs.oasis.io/sol/sapphire-contracts/contracts/Sapphire.sol/library.Sapphire.html#padgas -[used]: https://api.docs.oasis.io/sol/sapphire-contracts/contracts/Sapphire.sol/library.Sapphire.html#gasused diff --git a/docs/dapp/sapphire/security.md b/docs/dapp/sapphire/security.md new file mode 120000 index 0000000000..701ce461be --- /dev/null +++ b/docs/dapp/sapphire/security.md @@ -0,0 +1 @@ +../../../external/sapphire-paratime/docs/security.md \ No newline at end of file diff --git a/external/sapphire-paratime b/external/sapphire-paratime new file mode 160000 index 0000000000..60cfe61e67 --- /dev/null +++ b/external/sapphire-paratime @@ -0,0 +1 @@ +Subproject commit 60cfe61e675d32e7e9cbb68ef40d409f7c1f0b4a diff --git a/src/editUrl.js b/src/editUrl.js index 1b15c6c689..2aff35b4c0 100644 --- a/src/editUrl.js +++ b/src/editUrl.js @@ -7,6 +7,7 @@ const gitModules = { 'external/cli/': 'https://github.com/oasisprotocol/cli/{mode}/master/', 'external/oasis-core/': 'https://github.com/oasisprotocol/oasis-core/{mode}/stable/22.2.x/', 'external/oasis-sdk/': 'https://github.com/oasisprotocol/oasis-sdk/{mode}/main/', + 'external/sapphire-paratime/': 'https://github.com/oasisprotocol/sapphire-paratime/{mode}/main/', }; /** diff --git a/src/remark/cross-repo-links.js b/src/remark/cross-repo-links.js index 83a0bd3501..5df0f4520c 100644 --- a/src/remark/cross-repo-links.js +++ b/src/remark/cross-repo-links.js @@ -5,6 +5,7 @@ const oasisSdkContractRegex = /https:\/\/github\.com\/oasisprotocol\/oasis-sdk\/ const oasisSdkRuntimeRegex = /https:\/\/github\.com\/oasisprotocol\/oasis-sdk\/blob\/main\/docs\/runtime\/(.*)\.mdx?(#.*)?/; const oasisCoreRegex = /https:\/\/github\.com\/oasisprotocol\/oasis-core\/blob\/master\/docs\/(.*)\.mdx?(#.*)?/; const adrsRegex = /https:\/\/github\.com\/oasisprotocol\/adrs\/blob\/main\/(.*)\.mdx?(#.*)?/; +const sapphireParatimeRegex = /https:\/\/github\.com\/oasisprotocol\/sapphire-paratime\/blob\/main\/(.*)\.mdx?(#.*)?/; const docsRegex = /https:\/\/github\.com\/oasisprotocol\/docs\/blob\/main\/docs\/(.*)\.mdx?(#.*)?/; const indexReadmeRegex = /(index|README)($|#)/; @@ -33,6 +34,8 @@ module.exports = function (options) { node.url = node.url.replace(oasisCoreRegex, '/core/$1$2'); } else if (adrsRegex.test(node.url)) { node.url = node.url.replace(adrsRegex, '/adrs/$1$2'); + } else if (sapphireParatimeRegex.test(node.url)) { + node.url = node.url.replace(sapphireParatimeRegex, '/sapphire-paratime/$1$2'); } else if (docsRegex.test(node.url)) { node.url = node.url.replace(docsRegex, '/$1$2'); } else {