Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Migrate Sapphire documentation from oasisprotocol/docs #227

Merged
merged 2 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 30 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,49 @@ This monorepo includes the source code for the following Sapphire packages:
- Solidity [smart contracts](https://www.npmjs.com/package/@oasisprotocol/sapphire-contracts) ![npm](https://img.shields.io/npm/v/@oasisprotocol/sapphire-contracts)
- Hardhat [plugin](https://www.npmjs.com/package/@oasisprotocol/sapphire-hardhat) ![npm](https://img.shields.io/npm/v/@oasisprotocol/sapphire-hardhat)

## Documentation

The Sapphire Paratime documentation is deployed as part of the full set of Oasis [docs](https://docs.oasis.io/dapp/sapphire/)
based on this open source [repository](https://github.com/oasisprotocol/docs). Auto-generated API documentation is available at:

* https://api.docs.oasis.io/js/sapphire-paratime/
* https://api.docs.oasis.io/sol/sapphire-contracts/

## Layout

This repository includes all relevant Sapphire and dependencies organized into
the following directories:

- [`clients`](./clients): the Go and TypeScript clients
- [`contracts`](./contracts): Sapphire and [OPL](https://docs.oasis.io/dapp/opl/) smart contracts
- [`docs`](./docs): topic-oriented Sapphire documentation
- [`examples`](./examples/): sample code snippets in popular Ethereum
development environments
- [`integrations`](./integrations/): plugins with popular Solidity tools
- [`runtime`](./runtime/): the Sapphire Paratime as based off of the
[Oasis SDK](https://github.com/oasisprotocol/oasis-sdk)

## Documentation

The Sapphire documentation is deployed as part of the official
[Oasis documentation](https://docs.oasis.io/dapp/sapphire/). To make changes
visible on the docs website:

1. Merge any changes in the `docs` folder to the `main` branch.
2. Bump the git commit reference of the Sapphire submodule inside the `external`
directory of the [Oasis docs repository](https://github.com/oasisprotocol/docs)
(you can simply approve the auto-generated dependabot's submodule bump PR).
3. Merge changes into Oasis docs repository `main` branch. CI will deploy the
docs to the website automatically.

Note: If you want to introduce a new markdown file, don't forget to add
it to the [Oasis documentation's sidebar](https://github.com/oasisprotocol/docs/blob/main/sidebarDapp.js).
If you remove any chapters, don't forget to define sensible [redirects](https://github.com/oasisprotocol/docs/blob/main/redirects.js).
For more info on how to write the Oasis documentation, manage images and
diagrams, reference cross-repo markdown files and similar consult the
[official README](https://github.com/oasisprotocol/docs/blob/main/README.md).

The API documentation is auto-generated from the corresponding Sapphire
clients and libraries. It is deployed at:

* https://api.docs.oasis.io/js/sapphire-paratime/
* https://api.docs.oasis.io/sol/sapphire-contracts/

The API docs are generated automatically every 15 minutes from the `main`
branch.

## Release

### Clients
Expand All @@ -45,7 +67,6 @@ JS libraries should be updated with a version bump in the `package.json`
file and a respective tag in the pattern of `{{path}}/v{{semver}}`, such as
`clients/js/v1.1.1`.


## Contributing

Developers are encouraged to contribute their improvements to the Sapphire
Expand Down
18 changes: 18 additions & 0 deletions docs/addresses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
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
209 changes: 209 additions & 0 deletions docs/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
---
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);
```
Loading
Loading