From d1ef55911a09bd75ff9ac283d826be0d68e696b9 Mon Sep 17 00:00:00 2001 From: moskalyk Date: Fri, 13 Sep 2024 09:47:23 -0400 Subject: [PATCH 1/6] added work in progress branch --- .../guides/typed-on-chain-signatures.mdx | 253 ++++++++++++++++++ nav.ts | 18 ++ 2 files changed, 271 insertions(+) create mode 100644 docs/pages/guides/typed-on-chain-signatures.mdx diff --git a/docs/pages/guides/typed-on-chain-signatures.mdx b/docs/pages/guides/typed-on-chain-signatures.mdx new file mode 100644 index 00000000000..4bd4069d64e --- /dev/null +++ b/docs/pages/guides/typed-on-chain-signatures.mdx @@ -0,0 +1,253 @@ +--- +title: EIP712 and EIP1271 Signatures Enabling Message Verification +description: How to perform signature verification of messages on-chain and for non-deployed wallet contracts to ensure the integrity of data on a blockchain +--- + +import {Callout} from 'vocs/components' + +# Verify Typed (EIP712) and Magic (EIP1271) Signature Message Verification + +Time to complete: XX minutes + +In this guide we'll walk you through how to use provided source codefor an EVM based contract, the inner workings of a Universal Signature Validator contract, and how to perform in-app signature verification with typed data providing expensive application capaibilities to message integrity across the ecosystem. + +The general flow for this application can be seen in the following sequence diagram: + +![]() + +This can be accomplished with 6 steps: + +1. [Create a Builder Project & Obtain an Access Key]() +2. [Initialize React Vite Application]() +3. [Use Sequence Wallet for User Sign in]() +4. [Use EIP712 Typed Data to Generate EIP6492 Signatures]() +5. [Deploy Contract for EIP1271 Validation]() +6. [Render Response from Verifying Contract]() + +## 1. Initialize React Vite Application + +First, follow [this guide](/solutions/builder/getting-started) to create a project in the Sequence Builder. + +## 2. Initialize React Vite Application + +Next, begin by initializing a new project that will hold all the code neccessary to capture signatures and verification responses from the blockchain: + +```shell +pnpm create vite +``` + +This should create a blank project that you can start adding elements and logic to. + +## 3. Use Sequence Wallet for User Sign in + +Install the necessary packages required for the project to function: + +```shell +pnpm i 0xsequence ethers +``` + +Then enable a user to sign in with on your chosein network and the obtained project access key from step 1. + +```typescript + +import { sequence } from '0xsequence' + +sequence.initWallet(PROJECT_ACCESS_KEY, { + defaultNetwork: 'sepolia', +}); + +const signIn = async () => { + const wallet = sequence.getWallet() + const details = await wallet.connect({app: 'sequence signature validation demo'}) + + if(details){ + console.log('is signed in') + console.log(details) + } +} + +return ( + ... + + ... +) +``` + +## 4. Use EIP712 Typed Data to Generate EIP6492 Signatures + +Next, we'll define a custom typed data in typescript and using the utilities library from Sequence constructing a `TypedData` type + +``` +import { sequence } from '0xsequence' + +interface Person { + name: string; + wallet: string; + message: string; +} + +const verifySignature = () => { + const typedData: sequence.utils.TypedData = { + domain: { + // Domain settings must match verifying contract + name: "Sequence Signature Validation Demo", + version: "1", + chainId, + verifyingContract: VERIFYING_CONTRACT_ADDRESS, + }, + types: { + Person: [ + { name: "name", type: "string" }, + { name: "wallet", type: "address" }, + { name: "message", type: "string" }, + ], + }, + message: person, + primaryType: "Person", + }; + ... +} + +``` + +Then we will sign the type message object with the various referenced properties: + +``` + const wallet = await sequence.getWallet() + const signer = wallet.getSigner(DEFAULT_NETWORK_CHAIN_ID); + + const signature = await signer.signTypedData( + typedData.domain, + typedData.types, + typedData.message, + { + chainId, + eip6492: true, // enabling signatures for non-deployed wallet contracts + } + ); + + console.log("signature", signature); +``` + +Great, attach the function to a button and see the signature be generated after a user has clicked the button: + +``` + +``` + +## 5. Deploy Contract for EIP1271 Validation + +We will now provide source code that you can use in something like [Remix](https://remix.ethereum.org/) to deploy a contract, or even something like [Foundry](https://book.getfoundry.sh/reference/forge/forge): + +::::steps + +#### Universal Validator + +The Universal Validator can hypothetically be deployed once, and share for many applications, making it composable and reusable. + +You can find the source code [here]() that you can use to deploy + +### Custom Contract Verifer + +```javascript +contract EIP712Verifier is EIP712 { + using ECDSA for bytes32; + + IERC6492 public immutable ERC6492_SIG_VALIDATOR; + + bytes32 private constant _ERC6492_DETECTION_SUFFIX = + 0x6492649264926492649264926492649264926492649264926492649264926492; + bytes32 private constant _PERSON_TYPEHASH = keccak256(bytes("Person(string name,address wallet,string message)")); + + constructor(address erc6492SigValidator) { + ERC6492_SIG_VALIDATOR = IERC6492(erc6492SigValidator); + } + ... +} + +``` +### Hey + +``` + /// @dev Verifies the signature of a person. + function verifySignature(address signer, Person memory person, bytes calldata signature) + external + returns (bool success) + { + bytes32 digest = personDigest(person); + return validateSigner(signer, digest, signature); + } +``` + +``` + /// @dev Returns the EIP712 hash of a person. + function personDigest(Person memory person) public view returns (bytes32 digest) { + bytes32 structHash = keccak256( + abi.encode(_PERSON_TYPEHASH, keccak256(bytes(person.name)), person.wallet, keccak256(bytes(person.message))) + ); + digest = EIP712._hashTypedData(structHash); + } +``` + + +``` +/// @dev Validates the ERC1271 signature of a signer. + function validateSigner(address signer, bytes32 digest, bytes calldata signature) internal returns (bool success) { + if (signature.length >= 32) { + bool isCounterfactual = + bytes32(signature[signature.length - 32:signature.length]) == _ERC6492_DETECTION_SUFFIX; + if (isCounterfactual) { + return ERC6492_SIG_VALIDATOR.isValidSig(signer, digest, signature); + } + } + + try IERC1271(signer).isValidSignature(digest, signature) returns (bytes4 magicValue) { + return magicValue == IERC1271.isValidSignature.selector; + } catch {} + return false; + } +``` +:::: + +## 6. Render Response from Verifying Contract + +``` +async function checkSignatureValidity( + address: string, + person: Person, + signature: string + ): Promise { + const provider = new ethers.JsonRpcProvider( + "https://nodes.sequence.app/sepolia/AQAAAAAAAJbeftY2hQWuQG48gxVfoHYXKcw" + ); + console.log(address, person, signature); + const contract = new ethers.Contract( + VERIFYING_CONTRACT_ADDR, + ABI, + provider + ); + console.log("signature", signature); + try { + const data = contract.interface.encodeFunctionData("verifySignature", [ + address, + person, + signature, + ]); + console.log("data", data); + console.log("checking on chain"); + // Call the `isValidSignature` function from the contract + const result = await contract.verifySignature.staticCall( + address, + person, + signature + ); + console.log(`Signature is ${result ? "valid" : "invalid"}`); + return result; + } catch (error) { + console.error("Error calling isValidSignature:", error); + } + return false; + } +``` + +## Conclusion diff --git a/nav.ts b/nav.ts index 13b8a41f60b..5fcfd297933 100644 --- a/nav.ts +++ b/nav.ts @@ -518,6 +518,24 @@ export const sidebar = { // ] }, + { + text: 'Build Typed Data On-chain Verification Signatures', + collapsed: true, + link: '/guides/typed-on-chain-signatures', + // items: [ + // { text: 'Sequence Builder Console Signup & Project Creation', link: '/guides/lootbox-guide#1-builder-console-signup--project-creation' }, + // { text: 'Access Key Management', link: '/guides/lootbox-guide#2-access-key-management' }, + // { text: 'Sequence Kit integration', link: '/guides/lootbox-guide#3-sequence-kit-integration' }, + // { text: 'iframe-to-Dapp Communication', link: '/guides/lootbox-guide#4-iframe-to-dapp-communication' }, + // { text: 'Deploy a Contract & Sponsor Gas', link: '/guides/lootbox-guide#5-deploy-a-contract--sponsor-gas' }, + // { text: 'Deploy a Cloudflare Worker', link: '/guides/lootbox-guide#6-deploy-a-cloudflare-worker' }, + // { text: 'Generating AI Prompts & Images', link: '/guides/lootbox-guide#7-generating-ai-prompts--images' }, + // { text: 'Store Media To Sequence Metadata Service', link: '/guides/lootbox-guide#8-store-media-to-sequence-metadata-service' }, + // { text: 'Securing Your Cloudflare Worker', link: '/guides/lootbox-guide#9-securing-your-cloudflare-worker' }, + // { text: '(Optional) Naive Mint Restriction Per Wallet', link: '/guides/lootbox-guide#10-optional-naive-mint-restriction-per-wallet' }, + + // ] + }, { text: 'Build a Backend Transaction Service', collapsed: true, From 73307f469b8f7f623d0ee21561da952ea457bab8 Mon Sep 17 00:00:00 2001 From: moskalyk Date: Fri, 13 Sep 2024 16:21:43 -0400 Subject: [PATCH 2/6] added guide --- .../guides/typed-on-chain-signatures.mdx | 257 ++++++++++-------- .../eip712-eip1271-signatures.png | 3 + nav.ts | 2 +- 3 files changed, 155 insertions(+), 107 deletions(-) create mode 100644 docs/public/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png diff --git a/docs/pages/guides/typed-on-chain-signatures.mdx b/docs/pages/guides/typed-on-chain-signatures.mdx index 4bd4069d64e..5cdbc9d9543 100644 --- a/docs/pages/guides/typed-on-chain-signatures.mdx +++ b/docs/pages/guides/typed-on-chain-signatures.mdx @@ -1,36 +1,36 @@ --- -title: EIP712 and EIP1271 Signatures Enabling Message Verification -description: How to perform signature verification of messages on-chain and for non-deployed wallet contracts to ensure the integrity of data on a blockchain +title: EIP712 and EIP1271 Signatures Enabling Message Parameter Verification and Signature Validation +description: How to perform signature verification of messages on-chain and for non-deployed wallet contracts to ensure the integrity of data on a blockchain from signers --- import {Callout} from 'vocs/components' -# Verify Typed (EIP712) and Magic (EIP1271) Signature Message Verification +# Verified Typed Data & Validated Signature Messages On-chain Time to complete: XX minutes -In this guide we'll walk you through how to use provided source codefor an EVM based contract, the inner workings of a Universal Signature Validator contract, and how to perform in-app signature verification with typed data providing expensive application capaibilities to message integrity across the ecosystem. +In this guide we'll walk you through how to use provided source code for an EVM based contract representing a Universal Signature Validator contract, explain the inner workings of custom on-chain message verifier contract that composes the Universal Validator, and how to perform in-app signature verification with typed data providing application capabilities that ensure message integrity across the ecosystem. The general flow for this application can be seen in the following sequence diagram: -![]() +![sequence flow for eip712 eip1271 verfying and validating signatures](/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png) This can be accomplished with 6 steps: -1. [Create a Builder Project & Obtain an Access Key]() -2. [Initialize React Vite Application]() -3. [Use Sequence Wallet for User Sign in]() -4. [Use EIP712 Typed Data to Generate EIP6492 Signatures]() -5. [Deploy Contract for EIP1271 Validation]() -6. [Render Response from Verifying Contract]() +1. [Create a Builder Project & Obtain an Access Key](/guides/typed-on-chain-signatures#1-create-a-builder-project--obtain-an-access-key) +2. [Initialize React Vite Application](/guides/typed-on-chain-signatures#2-initialize-react-vite-application) +3. [Use Sequence Wallet for User Sign in](/guides/typed-on-chain-signatures#3-use-sequence-wallet-for-user-sign-in) +4. [Use EIP712 Typed Data to Generate EIP6492 Signatures](/guides/typed-on-chain-signatures#4-use-eip712-typed-data-to-generate-eip6492-signatures) +5. [Deploy Contract for EIP1271 Validation](/guides/typed-on-chain-signatures#5-deploy-contract-for-eip1271-validation) +6. [Render Response from Verifying & Validating Contract](/guides/typed-on-chain-signatures#6-render-response-from-verifying--validating-contractt) -## 1. Initialize React Vite Application +## 1. Create a Builder Project & Obtain an Access Key -First, follow [this guide](/solutions/builder/getting-started) to create a project in the Sequence Builder. +First, follow [this guide](/solutions/builder/getting-started) to create a project in the Sequence Builder and obtain a project access key. ## 2. Initialize React Vite Application -Next, begin by initializing a new project that will hold all the code neccessary to capture signatures and verification responses from the blockchain: +Next, begin by initializing a new project that will hold all the code neccessary to generate signatures and validation responses from the blockchain: ```shell pnpm create vite @@ -46,7 +46,7 @@ Install the necessary packages required for the project to function: pnpm i 0xsequence ethers ``` -Then enable a user to sign in with on your chosein network and the obtained project access key from step 1. +Then enable a user to sign in with on your chosen network and the obtained project access key from step 1. ```typescript @@ -75,9 +75,11 @@ return ( ## 4. Use EIP712 Typed Data to Generate EIP6492 Signatures -Next, we'll define a custom typed data in typescript and using the utilities library from Sequence constructing a `TypedData` type +Next, we'll define a custom typed data in typescript and using the utilities library from Sequence constructing a `TypedData` type, where we will be verifying a message structure with `name`, `wallet`, and `message` parameters: -``` +In this example `VERIFYING_CONTRACT_ADDRESS` is the smart contract we deployed on `sepolia` but we will show you in the next step what this contract does: + +```typescript import { sequence } from '0xsequence' interface Person { @@ -86,6 +88,8 @@ interface Person { message: string; } +const VERIFYING_CONTRACT_ADDRESS = '0x339e65cf64c58160e2f2681016a1c2841d7ef2e7' + const verifySignature = () => { const typedData: sequence.utils.TypedData = { domain: { @@ -112,51 +116,56 @@ const verifySignature = () => { Then we will sign the type message object with the various referenced properties: -``` - const wallet = await sequence.getWallet() - const signer = wallet.getSigner(DEFAULT_NETWORK_CHAIN_ID); - - const signature = await signer.signTypedData( - typedData.domain, - typedData.types, - typedData.message, - { - chainId, - eip6492: true, // enabling signatures for non-deployed wallet contracts - } - ); +```typescript +const wallet = await sequence.getWallet() +const signer = wallet.getSigner(DEFAULT_NETWORK_CHAIN_ID); + +const signature = await signer.signTypedData( + typedData.domain, + typedData.types, + typedData.message, + { + chainId, + eip6492: true, // enabling signatures for non-deployed wallet contracts + } +); - console.log("signature", signature); +console.log("signature", signature); ``` Great, attach the function to a button and see the signature be generated after a user has clicked the button: -``` +```typescript ``` ## 5. Deploy Contract for EIP1271 Validation -We will now provide source code that you can use in something like [Remix](https://remix.ethereum.org/) to deploy a contract, or even something like [Foundry](https://book.getfoundry.sh/reference/forge/forge): +We will now provide source code that you can use in something like [Remix](https://remix.ethereum.org/) to deploy a contract, or even something like [Foundry](https://book.getfoundry.sh/reference/forge/forge) for deploying with the [Sequence Builder](https://sequence.build) ::::steps -#### Universal Validator +### Universal Signature Validator -The Universal Validator can hypothetically be deployed once, and share for many applications, making it composable and reusable. +The Universal Signature Validator can hypothetically be deployed once, and shared with many applications, making it composable and reusable. It's use is for both off-chain and on-chain smart contract wallets for [EIP6492](https://eips.ethereum.org/EIPS/eip-6492) enabled wallets. -You can find the source code [here]() that you can use to deploy +You can find the source code [here](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo/blob/master/contracts/src/UniversalSigValidator.sol) that you can use to deploy. + +If you would like to learn more about this contract, feel free to reach out the [Sequence Support](/support). ### Custom Contract Verifer +The next contract we'll explain more in-depth with the various functions, as this contract can be customized. Begin with the following basics with the Universal Validator passed in the constructor in the first step: + ```javascript contract EIP712Verifier is EIP712 { using ECDSA for bytes32; - IERC6492 public immutable ERC6492_SIG_VALIDATOR; + IERC6492 public immutable ERC6492_SIG_VALIDATOR; // the universal signature validator - bytes32 private constant _ERC6492_DETECTION_SUFFIX = - 0x6492649264926492649264926492649264926492649264926492649264926492; + bytes32 private constant _ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492; + + // this line of code must be customized to the struct you're verifying bytes32 private constant _PERSON_TYPEHASH = keccak256(bytes("Person(string name,address wallet,string message)")); constructor(address erc6492SigValidator) { @@ -166,88 +175,124 @@ contract EIP712Verifier is EIP712 { } ``` -### Hey +### Verify Signature -``` - /// @dev Verifies the signature of a person. - function verifySignature(address signer, Person memory person, bytes calldata signature) - external - returns (bool success) - { - bytes32 digest = personDigest(person); - return validateSigner(signer, digest, signature); - } -``` +Next, we have the verify signature function which both: creates the message hash digest and validates the signer: +```javascript +/// @dev Verifies the signature of a person. +function verifySignature(address signer, Person memory person, bytes calldata signature) + external + returns (bool success) +{ + bytes32 digest = personDigest(person); + return validateSigner(signer, digest, signature); +} ``` - /// @dev Returns the EIP712 hash of a person. - function personDigest(Person memory person) public view returns (bytes32 digest) { - bytes32 structHash = keccak256( - abi.encode(_PERSON_TYPEHASH, keccak256(bytes(person.name)), person.wallet, keccak256(bytes(person.message))) - ); - digest = EIP712._hashTypedData(structHash); - } -``` +### Custom Person Digest + +In the following function we recreate the struct hash with the passed in parameters, which can be extended to include more or less parameters: +```javascript +/// @dev Returns the EIP712 hash of a person. +function personDigest(Person memory person) public view returns (bytes32 digest) { + bytes32 structHash = keccak256( + abi.encode(_PERSON_TYPEHASH, keccak256(bytes(person.name)), person.wallet, keccak256(bytes(person.message))) + ); + digest = EIP712._hashTypedDataV4(structHash); +} ``` + +### Validate Signer + +Next, we validate the `signer` address, the `digest` and `signature` and see if the signature created is counterfactual, basically meaning if it's not yet put on-chain. If it's not, we use the universal validator with [EIP6492](https://eips.ethereum.org/EIPS/eip-6492), if it is on-chain, we use the [EIP1271](https://eips.ethereum.org/EIPS/eip-1271) signature validation function: + +```javascript /// @dev Validates the ERC1271 signature of a signer. - function validateSigner(address signer, bytes32 digest, bytes calldata signature) internal returns (bool success) { - if (signature.length >= 32) { - bool isCounterfactual = - bytes32(signature[signature.length - 32:signature.length]) == _ERC6492_DETECTION_SUFFIX; - if (isCounterfactual) { - return ERC6492_SIG_VALIDATOR.isValidSig(signer, digest, signature); - } +function validateSigner(address signer, bytes32 digest, bytes calldata signature) internal returns (bool success) { + if (signature.length >= 32) { + bool isCounterfactual = + bytes32(signature[signature.length - 32:signature.length]) == _ERC6492_DETECTION_SUFFIX; + if (isCounterfactual) { + return ERC6492_SIG_VALIDATOR.isValidSig(signer, digest, signature); } - - try IERC1271(signer).isValidSignature(digest, signature) returns (bytes4 magicValue) { - return magicValue == IERC1271.isValidSignature.selector; - } catch {} - return false; } + + try IERC1271(signer).isValidSignature(digest, signature) returns (bytes4 magicValue) { + return magicValue == IERC1271.isValidSignature.selector; + } catch {} + return false; +} ``` :::: -## 6. Render Response from Verifying Contract +You're ready to deploy both contracts, make sure to choose your network. + +## 6. Render Response from Verifying & Validating Contract + +We pass the signatures and call the contract deployed using `ethers` with the passed in `PROJECT_ACCESS_KEY` in the following steps: + +::::steps +### Create a Provider + +Create a provider using the project access key: + +```typescript +import { ethers } from 'ethers' +const provider = new ethers.JsonRpcProvider( + "https://nodes.sequence.app/sepolia/AQAAAAAAAJbeftY2hQWuQG48gxVfoHYXKcw" +); ``` -async function checkSignatureValidity( - address: string, - person: Person, - signature: string - ): Promise { - const provider = new ethers.JsonRpcProvider( - "https://nodes.sequence.app/sepolia/AQAAAAAAAJbeftY2hQWuQG48gxVfoHYXKcw" - ); - console.log(address, person, signature); - const contract = new ethers.Contract( - VERIFYING_CONTRACT_ADDR, - ABI, - provider - ); - console.log("signature", signature); - try { - const data = contract.interface.encodeFunctionData("verifySignature", [ - address, - person, - signature, - ]); - console.log("data", data); - console.log("checking on chain"); - // Call the `isValidSignature` function from the contract - const result = await contract.verifySignature.staticCall( - address, - person, - signature - ); - console.log(`Signature is ${result ? "valid" : "invalid"}`); - return result; - } catch (error) { - console.error("Error calling isValidSignature:", error); + +### Initialize an Ethers Contract +Import the ABI that was generated from step 5, include the provider, and input the verifying contract address: + +```typescript +import { ABI } from "./abi"; + +const contract = new ethers.Contract( + VERIFYING_CONTRACT_ADDRESS, + ABI, + provider +); +``` + +### Static Call the Verify Signature Function + +By performing a static call on the function, we simulate if the wallet has been deployed or not, and this returns a result specifying if the validation was true or false: + +```typescript +const address = await wallet.getAddress() + +const person: Person = { + name: "user", + wallet: address, + message: message, +} + +const signature = await signer.signTypedData( + typedData.domain, + typedData.types, + typedData.message, + { + chainId, + eip6492: true, } - return false; - } +); + +const result = await contract.verifySignature.staticCall( + address, + person, + signature +); + +console.log(`Signature is ${result ? "valid" : "invalid"}`); +return result; ``` +:::: ## Conclusion + +Now that we have message structs being passed to a blockchain and messages verified with their inputs, we can extend the application to a multitude of use cases that ensure users are signing the right information (e.g. permitting to spend ERC20's, perform off-chain bidding, etc.). \ No newline at end of file diff --git a/docs/public/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png b/docs/public/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png new file mode 100644 index 00000000000..bfa8a72f111 --- /dev/null +++ b/docs/public/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13b16c6f0eaf45dd89b2c5957c3cd4c84f7217483ebc9686cb32503a1636f186 +size 98694 diff --git a/nav.ts b/nav.ts index 5fcfd297933..f5fb5543444 100644 --- a/nav.ts +++ b/nav.ts @@ -519,7 +519,7 @@ export const sidebar = { // ] }, { - text: 'Build Typed Data On-chain Verification Signatures', + text: 'Build On-chain Typed Data & Signature Validation', collapsed: true, link: '/guides/typed-on-chain-signatures', // items: [ From ad174e9f6047947607de10b76cb9b8b4a7235a94 Mon Sep 17 00:00:00 2001 From: moskalyk Date: Fri, 13 Sep 2024 16:24:50 -0400 Subject: [PATCH 3/6] updated sequence diagram --- .../eip712-eip1271-signatures/eip712-eip1271-signatures.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/public/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png b/docs/public/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png index bfa8a72f111..dbf8bd8a7f4 100644 --- a/docs/public/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png +++ b/docs/public/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13b16c6f0eaf45dd89b2c5957c3cd4c84f7217483ebc9686cb32503a1636f186 -size 98694 +oid sha256:8da56bb4713a3dfa2f36f5b8ce18ba60fdbdb810255bb8d3b7d64524520567a9 +size 101711 From a46cc7171ed2dd53e59596961f21b3a12030a79b Mon Sep 17 00:00:00 2001 From: moskalyk Date: Fri, 13 Sep 2024 16:36:21 -0400 Subject: [PATCH 4/6] added demo code --- docs/pages/guides/typed-on-chain-signatures.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/pages/guides/typed-on-chain-signatures.mdx b/docs/pages/guides/typed-on-chain-signatures.mdx index 5cdbc9d9543..1d021e91045 100644 --- a/docs/pages/guides/typed-on-chain-signatures.mdx +++ b/docs/pages/guides/typed-on-chain-signatures.mdx @@ -24,6 +24,10 @@ This can be accomplished with 6 steps: 5. [Deploy Contract for EIP1271 Validation](/guides/typed-on-chain-signatures#5-deploy-contract-for-eip1271-validation) 6. [Render Response from Verifying & Validating Contract](/guides/typed-on-chain-signatures#6-render-response-from-verifying--validating-contractt) + +For full code for the demo, see [here](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo) + + ## 1. Create a Builder Project & Obtain an Access Key First, follow [this guide](/solutions/builder/getting-started) to create a project in the Sequence Builder and obtain a project access key. From 9c165e8db19ad1d7424136033410e7a6eb58975a Mon Sep 17 00:00:00 2001 From: moskalyk Date: Mon, 16 Sep 2024 08:58:17 -0400 Subject: [PATCH 5/6] updated guide with comments and edits --- .../guides/typed-on-chain-signatures.mdx | 105 +++++++++++------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/docs/pages/guides/typed-on-chain-signatures.mdx b/docs/pages/guides/typed-on-chain-signatures.mdx index 1d021e91045..01b2b8865bd 100644 --- a/docs/pages/guides/typed-on-chain-signatures.mdx +++ b/docs/pages/guides/typed-on-chain-signatures.mdx @@ -7,13 +7,9 @@ import {Callout} from 'vocs/components' # Verified Typed Data & Validated Signature Messages On-chain -Time to complete: XX minutes +Time to complete: 10-20 minutes -In this guide we'll walk you through how to use provided source code for an EVM based contract representing a Universal Signature Validator contract, explain the inner workings of custom on-chain message verifier contract that composes the Universal Validator, and how to perform in-app signature verification with typed data providing application capabilities that ensure message integrity across the ecosystem. - -The general flow for this application can be seen in the following sequence diagram: - -![sequence flow for eip712 eip1271 verfying and validating signatures](/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png) +In this guide we'll walk you through how to use provided source code for an EVM based contract, explain the inner workings of a custom on-chain message verifier contract that composes the Universal Signature Validator ([ERC-6492](https://ercs.ethereum.org/ERCS/erc-6492)), and how to perform in-app signature verification with typed data. This can be accomplished with 6 steps: @@ -22,10 +18,14 @@ This can be accomplished with 6 steps: 3. [Use Sequence Wallet for User Sign in](/guides/typed-on-chain-signatures#3-use-sequence-wallet-for-user-sign-in) 4. [Use EIP712 Typed Data to Generate EIP6492 Signatures](/guides/typed-on-chain-signatures#4-use-eip712-typed-data-to-generate-eip6492-signatures) 5. [Deploy Contract for EIP1271 Validation](/guides/typed-on-chain-signatures#5-deploy-contract-for-eip1271-validation) -6. [Render Response from Verifying & Validating Contract](/guides/typed-on-chain-signatures#6-render-response-from-verifying--validating-contractt) +6. [Render Response from Verifying & Validating Contract](/guides/typed-on-chain-signatures#6-render-response-from-verifying--validating-contract) + +The general flow for this application can be seen in the following sequence diagram: - -For full code for the demo, see [here](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo) +![sequence flow for eip712 eip1271 verfying and validating signatures](/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png) + + +See the [full code for the demo](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo) for more information and an example [demo can be found here](https://eip712-eip1271-signatures-demo.pages.dev/). ## 1. Create a Builder Project & Obtain an Access Key @@ -47,7 +47,7 @@ This should create a blank project that you can start adding elements and logic Install the necessary packages required for the project to function: ```shell -pnpm i 0xsequence ethers +pnpm install 0xsequence ethers ``` Then enable a user to sign in with on your chosen network and the obtained project access key from step 1. @@ -56,25 +56,27 @@ Then enable a user to sign in with on your chosen network and the obtained proje import { sequence } from '0xsequence' -sequence.initWallet(PROJECT_ACCESS_KEY, { - defaultNetwork: 'sepolia', -}); +function App() { + sequence.initWallet(PROJECT_ACCESS_KEY, { + defaultNetwork: 'sepolia', + }); -const signIn = async () => { - const wallet = sequence.getWallet() - const details = await wallet.connect({app: 'sequence signature validation demo'}) + const signIn = async () => { + const wallet = sequence.getWallet() + const details = await wallet.connect({app: 'sequence signature validation demo'}) - if(details){ - console.log('is signed in') - console.log(details) + if(details){ + console.log('is signed in') + console.log(details) + } } -} -return ( - ... - - ... -) + return ( + ... + + ... + ) +} ``` ## 4. Use EIP712 Typed Data to Generate EIP6492 Signatures @@ -92,9 +94,20 @@ interface Person { message: string; } -const VERIFYING_CONTRACT_ADDRESS = '0x339e65cf64c58160e2f2681016a1c2841d7ef2e7' +const VERIFYING_CONTRACT_ADDRESS = '0xB81efF8d6700b83B24AA69ABB18Ca8f9F7A356c5' +const CHAIN_ID = 11155111 + +const submitSignature = () => { + const wallet = sequence.getWallet() + + const message = 'hey' + const person: Person = { + name: "user", + wallet: wallet.getAddress(), + message: message, + }; -const verifySignature = () => { + const chainId = CHAIN_ID const typedData: sequence.utils.TypedData = { domain: { // Domain settings must match verifying contract @@ -122,7 +135,7 @@ Then we will sign the type message object with the various referenced properties ```typescript const wallet = await sequence.getWallet() -const signer = wallet.getSigner(DEFAULT_NETWORK_CHAIN_ID); +const signer = wallet.getSigner(CHAIN_ID); const signature = await signer.signTypedData( typedData.domain, @@ -153,15 +166,25 @@ We will now provide source code that you can use in something like [Remix](https The Universal Signature Validator can hypothetically be deployed once, and shared with many applications, making it composable and reusable. It's use is for both off-chain and on-chain smart contract wallets for [EIP6492](https://eips.ethereum.org/EIPS/eip-6492) enabled wallets. -You can find the source code [here](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo/blob/master/contracts/src/UniversalSigValidator.sol) that you can use to deploy. +You can find [the source code here](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo/blob/master/contracts/src/UniversalSigValidator.sol) that you can use to deploy. -If you would like to learn more about this contract, feel free to reach out the [Sequence Support](/support). +### Custom Contract Verifier -### Custom Contract Verifer - -The next contract we'll explain more in-depth with the various functions, as this contract can be customized. Begin with the following basics with the Universal Validator passed in the constructor in the first step: +The next contract we'll explain more in-depth with the various functions, as this contract can be customized. Begin with the following basics with the Universal Signature Validator passed in the constructor in the first step: ```javascript +import {IERC1271} from "./interfaces/IERC1271.sol"; +import {IERC6492} from "./interfaces/IERC6492.sol"; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; + +struct Person { + string name; + address wallet; + string message; +} + contract EIP712Verifier is EIP712 { using ECDSA for bytes32; @@ -190,14 +213,16 @@ function verifySignature(address signer, Person memory person, bytes calldata si returns (bool success) { bytes32 digest = personDigest(person); - return validateSigner(signer, digest, signature); + return ERC6492_SIG_VALIDATOR.isValidSig(signer, digest, signature); } ``` -### Custom Person Digest +#### Custom Person Digest In the following function we recreate the struct hash with the passed in parameters, which can be extended to include more or less parameters: +For more information on constructing the digest see the [EIP712 specification](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). + ```javascript /// @dev Returns the EIP712 hash of a person. function personDigest(Person memory person) public view returns (bytes32 digest) { @@ -208,9 +233,9 @@ function personDigest(Person memory person) public view returns (bytes32 digest) } ``` -### Validate Signer +#### Validate Signer -Next, we validate the `signer` address, the `digest` and `signature` and see if the signature created is counterfactual, basically meaning if it's not yet put on-chain. If it's not, we use the universal validator with [EIP6492](https://eips.ethereum.org/EIPS/eip-6492), if it is on-chain, we use the [EIP1271](https://eips.ethereum.org/EIPS/eip-1271) signature validation function: +Next, we validate the `signer` address, the `digest` and `signature`. If an EIP6492 signature has been supplied we use the [universal signature validator](https://eips.ethereum.org/EIPS/eip-6492), otherwise we check the [EIP1271](https://eips.ethereum.org/EIPS/eip-1271) signature validation directly: ```javascript /// @dev Validates the ERC1271 signature of a signer. @@ -246,12 +271,12 @@ Create a provider using the project access key: import { ethers } from 'ethers' const provider = new ethers.JsonRpcProvider( - "https://nodes.sequence.app/sepolia/AQAAAAAAAJbeftY2hQWuQG48gxVfoHYXKcw" + `https://nodes.sequence.app/sepolia/${PROJECT_ACCESS_KEY}` ); ``` ### Initialize an Ethers Contract -Import the ABI that was generated from step 5, include the provider, and input the verifying contract address: +Import the ABI that was generated from step 5 (or copy [from the git source code](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo/blob/master/src/abi.ts)), include the provider, and input the verifying contract address: ```typescript import { ABI } from "./abi"; @@ -265,7 +290,7 @@ const contract = new ethers.Contract( ### Static Call the Verify Signature Function -By performing a static call on the function, we simulate if the wallet has been deployed or not, and this returns a result specifying if the validation was true or false: +By performing a static call on the function, we simulate the transaction without submitting it on chain. This returns a result specifying if the validation was true or false: ```typescript const address = await wallet.getAddress() From 3baf48b4058db7c6af4d483087d03af72777a836 Mon Sep 17 00:00:00 2001 From: moskalyk Date: Tue, 17 Sep 2024 09:26:51 -0400 Subject: [PATCH 6/6] updates --- .../guides/typed-on-chain-signatures.mdx | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/docs/pages/guides/typed-on-chain-signatures.mdx b/docs/pages/guides/typed-on-chain-signatures.mdx index 01b2b8865bd..524faec8595 100644 --- a/docs/pages/guides/typed-on-chain-signatures.mdx +++ b/docs/pages/guides/typed-on-chain-signatures.mdx @@ -9,7 +9,7 @@ import {Callout} from 'vocs/components' Time to complete: 10-20 minutes -In this guide we'll walk you through how to use provided source code for an EVM based contract, explain the inner workings of a custom on-chain message verifier contract that composes the Universal Signature Validator ([ERC-6492](https://ercs.ethereum.org/ERCS/erc-6492)), and how to perform in-app signature verification with typed data. +In this guide we'll walk you through how to use provided source code for an EVM based contract and explain the inner workings of a custom on-chain message verifier contract that composes the Universal Signature Validator ([ERC-6492](https://ercs.ethereum.org/ERCS/erc-6492)), and how to perform in-app signature verification with typed data. This can be accomplished with 6 steps: @@ -17,7 +17,7 @@ This can be accomplished with 6 steps: 2. [Initialize React Vite Application](/guides/typed-on-chain-signatures#2-initialize-react-vite-application) 3. [Use Sequence Wallet for User Sign in](/guides/typed-on-chain-signatures#3-use-sequence-wallet-for-user-sign-in) 4. [Use EIP712 Typed Data to Generate EIP6492 Signatures](/guides/typed-on-chain-signatures#4-use-eip712-typed-data-to-generate-eip6492-signatures) -5. [Deploy Contract for EIP1271 Validation](/guides/typed-on-chain-signatures#5-deploy-contract-for-eip1271-validation) +5. [Deploy Contract for EIP712 Verification and EIP1271 Validation](/guides/typed-on-chain-signatures#5-deploy-contract-for-eip712-verification-and-eip1271-validation) 6. [Render Response from Verifying & Validating Contract](/guides/typed-on-chain-signatures#6-render-response-from-verifying--validating-contract) The general flow for this application can be seen in the following sequence diagram: @@ -25,7 +25,7 @@ The general flow for this application can be seen in the following sequence diag ![sequence flow for eip712 eip1271 verfying and validating signatures](/img/guides/eip712-eip1271-signatures/eip712-eip1271-signatures.png) -See the [full code for the demo](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo) for more information and an example [demo can be found here](https://eip712-eip1271-signatures-demo.pages.dev/). +See the [full code for the demo](https://github.com/0xsequence-demos/demo-EIP712-EIP1271-signatures) for more information and an example [demo can be found here](https://demo-eip712-eip1271-signatures.pages.dev/). ## 1. Create a Builder Project & Obtain an Access Key @@ -50,7 +50,7 @@ Install the necessary packages required for the project to function: pnpm install 0xsequence ethers ``` -Then enable a user to sign in with on your chosen network and the obtained project access key from step 1. +Then enable a user to sign in with on your chosen network and the obtained project access key from [step 1](/guides/typed-on-chain-signatures#1-create-a-builder-project--obtain-an-access-key). ```typescript @@ -83,7 +83,7 @@ function App() { Next, we'll define a custom typed data in typescript and using the utilities library from Sequence constructing a `TypedData` type, where we will be verifying a message structure with `name`, `wallet`, and `message` parameters: -In this example `VERIFYING_CONTRACT_ADDRESS` is the smart contract we deployed on `sepolia` but we will show you in the next step what this contract does: +In this example `VERIFYING_CONTRACT_ADDRESS` is the smart contract we deployed on `sepolia` but we will show you in the next step what this contract does so you can deploy yourself any any network: ```typescript import { sequence } from '0xsequence' @@ -100,9 +100,9 @@ const CHAIN_ID = 11155111 const submitSignature = () => { const wallet = sequence.getWallet() - const message = 'hey' + const message = 'hey' // message can be dynamic const person: Person = { - name: "user", + name: "user", // name can be dynamic wallet: wallet.getAddress(), message: message, }; @@ -156,21 +156,21 @@ Great, attach the function to a button and see the signature be generated after ``` -## 5. Deploy Contract for EIP1271 Validation +## 5. Deploy Contract for EIP712 Verification and EIP1271 Validation -We will now provide source code that you can use in something like [Remix](https://remix.ethereum.org/) to deploy a contract, or even something like [Foundry](https://book.getfoundry.sh/reference/forge/forge) for deploying with the [Sequence Builder](https://sequence.build) +We will now provide source code that you can use in something like [Remix](https://remix.ethereum.org/) to deploy a contract, or even something like [Foundry](https://book.getfoundry.sh/reference/forge/forge) for building and deploying with the [Sequence Builder](https://sequence.build) ::::steps ### Universal Signature Validator -The Universal Signature Validator can hypothetically be deployed once, and shared with many applications, making it composable and reusable. It's use is for both off-chain and on-chain smart contract wallets for [EIP6492](https://eips.ethereum.org/EIPS/eip-6492) enabled wallets. +The Universal Signature Validator can hypothetically be deployed once for a specific network, and shared with many applications, making it composable and reusable. It's use is for both off-chain and on-chain smart contract wallets for [EIP6492](https://eips.ethereum.org/EIPS/eip-6492) enabled wallets. -You can find [the source code here](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo/blob/master/contracts/src/UniversalSigValidator.sol) that you can use to deploy. +You can find [the source code here](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo/blob/master/contracts/src/UniversalSignatureValidator.sol) that you can use to deploy. ### Custom Contract Verifier -The next contract we'll explain more in-depth with the various functions, as this contract can be customized. Begin with the following basics with the Universal Signature Validator passed in the constructor in the first step: +The next contract we'll explain more in-depth with the various functions, as this contract can be customized for the specific application. Begin with the following basics with the Universal Signature Validator passed in the constructor in the first step: ```javascript import {IERC1271} from "./interfaces/IERC1271.sol"; @@ -179,7 +179,7 @@ import {IERC6492} from "./interfaces/IERC6492.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -struct Person { +struct Person { // can be customized string name; address wallet; string message; @@ -188,15 +188,15 @@ struct Person { contract EIP712Verifier is EIP712 { using ECDSA for bytes32; - IERC6492 public immutable ERC6492_SIG_VALIDATOR; // the universal signature validator + IERC6492 public immutable ERC6492_SIGNATURE_VALIDATOR; // the universal signature validator bytes32 private constant _ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492; // this line of code must be customized to the struct you're verifying bytes32 private constant _PERSON_TYPEHASH = keccak256(bytes("Person(string name,address wallet,string message)")); - constructor(address erc6492SigValidator) { - ERC6492_SIG_VALIDATOR = IERC6492(erc6492SigValidator); + constructor(address erc6492SignatureValidator) { + ERC6492_SIGNATURE_VALIDATOR = IERC6492(erc6492SignatureValidator); } ... } @@ -213,15 +213,15 @@ function verifySignature(address signer, Person memory person, bytes calldata si returns (bool success) { bytes32 digest = personDigest(person); - return ERC6492_SIG_VALIDATOR.isValidSig(signer, digest, signature); + return ERC6492_SIGNATURE_VALIDATOR.isValidSig(signer, digest, signature); } ``` #### Custom Person Digest -In the following function we recreate the struct hash with the passed in parameters, which can be extended to include more or less parameters: +In the following function we recreate the struct hash with the passed in parameters, which can be extended to include more or less parameters with varying types: -For more information on constructing the digest see the [EIP712 specification](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). +For more information on constructing the digest, see the [EIP712 specification](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). ```javascript /// @dev Returns the EIP712 hash of a person. @@ -244,7 +244,7 @@ function validateSigner(address signer, bytes32 digest, bytes calldata signature bool isCounterfactual = bytes32(signature[signature.length - 32:signature.length]) == _ERC6492_DETECTION_SUFFIX; if (isCounterfactual) { - return ERC6492_SIG_VALIDATOR.isValidSig(signer, digest, signature); + return ERC6492_SIGNATURE_VALIDATOR.isValidSig(signer, digest, signature); } } @@ -270,13 +270,15 @@ Create a provider using the project access key: ```typescript import { ethers } from 'ethers' +const CHAIN_HANDLE = 'sepolia' + const provider = new ethers.JsonRpcProvider( - `https://nodes.sequence.app/sepolia/${PROJECT_ACCESS_KEY}` + `https://nodes.sequence.app/${CHAIN_HANDLE}/${PROJECT_ACCESS_KEY}` ); ``` ### Initialize an Ethers Contract -Import the ABI that was generated from step 5 (or copy [from the git source code](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo/blob/master/src/abi.ts)), include the provider, and input the verifying contract address: +Import the ABI that was generated from [step 5](/guides/typed-on-chain-signatures#5-deploy-contract-for-eip712-verification-and-eip1271-validation) (or copy [from the git source code](https://github.com/0xsequence-demos/EIP712-EIP1271-signatures-demo/blob/master/src/abi.ts)), include the provider, and input the verifying contract address: ```typescript import { ABI } from "./abi"; @@ -324,4 +326,4 @@ return result; ## Conclusion -Now that we have message structs being passed to a blockchain and messages verified with their inputs, we can extend the application to a multitude of use cases that ensure users are signing the right information (e.g. permitting to spend ERC20's, perform off-chain bidding, etc.). \ No newline at end of file +Now that we have message structs being passed to a blockchain and messages verified with their inputs, we can extend the application to a multitude of use cases that ensure users are signing the right information (e.g. permitting to spend ERC20's, perform off-chain bidding, QR code containing signature approved minting, etc.). \ No newline at end of file