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
This commit is contained in:
Howard 2022-11-28 14:40:57 -08:00 committed by GitHub
parent 1717ea6890
commit c5dfc1e9af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 186 additions and 65 deletions

View File

@ -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 { useState } from "react";
import "./App.css"; 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.LitJsSdk = LitJsSdk;
window.ethers = ethers; window.ethers = ethers;
const RELAY_API_URL = process.env.RELAY_API_URL || 'http://localhost:3001';
function App() { function App() {
const [pkpEthAddress, setPkpEthAddress] = useState(null); const [pkpEthAddress, setPkpEthAddress] = useState(null);
const [googleCredentialResponse, setGoogleCredentialResponse] = const [googleCredentialResponse, setGoogleCredentialResponse] =
@ -21,69 +18,194 @@ function App() {
const handleLoggedInToGoogle = async (credentialResponse) => { const handleLoggedInToGoogle = async (credentialResponse) => {
setStatus("Logged in to Google"); 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); setGoogleCredentialResponse(credentialResponse);
mintPkp(credentialResponse); const requestId = await mintPkpWithRelayer(credentialResponse);
await pollRequestUntilTerminalState(requestId);
}; };
const mintPkp = async (credentialResponse) => { const mintPkpWithRelayer = async (credentialResponse) => {
setStatus("Minting PKP..."); setStatus("Minting PKP with relayer...");
// 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);
// MetaMask requires requesting permission to connect users accounts const mintRes = await fetch(`${RELAY_API_URL}/auth/google`, {
await provider.send("eth_requestAccounts", []); method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
idToken: credentialResponse.credential
}),
});
// The MetaMask plugin also allows signing transactions to if (mintRes.status < 200 || mintRes.status >= 400) {
// send ether and pay to change state within the blockchain. console.warn("Something wrong with the API call", await mintRes.json());
// For this, you need the account signer... setStatus("Uh oh, something's not quite right.");
const signer = provider.getSigner(); 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( const pollRequestUntilTerminalState = async (requestId) => {
ContractAddresses.pkpHelperContractAddress, if (!requestId) {
PKPHelper.abi, return;
signer }
);
const pkpContract = new ethers.Contract(
ContractAddresses.pkpNftContractAddress,
PKPNFT.abi,
signer
);
let jwtParts = credentialResponse.credential.split("."); const maxPollCount = 20;
let jwtPayload = JSON.parse(Base64.decode(jwtParts[1])); 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}`);
let idForAuthMethod = ethers.utils.keccak256( if (getAuthStatusRes.status < 200 || getAuthStatusRes.status >= 400) {
ethers.utils.toUtf8Bytes(`${jwtPayload.sub}:${jwtPayload.aud}`) console.warn("Something wrong with the API call", await getAuthStatusRes.json());
); setStatus("Uh oh, something's not quite right.");
return;
}
const mintCost = await pkpContract.mintCost(); const resBody = await getAuthStatusRes.json();
console.log("Response OK", { body: resBody });
const mintTx = await helperContract.mintNextAndAddAuthMethods( if (resBody.error) {
2, // keyType // exit loop since error
[6], // permittedAuthMethodTypes, console.warn("Something wrong with the API call", { error: resBody.error });
[idForAuthMethod], // permittedAuthMethodIds setStatus("Uh oh, something's not quite right.");
["0x"], // permittedAuthMethodPubkeys return;
[[ethers.BigNumber.from("0")]], // permittedAuthMethodScopes } else if (resBody.status === "Succeeded") {
true, // addPkpEthAddressAsPermittedAddress // exit loop since success
true, // sendPkpToItself console.info("Successfully authed", { ...resBody });
{ value: mintCost } setStatus("Successfully authed and minted PKP!");
); setPkpEthAddress(resBody.pkpEthAddress);
console.log("mintTx: ", mintTx); setPkpPublicKey(resBody.pkpPublicKey);
const mintingReceipt = await mintTx.wait(); return;
console.log("mintingReceipt: ", mintingReceipt); }
const tokenIdFromEvent = mintingReceipt.events[2].topics[3];
const ethAddress = await pkpContract.getEthAddress(tokenIdFromEvent);
setPkpEthAddress(ethAddress);
console.log("minted PKP with eth address: ", ethAddress); // otherwise, sleep then continue polling
const pkpPublicKey = await pkpContract.getPubkey(tokenIdFromEvent); await new Promise(r => setTimeout(r, 15000));
setPkpPublicKey(pkpPublicKey); }
setStatus("Minted PKP");
// 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"
);
// value parameter - hash unified conditions
const hashedAccessControlConditions = await LitJsSdk.hashUnifiedAccessControlConditions(unifiedAccessControlConditions);
console.log("hashedAccessControlConditions", { hashedAccessControlConditions });
const hashedAccessControlConditionsStr = LitJsSdk.uint8arrayToString(new Uint8Array(hashedAccessControlConditions), "base16");
// 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 });
console.log("Storing encryption condition with relay", {
hashedEncryptedSymmetricKeyStr,
hashedAccessControlConditionsStr,
securityHashStr,
sessionSig: sessionSigs["https://serrano.litgateway.com:7370"],
});
// 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"],
}),
});
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 () => { const handleEncryptThenDecrypt = async () => {
setStatus("Encrypting then decrypting..."); setStatus("Encrypting then decrypting...");
var unifiedAccessControlConditions = [ var unifiedAccessControlConditions = [
@ -178,10 +300,9 @@ function App() {
<div className="App"> <div className="App">
<div style={{ height: 50 }} /> <div style={{ height: 50 }} />
<h1>{status}</h1> <h1>{status}</h1>
<div style={{ height: 100 }} /> <div style={{ height: 200 }} />
<h3> <h3>
Step 1: log in with Google. You will mint a PKP and obtain a session Step 1: log in with Google. Upon OAuth success, we will mint a PKP on your behalf.
sig. Note: Your metamask must be switched to Mumbai.
</h3> </h3>
<GoogleLogin <GoogleLogin
onSuccess={handleLoggedInToGoogle} onSuccess={handleLoggedInToGoogle}
@ -193,9 +314,9 @@ function App() {
<div style={{ height: 100 }} /> <div style={{ height: 100 }} />
{pkpEthAddress && <div>PKP Eth Address: {pkpEthAddress}</div>} {pkpEthAddress && <div>PKP Eth Address: {pkpEthAddress}</div>}
<div style={{ height: 100 }} /> <div style={{ height: 100 }} />
<h3>Step 2: Use Lit</h3> <h3>Step 2: Use Lit Network to obtain a session sig before storing a condition.</h3>
<button onClick={handleEncryptThenDecrypt}> <button onClick={handleStoreEncryptionCondition}>
Encrypt then Decrypt with Lit Encrypt with Lit
</button> </button>
</div> </div>
); );

View File

@ -8,7 +8,7 @@ import { GoogleOAuthProvider } from "@react-oauth/google";
const root = ReactDOM.createRoot(document.getElementById("root")); const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<GoogleOAuthProvider clientId="490433686717-ojog337bhespbnsl29cr5h402e7pgkho.apps.googleusercontent.com"> <GoogleOAuthProvider clientId="1071348522014-3qq1ln33ful535dnd8r4f6f9vtjrv2nu.apps.googleusercontent.com">
<App /> <App />
</GoogleOAuthProvider> </GoogleOAuthProvider>
</React.StrictMode> </React.StrictMode>