From c5dfc1e9af675d550386aa9a3b01f4000ba7e386 Mon Sep 17 00:00:00 2001 From: Howard Date: Mon, 28 Nov 2022 14:40:57 -0800 Subject: [PATCH] Use centralized relay for minting PKP and storing encryption condition (#1) * implement encryption with relay server * implement minting pkp with relay server * revert yarn.lock changes --- src/App.js | 249 ++++++++++++++++++++++++++++++++++++++------------- src/index.js | 2 +- 2 files changed, 186 insertions(+), 65 deletions(-) diff --git a/src/App.js b/src/App.js index 57d9e62..6f05d1d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,17 +1,14 @@ +import { GoogleLogin } from "@react-oauth/google"; +import { ethers, utils } from "ethers"; +import LitJsSdk from "lit-js-sdk"; import { useState } from "react"; import "./App.css"; -import { GoogleLogin } from "@react-oauth/google"; -import { ethers } from "ethers"; -import { Base64 } from "js-base64"; -import LitJsSdk from "lit-js-sdk"; - -import PKPHelper from "./abis/PKPHelper.json"; -import PKPNFT from "./abis/PKPNFT.json"; -import ContractAddresses from "./abis/deployed-contracts.json"; window.LitJsSdk = LitJsSdk; window.ethers = ethers; +const RELAY_API_URL = process.env.RELAY_API_URL || 'http://localhost:3001'; + function App() { const [pkpEthAddress, setPkpEthAddress] = useState(null); const [googleCredentialResponse, setGoogleCredentialResponse] = @@ -21,68 +18,193 @@ function App() { const handleLoggedInToGoogle = async (credentialResponse) => { setStatus("Logged in to Google"); - console.log("got this response from google sign in: ", credentialResponse); + console.log("Got response from google sign in: ", { credentialResponse }); setGoogleCredentialResponse(credentialResponse); - mintPkp(credentialResponse); + const requestId = await mintPkpWithRelayer(credentialResponse); + await pollRequestUntilTerminalState(requestId); }; - const mintPkp = async (credentialResponse) => { - setStatus("Minting PKP..."); - // mint a PKP for the user - // A Web3Provider wraps a standard Web3 provider, which is - // what MetaMask injects as window.ethereum into each page - const provider = new ethers.providers.Web3Provider(window.ethereum); + const mintPkpWithRelayer = async (credentialResponse) => { + setStatus("Minting PKP with relayer..."); - // MetaMask requires requesting permission to connect users accounts - await provider.send("eth_requestAccounts", []); + const mintRes = await fetch(`${RELAY_API_URL}/auth/google`, { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + idToken: credentialResponse.credential + }), + }); - // The MetaMask plugin also allows signing transactions to - // send ether and pay to change state within the blockchain. - // For this, you need the account signer... - const signer = provider.getSigner(); + if (mintRes.status < 200 || mintRes.status >= 400) { + console.warn("Something wrong with the API call", await mintRes.json()); + setStatus("Uh oh, something's not quite right."); + return null; + } else { + const resBody = await mintRes.json(); + console.log("Response OK", { body: resBody }); + setStatus("Successfully initiated minting PKP with relayer.") + return resBody.requestId; + } + } - const helperContract = new ethers.Contract( - ContractAddresses.pkpHelperContractAddress, - PKPHelper.abi, - signer - ); - const pkpContract = new ethers.Contract( - ContractAddresses.pkpNftContractAddress, - PKPNFT.abi, - signer + const pollRequestUntilTerminalState = async (requestId) => { + if (!requestId) { + return; + } + + const maxPollCount = 20; + for (let i = 0; i < maxPollCount; i++) { + setStatus(`Waiting for auth completion (poll #${i+1})`); + const getAuthStatusRes = await fetch(`${RELAY_API_URL}/auth/status/${requestId}`); + + if (getAuthStatusRes.status < 200 || getAuthStatusRes.status >= 400) { + console.warn("Something wrong with the API call", await getAuthStatusRes.json()); + setStatus("Uh oh, something's not quite right."); + return; + } + + const resBody = await getAuthStatusRes.json(); + console.log("Response OK", { body: resBody }); + + if (resBody.error) { + // exit loop since error + console.warn("Something wrong with the API call", { error: resBody.error }); + setStatus("Uh oh, something's not quite right."); + return; + } else if (resBody.status === "Succeeded") { + // exit loop since success + console.info("Successfully authed", { ...resBody }); + setStatus("Successfully authed and minted PKP!"); + setPkpEthAddress(resBody.pkpEthAddress); + setPkpPublicKey(resBody.pkpPublicKey); + return; + } + + // otherwise, sleep then continue polling + await new Promise(r => setTimeout(r, 15000)); + } + + // at this point, polling ended and still no success, set failure status + setStatus(`Hmm this is taking longer than expected...`) + } + + const handleStoreEncryptionCondition = async () => { + setStatus("Storing encryption condition..."); + var unifiedAccessControlConditions = [ + { + conditionType: "evmBasic", + contractAddress: "", + standardContractType: "", + chain: "mumbai", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: pkpEthAddress, + }, + }, + ]; + + // this will be fired if auth is needed. we can use this to prompt the user to sign in + const authNeededCallback = async ({ + chain, + resources, + expiration, + uri, + litNodeClient, + }) => { + console.log("authNeededCallback fired"); + const sessionSig = await litNodeClient.signSessionKey({ + sessionKey: uri, + authMethods: [ + { + authMethodType: 6, + accessToken: googleCredentialResponse.credential, + }, + ], + pkpPublicKey, + expiration, + resources, + chain, + }); + console.log("got session sig from node and PKP: ", sessionSig); + return sessionSig; + }; + + // get the user a session with it + const litNodeClient = new LitJsSdk.LitNodeClient({ + litNetwork: "serrano", + }); + await litNodeClient.connect(); + + const sessionSigs = await litNodeClient.getSessionSigs({ + expiration: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), // 24 hours + chain: "ethereum", + resources: [`litEncryptionCondition://*`], + sessionCapabilityObject: { + def: ["litEncryptionCondition"] + }, + switchChain: false, + authNeededCallback, + }); + console.log("sessionSigs before saving encryption key: ", sessionSigs); + + const { encryptedZip, symmetricKey } = await LitJsSdk.zipAndEncryptString( + "this is a secret message" ); - let jwtParts = credentialResponse.credential.split("."); - let jwtPayload = JSON.parse(Base64.decode(jwtParts[1])); + // value parameter - hash unified conditions + const hashedAccessControlConditions = await LitJsSdk.hashUnifiedAccessControlConditions(unifiedAccessControlConditions); + console.log("hashedAccessControlConditions", { hashedAccessControlConditions }); + const hashedAccessControlConditionsStr = LitJsSdk.uint8arrayToString(new Uint8Array(hashedAccessControlConditions), "base16"); - let idForAuthMethod = ethers.utils.keccak256( - ethers.utils.toUtf8Bytes(`${jwtPayload.sub}:${jwtPayload.aud}`) - ); + // key parameter - encrypt symmetric key then hash it + const encryptedSymmetricKey = LitJsSdk.encryptWithBlsPubkey({ + pubkey: litNodeClient.networkPubKey, + data: symmetricKey, + }); + const hashedEncryptedSymmetricKeyStr = await LitJsSdk.hashEncryptionKey({ encryptedSymmetricKey }); + + // securityHash parameter - encrypt symmetric key, concat with creator address + const pkpEthAddressBytes = utils.arrayify(pkpEthAddress); + const securityHashPreimage = new Uint8Array([...encryptedSymmetricKey, ...pkpEthAddressBytes]); + // TODO: LitJsSdk.hashEncryptionKey ought to be renamed to just .hashBytes + const securityHashStr = await LitJsSdk.hashEncryptionKey({ encryptedSymmetricKey: securityHashPreimage }); - const mintCost = await pkpContract.mintCost(); + console.log("Storing encryption condition with relay", { + hashedEncryptedSymmetricKeyStr, + hashedAccessControlConditionsStr, + securityHashStr, + sessionSig: sessionSigs["https://serrano.litgateway.com:7370"], + }); - const mintTx = await helperContract.mintNextAndAddAuthMethods( - 2, // keyType - [6], // permittedAuthMethodTypes, - [idForAuthMethod], // permittedAuthMethodIds - ["0x"], // permittedAuthMethodPubkeys - [[ethers.BigNumber.from("0")]], // permittedAuthMethodScopes - true, // addPkpEthAddressAsPermittedAddress - true, // sendPkpToItself - { value: mintCost } - ); - console.log("mintTx: ", mintTx); - const mintingReceipt = await mintTx.wait(); - console.log("mintingReceipt: ", mintingReceipt); - const tokenIdFromEvent = mintingReceipt.events[2].topics[3]; - const ethAddress = await pkpContract.getEthAddress(tokenIdFromEvent); - setPkpEthAddress(ethAddress); + // call centralized conditions relayer to write encryption conditions to chain. + const storeRes = await fetch(`${RELAY_API_URL}/store-condition`, { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + key: hashedEncryptedSymmetricKeyStr, + value: hashedAccessControlConditionsStr, + securityHash: securityHashStr, + chainId: "1", + permanent: false, + capabilityProtocolPrefix: "litEncryptionCondition", + // just choose any one session signature that is generated. + sessionSig: sessionSigs["https://serrano.litgateway.com:7370"], + }), + }); - console.log("minted PKP with eth address: ", ethAddress); - const pkpPublicKey = await pkpContract.getPubkey(tokenIdFromEvent); - setPkpPublicKey(pkpPublicKey); - setStatus("Minted PKP"); - }; + if (storeRes.status < 200 || storeRes.status >= 400) { + console.warn("Something wrong with the API call", await storeRes.json()); + setStatus("Uh oh, something's not quite right"); + } else { + setStatus("Successfully stored encryption condition with relayer!"); + } + } const handleEncryptThenDecrypt = async () => { setStatus("Encrypting then decrypting..."); @@ -178,10 +300,9 @@ function App() {

{status}

-
+

- Step 1: log in with Google. You will mint a PKP and obtain a session - sig. Note: Your metamask must be switched to Mumbai. + Step 1: log in with Google. Upon OAuth success, we will mint a PKP on your behalf.

{pkpEthAddress &&
PKP Eth Address: {pkpEthAddress}
}
-

Step 2: Use Lit

-
); diff --git a/src/index.js b/src/index.js index 96802b0..90e5fe3 100644 --- a/src/index.js +++ b/src/index.js @@ -8,7 +8,7 @@ import { GoogleOAuthProvider } from "@react-oauth/google"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - +