Skip to content

Commit

Permalink
Merge pull request #131 from bcnmy/refactor/session-keys
Browse files Browse the repository at this point in the history
📝 Documentation Update: Intro and Session Validation Module
  • Loading branch information
Rahat-ch authored Dec 18, 2023
2 parents b539a63 + c899a7b commit 5147998
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 127 deletions.
53 changes: 47 additions & 6 deletions docs/tutorials/sessionkeys/introduction.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,55 @@
---
sidebar_label: 'Introduction'
sidebar_label: "Introduction"
sidebar_position: 1
---

# Introduction
# Introduction to Session Keys in dApps 🌐

This tutorial will cover how to use session keys in a dApp. In this tutorial we will:
This guide focuses on **session keys** in decentralized applications (dApps), highlighting their role in enhancing user experience and security.

- Go over a smart contract that allows for sessions to be validated for ERC20 token transfers.
- Go over initilization and creation of a session key on a Next JS frontend. (note that the steps for this will be the same as any React frontend)
- Execute a basic ERC20 token transfer without the need to sign
:::note
**Session Keys**: Session keys are **temporary cryptographic keys** used in dApps for validating transactions or operations without the need for constant user interaction, maintaining both security and ease of use. They're like one-time passwords but for blockchain transactions.
:::

## Why Session Keys?

Traditionally, blockchain operations require **explicit user approval** for each transaction, typically through a **digital signature**. While secure, this can be cumbersome, particularly for frequent transactions. Session keys offer a **seamless and user-friendly alternative**.

:::tip
**User-Friendly Transactions**: Utilizing session keys allows dApps to process multiple transactions with **single user approval**, greatly enhancing the user experience.
:::

## How Do Session Keys Work?

Session keys are **temporary** and have **defined permissions**. Once a user approves a session, the dApp can autonomously execute transactions within the session's limits, eliminating the need for further approvals.

:::info
**Scope and Permissions**: The scope of a session key, like the **types, duration and volume of transactions** it can authorize, is predefined. This ensures a balance between **control and convenience**.
:::

## Use Cases of Session Keys

- **Token Transfers**: Automating small, recurrent token transfers without requiring the user to confirm each one.
- **Voting in DAOs**: Facilitating users to participate in multiple votes in a decentralized autonomous organization (DAO) without repeated confirmations.

:::warning
**Security Reminder**: Despite their convenience, session keys must be handled with care. It's crucial to **strictly define their scope** to mitigate potential security risks.
:::

## Next Steps

Throughout this tutorial series, we'll explore:

- **Smart Contract Analysis:** Understanding the Session Validation Module.
- **Frontend Initialization:** Setting up the frontend using Next JS.
- **SDK Integration:** Integrating Biconomy SDK for smart account management.
- **Session Key Management:** Creating and managing session keys.
- **ERC20 Transfer Execution:** Using session keys for ERC20 token transfers.

Ready to get started? Let's head over to the **Session Validation Module** in the next section! 🌟

:::danger
**Advanced Topic**: This guide is tailored for those with a grasp of blockchain concepts and basic programming skills. New to blockchain? Consider reviewing foundational concepts first.
:::

---
208 changes: 87 additions & 121 deletions docs/tutorials/sessionkeys/sessionvalidationmodule.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
---
sidebar_label: 'Session Validation Module'
sidebar_label: "Session Validation Module"
sidebar_position: 2
---

# Session Validation Module

Before we continue it is important to understand Session Validation Modules and the Session Key Manager Module which interacts with them via the SDK.
Diving into the **Session Validation Module**, we explore its significance and interaction with the **Session Key Manager Module** via SDK.

To utilize session keys in a blockchain context, we require a smart contract that verifies whether a given user operation adheres to the permissions defined within the session key and confirms that the operation has been appropriately signed by the said session key. In this section we will cover a deployed contract that validates specific permissions to execute ERC20 token transfers. Using this ERC20 Validation module you will be able to create a small dApp that allows user to send a limited amount of funds to a specific address without needing to sign a transaction every single time.
:::note
Understanding the **Session Validation Modules** is crucial for leveraging **session keys** effectively in blockchain applications, particularly for tasks like managing ERC20 token transfers.
:::

