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: SIWE tutorial #459

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

docs: SIWE tutorial #459

wants to merge 2 commits into from

Conversation

matevz
Copy link
Member

@matevz matevz commented Nov 22, 2024

Fixes #338

PREVIEW

@matevz matevz changed the title Matevz/docs/siwe tutorial docs: SIWE tutorial Nov 22, 2024
Copy link

netlify bot commented Nov 22, 2024

Deploy Preview for oasisprotocol-sapphire-paratime ready!

Name Link
🔨 Latest commit 6538060
🔍 Latest deploy log https://app.netlify.com/sites/oasisprotocol-sapphire-paratime/deploys/6744a3822d72e900085d6863
😎 Deploy Preview https://deploy-preview-459--oasisprotocol-sapphire-paratime.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@matevz matevz force-pushed the matevz/docs/siwe-tutorial branch 4 times, most recently from 0c345f4 to 64b3602 Compare November 25, 2024 15:59
@matevz matevz marked this pull request as ready for review November 25, 2024 15:59
@matevz matevz force-pushed the matevz/docs/siwe-tutorial branch from 64b3602 to ea39756 Compare November 25, 2024 15:59
@matevz matevz force-pushed the matevz/docs/siwe-tutorial branch from ea39756 to ffd51aa Compare November 25, 2024 16:04
@matevz matevz force-pushed the matevz/docs/siwe-tutorial branch from ffd51aa to 6538060 Compare November 25, 2024 16:19
all subsequent view calls will be signed. For example:

```python
from web3 import Web3
Copy link
Contributor

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

@matevz matevz requested a review from aefhm November 26, 2024 08:45
Comment on lines +12 to +15
* 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* 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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* authentication for your contract methods that require authentication.
* authentication in your contract functions.

(optional) I read "functions" more in Solidity documentation?

Comment on lines +54 to +55
signing account. This method is mostly appropriate for backend services,
since the frontend would require user interaction each time.
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
above. Suppose the following Solidity code:
above. Consider the following Solidity code:

Comment on lines +98 to +100
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:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The following changes were made:
We made the following changes:

Comment on lines +181 to +189
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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
Copy link
Contributor

Choose a reason for hiding this comment

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

(optional/nit) Technically JavaScript 😬

Comment on lines +241 to +247
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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.

Comment on lines +249 to +254
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).
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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';

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let token = ''

token = await messageBox.login(siweMsg, sig);
}

return messageBox.getSecretMessage(bearer);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

docs: SIWE tutorial and example
4 participants