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:
parent
1717ea6890
commit
c5dfc1e9af
249
src/App.js
249
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() {
|
||||
<div className="App">
|
||||
<div style={{ height: 50 }} />
|
||||
<h1>{status}</h1>
|
||||
<div style={{ height: 100 }} />
|
||||
<div style={{ height: 200 }} />
|
||||
<h3>
|
||||
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.
|
||||
</h3>
|
||||
<GoogleLogin
|
||||
onSuccess={handleLoggedInToGoogle}
|
||||
@ -193,9 +314,9 @@ function App() {
|
||||
<div style={{ height: 100 }} />
|
||||
{pkpEthAddress && <div>PKP Eth Address: {pkpEthAddress}</div>}
|
||||
<div style={{ height: 100 }} />
|
||||
<h3>Step 2: Use Lit</h3>
|
||||
<button onClick={handleEncryptThenDecrypt}>
|
||||
Encrypt then Decrypt with Lit
|
||||
<h3>Step 2: Use Lit Network to obtain a session sig before storing a condition.</h3>
|
||||
<button onClick={handleStoreEncryptionCondition}>
|
||||
Encrypt with Lit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
@ -8,7 +8,7 @@ import { GoogleOAuthProvider } from "@react-oauth/google";
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<GoogleOAuthProvider clientId="490433686717-ojog337bhespbnsl29cr5h402e7pgkho.apps.googleusercontent.com">
|
||||
<GoogleOAuthProvider clientId="1071348522014-3qq1ln33ful535dnd8r4f6f9vtjrv2nu.apps.googleusercontent.com">
|
||||
<App />
|
||||
</GoogleOAuthProvider>
|
||||
</React.StrictMode>
|
||||
|
Loading…
Reference in New Issue
Block a user