The address of our deployed contract is: [0x000000D50C68705bd6897B2d17c7de32FB519fDA](https://mumbai.polygonscan.com/address/0x000000D50C68705bd6897B2d17c7de32FB519fDA#code)
## The Purpose of Session Validation Modules

The Smart contract we will be breaking down is the one shown below:
At the core, a Session Validation Module is a smart contract designed to authenticate whether a user's operation complies with the permissions set within a session key. It functions to validate user operations based on pre-defined session key permissions.

:::info
**Key Functionality**: We'll dissect a deployed contract that validates permissions for ERC20 token transfers, enabling dApps to execute transactions without user signatures every time.
Check the contract [here](https://mumbai.polygonscan.com/address/0x000000D50C68705bd6897B2d17c7de32FB519fDA#code).
:::

## Breaking Down the Contract

The smart contract we focus on is structured to validate user operations (userOps) for ERC20 transfers using session key signatures. It's tailored for standard ERC20 tokens and can interact with any contract implementing the method `(address, uint256)` interface.

:::warning
**Technical Deep Dive**: The following contract breakdown is technical in nature, aimed at developers with a solid understanding of smart contract functionalities.
:::

```javascript
// SPDX-License-Identifier: MIT
Expand All @@ -23,125 +37,87 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/**
* @title ERC20 Session Validation Module for Biconomy Smart Accounts.
* @dev Validates userOps for ERC20 transfers and approvals using a session key signature.
* - Recommended to use with standard ERC20 tokens only
* - Can be used with any method of any contract which implement
* method(address, uint256) interface
*
* @author Fil Makarov - <filipp.makarov@biconomy.io>
*/

contract ERC20SessionValidationModule is ISessionValidationModule {
/**
* @dev validates that the call (destinationContract, callValue, funcCallData)
* complies with the Session Key permissions represented by sessionKeyData
* @param destinationContract address of the contract to be called
* @param callValue value to be sent with the call
* @param _funcCallData the data for the call. is parsed inside the SVM
* @param _sessionKeyData SessionKey data, that describes sessionKey permissions
*/

function validateSessionParams(
address destinationContract,
uint256 callValue,
bytes calldata _funcCallData,
bytes calldata _sessionKeyData,
bytes calldata /*_callSpecificData*/
) external virtual override returns (address) {
(
address sessionKey,
address token,
address recipient,
uint256 maxAmount
) = abi.decode(_sessionKeyData, (address, address, address, uint256));

require(destinationContract == token, "ERC20SV Invalid Token");
require(callValue == 0, "ERC20SV Non Zero Value");
// Decode the session key data
(address sessionKey, address token, address recipient, uint256 maxAmount) =
abi.decode(_sessionKeyData, (address, address, address, uint256));

(address recipientCalled, uint256 amount) = abi.decode(
_funcCallData[4:],
(address, uint256)
);
// Validate the contract and call value
require(destinationContract == token, "Invalid Token");
require(callValue == 0, "Non Zero Value");

require(recipient == recipientCalled, "ERC20SV Wrong Recipient");
require(amount <= maxAmount, "ERC20SV Max Amount Exceeded");
// Check recipient and amount
(address recipientCalled, uint256 amount) =
abi.decode(_funcCallData[4:], (address, uint256));
require(recipient == recipientCalled, "Wrong Recipient");
require(amount <= maxAmount, "Max Amount Exceeded");
return sessionKey;
}

/**
* @dev validates if the _op (UserOperation) matches the SessionKey permissions
* and that _op has been signed by this SessionKey
* Please mind the decimals of your exact token when setting maxAmount
* @param _op User Operation to be validated.
* @param _userOpHash Hash of the User Operation to be validated.
* @param _sessionKeyData SessionKey data, that describes sessionKey permissions
* @param _sessionKeySignature Signature over the the _userOpHash.
* @return true if the _op is valid, false otherwise.
* @dev Validates if the UserOperation matches the SessionKey permissions.
*/
function validateSessionUserOp(
UserOperation calldata _op,
bytes32 _userOpHash,
bytes calldata _sessionKeyData,
bytes calldata _sessionKeySignature
) external pure override returns (bool) {

// Ensure correct operation and signature
require(
bytes4(_op.callData[0:4]) == EXECUTE_OPTIMIZED_SELECTOR ||
bytes4(_op.callData[0:4]) == EXECUTE_SELECTOR,
"ERC20SV Invalid Selector"
bytes4(_op.callData[0:4]) == EXECUTE_SELECTOR,
"Invalid Selector"
);

(
address sessionKey,
address token,
address recipient,
uint256 maxAmount
) = abi.decode(_sessionKeyData, (address, address, address, uint256));
// Decode session key data
(address sessionKey, address token, address recipient, uint256 maxAmount) =
abi.decode(_sessionKeyData, (address, address, address, uint256));

{
// we expect _op.callData to be `SmartAccount.execute(to, value, calldata)` calldata
(address tokenAddr, uint256 callValue, ) = abi.decode(
_op.callData[4:], // skip selector
(address, uint256, bytes)
);
if (tokenAddr != token) {
revert("ERC20SV Wrong Token");
}
if (callValue != 0) {
revert("ERC20SV Non Zero Value");
}
}
// working with userOp.callData
// check if the call is to the allowed recepient and amount is not more than allowed
// Validate token and call value
(address tokenAddr, uint256 callValue, ) =
abi.decode(_op.callData[4:], (address, uint256, bytes));
require(tokenAddr == token, "Wrong Token");
require(callValue == 0, "Non Zero Value");

// Validate recipient and amount
bytes calldata data;
{
uint256 offset = uint256(bytes32(_op.callData[4 + 64:4 + 96]));
uint256 length = uint256(
bytes32(_op.callData[4 + offset:4 + offset + 32])
);
//we expect data to be the `IERC20.transfer(address, uint256)` calldata
data = _op.callData[4 + offset + 32:4 + offset + 32 + length];
}
if (address(bytes20(data[16:36])) != recipient) {
revert("ERC20SV Wrong Recipient");
}
if (uint256(bytes32(data[36:68])) > maxAmount) {
revert("ERC20SV Max Amount Exceeded");
}
return
ECDSA.recover(
ECDSA.toEthSignedMessageHash(_userOpHash),
_sessionKeySignature
) == sessionKey;
uint256 offset = uint256(bytes32(_op.callData[4 + 64:4 + 96]));
uint256 length = uint256(bytes32(_op.callData[4 + offset:4 + offset + 32]));
data = _op.callData[4 + offset + 32:4 + offset + 32 + length];
require(address(bytes20(data[16:36])) == recipient, "Wrong Recipient");
require(uint256(bytes32(data[36:68])) <= maxAmount, "Max Amount Exceeded");

// Verify signature
return ECDSA.recover(ECDSA.toEthSignedMessageHash(_userOpHash), _sessionKeySignature) == sessionKey;
}
}
```

This contract extends the ISessionValidationModule interface which gives us the needed parameters for `validateSessionUserOp` and `validateSessionParams`. We will need to extend the implementation of these two functions in order to create a session validation module. You can view this interface [here](https://github.com/bcnmy/scw-contracts/blob/master/contracts/smart-account/modules/SessionValidationModules/ISessionValidationModule.sol).
The contract, extending the `ISessionValidationModule` interface, contains essential functions like `validateSessionUserOp` and `validateSessionParams`, each serving distinct roles in operation validation.

## Solidity Contract Breakdown

Here's the Solidity contract in question:

Coming back to the validation module lets break down the two functions:
### Function Analysis: `validateSessionUserOp`

## validateSessionUserOp
:::note
This function is essential for **validating user operations** against **session key permissions** and ensuring they are correctly signed.
:::

```javascript
/**
/**
* @dev validates if the _op (UserOperation) matches the SessionKey permissions
* and that _op has been signed by this SessionKey
* Please mind the decimals of your exact token when setting maxAmount
Expand Down Expand Up @@ -208,30 +184,21 @@ Coming back to the validation module lets break down the two functions:
}
```

**Execution Steps:**

When validating a single user operation this function will be called by the Session Key Manager Module from the Biconomy SDK. Let's break down the code:

This function takes four arguments:
1. **Match Function Selectors:** Verifies the user operation aligns with specific function selectors.
2. **Decode Session Key Data:** Extracts essential details like session key, token, recipient, and maximum transaction amount.
3. **Verify Operation Details:** Checks the operation's token address and call value, and confirms recipient and amount limits.
4. **Signature Validation:** Utilizes ECDSA to confirm the operation's signature matches the session key.

- Useroperation
- _userOpHash
- _sessionKeyData
- _sessionKeySignature
## Function Analysis: `validateSessionParams`


- The first check verifies that the useroperation supplied matches the function selectors that are defined in the interface.
- Next we decode the session key data - these arguments are what we need in order to validate if the userOp should be allowed to execute. Depending on your use case you may want different arguments here. In this case we get addresses for: sessionKey(the eoa that will sign on our behalf), token, recipent, and the maximum amount of tokens that can be transferred.
- Next we decode the token address and call value fields the userop
- If the token address in the userop does not match the token address of the session key we revert the transaction as this is not the token we have permission to send
- If the call value does not equal zero we will also revert. If your logic requires some value to be sent by the end user you would update this condition accordingly.
- Next we look at the call data and verify the recipient is who we want it to be as defined in the session and that the maximum amount of tokens to be sent has not been exceeded.
- Finally the ECDSA functions are used to confirm the operations signature matches the session key.

## validateSessionParams
:::note
This function plays a vital role in **batch session validation**, ensuring each operation aligns with the set session key permissions. It's key for processing multiple operations efficiently.
:::

```javascript

/**
/**
* @dev validates that the call (destinationContract, callValue, funcCallData)
* complies with the Session Key permissions represented by sessionKeyData
* @param destinationContract address of the contract to be called
Expand Down Expand Up @@ -265,24 +232,23 @@ This function takes four arguments:
require(amount <= maxAmount, "ERC20SV Max Amount Exceeded");
return sessionKey;
}

```

When validating a batch of sessions this function will be called by the Session Key Manager Module from the Biconomy SDK. Let's break down the code:
**Operational Flow:**

The `validateSessionParams` takes five arguments supplied by the Session Key Manager Module:
1. **Decode Session Key Data:** Extracts session key, token address, recipient address, and maximum token amount.
2. **Validation Checks:** Ensures the destination contract and call value are as required.
3. **Recipient and Amount Verification:** Compares recipient and transaction amount against session key data.
4. **Return Session Key:** If all checks pass, returns the session key address.

- destinationContract
- callValue
- _funcCallData
- _sessionKeyData
- _callSpecificData
Both `validateSessionUserOp` and `validateSessionParams` are integral to our dApp's security framework, ensuring strict adherence to permissions and enhancing transaction integrity.

This is very similar to the `validateSessionUserOp`
## Next Steps

- First we decode the session key address, token address, recipient address, and maximum amount of tokens. Again this can be altered based on your needs if you are building your own session key module.
- Two checks are then conducted to make sure that the destination contract is the token address and that the call value is zero. You can edit this if you need a value transferred for the transaction such as paying to mint an nft.
- The recipientCalled and amount are then returned from decoding the _funcCallData and compared to make sure they match with session key data
- the session key address is returned
With a foundational understanding of the Session Validation Module, we're set to move forward. Up next, we'll embark on initializing the frontend and integrating the Biconomy SDK, crucial steps in bringing our dApp to life.

This is a breakdown of the contract we will be using to power our session key demo. In the next section we will initialize our frontend and start integrating the Biconomy SDK.
:::tip
**Explore the Interface**: Familiarize yourself with the `ISessionValidationModule` interface [here](https://github.com/bcnmy/scw-contracts/blob/master/contracts/smart-account/modules/SessionValidationModules/ISessionValidationModule.sol) for a comprehensive understanding.
:::

---

0 comments on commit 5147998

Please sign in to comment.