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

sapphire: initial notes on authenticated calls & contract authentication #476

Merged
merged 30 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8cc1219
sapphire: initial notes on authenticated calls & contract authentication
CedarMist Jul 25, 2023
8e75004
sapphire: improved authentication verbiage
CedarMist Jul 25, 2023
ec319e8
sapphire: added hyperlink to sapphire-paratime NPM package.
CedarMist Jul 25, 2023
8262f9b
Merge branch 'main' into CedarMist/712-auth
CedarMist Jul 25, 2023
42f6187
sapphire/authentication: added EIP-712 link and side-menu item
CedarMist Jul 28, 2023
583057b
sapphire/authentication: feedback from aefhm
CedarMist Jul 31, 2023
d13bae5
sapphire/authentication: suggestions from Matevz
CedarMist Aug 2, 2023
66623ed
sapphire/authentication: changed order of paragraphs
CedarMist Aug 4, 2023
8f18d71
sapphire/authentication: add notes about cached signed queries
CedarMist Aug 7, 2023
f84db5e
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 9, 2023
191172b
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
25bee5c
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
4161625
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
bcd2ea3
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
365c51f
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
080447e
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
f6fc788
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
170ea89
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
8700eb5
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
78381b1
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
2fd74ab
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
e83da70
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
49e898a
Update docs/dapp/sapphire/authentication.md
CedarMist Aug 16, 2023
d5c5f9a
Update docs/dapp/sapphire/authentication.md
CedarMist Oct 10, 2023
d67b20e
Update docs/dapp/sapphire/authentication.md
CedarMist Oct 10, 2023
1e73595
Update docs/dapp/sapphire/authentication.md
CedarMist Oct 10, 2023
d2d6591
Update docs/dapp/sapphire/authentication.md
CedarMist Oct 10, 2023
dba0d02
Update docs/dapp/sapphire/authentication.md
CedarMist Oct 10, 2023
9158558
Update docs/dapp/sapphire/authentication.md
CedarMist Oct 10, 2023
e9a8410
Merge branch 'main' into CedarMist/712-auth
CedarMist Oct 10, 2023
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
209 changes: 209 additions & 0 deletions docs/dapp/sapphire/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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default? I don't think you can easily change this behavior in Sapphire so I would remove "By default".

`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 signs them (e.g. a
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
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
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
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`
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
* `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

CedarMist marked this conversation as resolved.
Show resolved Hide resolved
## 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the query is cached, then no user interaction is required? Where does EIP-712 popup then come from?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
These parameters are visible in the EIP-712 popup signed by the user. Queries
These parameters are visible in the [EIP-712][eip-712] popup signed by the user. Queries

with the same parameters will use the same leash.
Comment on lines +87 to +88
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
These parameters are visible in the EIP-712 popup signed by the user. Queries
with the same parameters will use the same leash.
These parameters are visible in the [EIP-712][eip-712] popup signed by the user. **Queries
with the same parameters will use the same leash.**

Perhaps this would be a good place to have a screenshot of Metamask showing some EIP-712 content?


## 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
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
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(
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
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`
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
}
}
```

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
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
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
CedarMist marked this conversation as resolved.
Show resolved Hide resolved

// Then in future, authenticated view calls can be performed by
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
// passing auth without further user interaction authenticated data
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
await contract.authenticatedViewCall(auth, ...args);
```
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions sidebarDapp.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const sidebars = {
'dapp/sapphire/quickstart',
'dapp/sapphire/guide',
'dapp/sapphire/browser',
'dapp/sapphire/authentication',
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
'dapp/sapphire/precompiles',
'dapp/sapphire/addresses',
],
Expand Down