-
Notifications
You must be signed in to change notification settings - Fork 29
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: SIWE tutorial #459
base: main
Are you sure you want to change the base?
docs: SIWE tutorial #459
Conversation
✅ Deploy Preview for oasisprotocol-sapphire-paratime ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
0c345f4
to
64b3602
Compare
64b3602
to
ea39756
Compare
ea39756
to
ffd51aa
Compare
ffd51aa
to
6538060
Compare
all subsequent view calls will be signed. For example: | ||
|
||
```python | ||
from web3 import Web3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might also add the compilation and deployment step in the function
import os
import json
from typing import Optional, Tuple
from web3 import Web3
from web3.middleware import construct_sign_and_send_raw_middleware
from eth_account.signers.local import LocalAccount
from eth_account import Account
from sapphirepy import sapphire
from solcx import compile_standard, install_solc
def deploy_and_get_message(
solidity_file: str,
network_name: Optional[str] = "sapphire-localnet"
) -> Tuple[str, str]:
# Initialize web3 and account
w3 = Web3(Web3.HTTPProvider(sapphire.NETWORKS[network_name]))
account: LocalAccount = Account.from_key(os.environ.get("PRIVATE_KEY"))
w3.middleware_onion.add(construct_sign_and_send_raw_middleware(account))
w3 = sapphire.wrap(w3)
# Compile the Solidity file
install_solc('0.8.0') # Install specific solidity version
with open(solidity_file, 'r') as file:
solidity_code = file.read()
compiled_contract = compile_standard({
"language": "Solidity",
"sources": {solidity_file: {"content": solidity_code}},
"settings": {
"outputSelection": {
"*": {
"*": ["abi", "metadata", "evm.bytecode", "evm.sourceMap"]
}
}
}
}, solc_version="0.8.0")
# Get contract binary and abi
contract_name = solidity_file.split('/')[-1].split('.')[0]
contract_data = compiled_contract["contracts"][solidity_file][contract_name]
bytecode = contract_data["evm"]["bytecode"]["object"]
abi = contract_data["abi"]
# Deploy contract
Contract = w3.eth.contract(abi=abi, bytecode=bytecode)
nonce = w3.eth.get_transaction_count(account.address)
# Submit deployment transaction
transaction = Contract.constructor().build_transaction({
"chainId": w3.eth.chain_id,
"from": account.address,
"nonce": nonce,
"gasPrice": w3.eth.gas_price,
})
# Send transaction and wait for receipt
tx_hash = w3.eth.send_transaction(transaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
contract_address = tx_receipt.contractAddress
# Create contract instance and get message
contract = w3.eth.contract(address=contract_address, abi=abi)
message = contract.functions.getMessage().call()
return contract_address, message
* 2. Any smart contract method that requires authentication takes this token | ||
* as an argument. It passes this token to `authMsgSender()` to verify it | ||
* and obtain the **authenticated** user address. This address can then | ||
* serve as a user ID for authorization. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* 2. Any smart contract method that requires authentication takes this token | |
* as an argument. It passes this token to `authMsgSender()` to verify it | |
* and obtain the **authenticated** user address. This address can then | |
* serve as a user ID for authorization. | |
* 2. Any smart contract method that requires authentication can take this token | |
* as an argument. Passing this token to `authMsgSender()` verifies it | |
* and returns the **authenticated** user address. This verified address can then | |
* serve as a user ID for authorization. |
@@ -16,17 +16,18 @@ struct Bearer { | |||
/** | |||
* @title Base contract for SIWE-based authentication | |||
* @notice Inherit this contract, if you wish to enable SIWE-based |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @notice Inherit this contract, if you wish to enable SIWE-based | |
* @notice Inherit this contract if you wish to enable SIWE-based |
@@ -16,17 +16,18 @@ struct Bearer { | |||
/** | |||
* @title Base contract for SIWE-based authentication | |||
* @notice Inherit this contract, if you wish to enable SIWE-based | |||
* authentication for your contract methods that require authenticated calls. | |||
* authentication for your contract methods that require authentication. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* authentication for your contract methods that require authentication. | |
* authentication in your contract functions. |
(optional) I read "functions" more in Solidity documentation?
signing account. This method is mostly appropriate for backend services, | ||
since the frontend would require user interaction each time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to nudge people towards 3?
:::info Example: Oasis starter in Python | ||
|
||
To see a running example of the Python code including the end-to-end | ||
encryption and signed queries check out the official [Oasis starter project |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
encryption and signed queries check out the official [Oasis starter project | |
encryption and signed queries, check out the official [Oasis starter project |
Let's see how Sapphire interprets different contract calls. Suppose the | ||
following solidity code: | ||
Let's see how Sapphire executes contract calls for each call variant presented | ||
above. Suppose the following Solidity code: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
above. Suppose the following Solidity code: | |
above. Consider the following Solidity code: |
Suppose the contract below which is a slightly extended version of the one | ||
above. The contract is used for storing and retrieving secret messages by | ||
the contract owner only: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose the contract below which is a slightly extended version of the one | |
above. The contract is used for storing and retrieving secret messages by | |
the contract owner only: | |
Consider this slightly extended version of the contract | |
above. Only the owner is allowed to store and retrieve secret message: |
|
||
## Daily Sign-In with EIP-712 | ||
SIWE stands for "Sign-In with Ethereum" and is formally defined in [EIP-4361]. | ||
The initial use case for SIWE was using your Ethereum account as a form of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initial use case for SIWE was using your Ethereum account as a form of | |
The initial use case for SIWE involved using your Ethereum account as a form of |
## Daily Sign-In with EIP-712 | ||
SIWE stands for "Sign-In with Ethereum" and is formally defined in [EIP-4361]. | ||
The initial use case for SIWE was using your Ethereum account as a form of | ||
authentication for off-chain services (as an alternative to the user names and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
authentication for off-chain services (as an alternative to the user names and | |
authentication for off-chain services (providing an alternative to user names and |
string public constant SIGNIN_TYPE = "SignIn(address user,uint32 time)"; | ||
bytes32 public constant SIGNIN_TYPEHASH = keccak256(bytes(SIGNIN_TYPE)); | ||
bytes32 public immutable DOMAIN_SEPARATOR; | ||
The following changes were made: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following changes were made: | |
We made the following changes: |
1. In the constructor, we need to define the domain name where the dApp frontend | ||
will be deployed at. This domain is included inside the SIWE log-in message | ||
and is verified by the user-facing wallet to make sure they are accessing the | ||
contract from a legit domain. | ||
2. The `onlyOwner` modifier is extended with an optional `bytes memory bearer` | ||
parameter and is considered in the case of invalid `msg.sender` value. The | ||
same modifier is used for authenticating both SIWE queries and the | ||
transactions. | ||
3. `getSecretMessage` was extended with the `bytes memory bearer` session token. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. In the constructor, we need to define the domain name where the dApp frontend | |
will be deployed at. This domain is included inside the SIWE log-in message | |
and is verified by the user-facing wallet to make sure they are accessing the | |
contract from a legit domain. | |
2. The `onlyOwner` modifier is extended with an optional `bytes memory bearer` | |
parameter and is considered in the case of invalid `msg.sender` value. The | |
same modifier is used for authenticating both SIWE queries and the | |
transactions. | |
3. `getSecretMessage` was extended with the `bytes memory bearer` session token. | |
1. In the constructor, we need to define the domain name where the dApp frontend | |
will be deployed. This domain is included inside the SIWE log-in message | |
and is verified by the user-facing wallet to make sure they are accessing the | |
contract from a legitimate domain. | |
2. The `onlyOwner` modifier is extended with an optional `bytes memory bearer` | |
parameter and is considered in the case of invalid `msg.sender` value. The | |
same modifier is used for authenticating both SIWE queries and the | |
transactions. | |
3. `getSecretMessage` was extended with the `bytes memory bearer` session token. |
uint32 time; | ||
SignatureRSV rsv; | ||
} | ||
On the client side, the TypeScript code running inside a browser needs to make |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(optional/nit) Technically JavaScript 😬
The [EIP-712] proposal defines a method to show data to the user in a structured | ||
fashion so they can verify it and sign it. In the frontend apps signing a view | ||
call would require user interaction each time—sometimes even multiple times per | ||
page—which results in bad UX. Backend services on the other hand can have direct | ||
access to an Ethereum wallet without needing user interaction. This is possible | ||
because such services never access random websites, but execute only trusted | ||
code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The [EIP-712] proposal defines a method to show data to the user in a structured | |
fashion so they can verify it and sign it. In the frontend apps signing a view | |
call would require user interaction each time—sometimes even multiple times per | |
page—which results in bad UX. Backend services on the other hand can have direct | |
access to an Ethereum wallet without needing user interaction. This is possible | |
because such services never access random websites, but execute only trusted | |
code. | |
The [EIP-712] proposal defines a method to show data to the user in a structured | |
fashion, so they can verify it and sign it. On the frontend, apps signing a view | |
call would require user interaction each time—sometimes even multiple times per | |
page—which is bad UX that frustrates users. Backend services on the other hand can have direct | |
access to an Ethereum wallet without needing user interaction. This is possible | |
because these services do not access unspecified websites, and only execute trusted | |
code. |
The Sapphire wrappers for [Go][sp-go] and [Python][sp-py] will make sure to | ||
**automatically sign any view calls** you make to a contract running on Sapphire | ||
using the proposed [EIP-712] method. Suppose we store a private key of an | ||
account used for signing the view calls inside a `PRIVATE_KEY` environment variable. The following snippets | ||
demonstrate how to trigger signed queries without any changes to the original | ||
`MessageBox` contract from [above](#authenticated-view-calls). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Sapphire wrappers for [Go][sp-go] and [Python][sp-py] will make sure to | |
**automatically sign any view calls** you make to a contract running on Sapphire | |
using the proposed [EIP-712] method. Suppose we store a private key of an | |
account used for signing the view calls inside a `PRIVATE_KEY` environment variable. The following snippets | |
demonstrate how to trigger signed queries without any changes to the original | |
`MessageBox` contract from [above](#authenticated-view-calls). | |
The Sapphire wrappers for [Go][sp-go] and [Python][sp-py] will make sure to | |
**automatically sign any view calls** you make to a contract running on Sapphire | |
using the proposed [EIP-712] method. Suppose we want to store the private key of an | |
account used to sign the view calls inside a `PRIVATE_KEY` environment variable. The following snippets | |
demonstrate how to trigger signed queries without any changes to the original | |
`MessageBox` contract from [above](#authenticated-view-calls). |
|
||
```typescript | ||
import {SiweMessage} from 'siwe'; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let token = '' | |
token = await messageBox.login(siweMsg, sig); | ||
} | ||
|
||
return messageBox.getSecretMessage(bearer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return messageBox.getSecretMessage(bearer); | |
return messageBox.getSecretMessage(token); |
bearer
isn't set here, I guess you meant token
. Alternatively use the same code like in the demo starter withbearer.value
, to have it consistent between the example and the docs. Or use bearer
also in the code before to have a consistent name with the Solidity code before.
Fixes #338
PREVIEW