Fix session sigs (#7)

* Decouple Google OAuth sign in with Mint PKP step

* Use new signSessionKey SDK method

* Handle authentication separately from storing conditions

* Use auth-helpers package to generate session sigs correctly

* Use react-app-rewired to use config override for fixing webpack bundle

create-react-app default webpack config does not match against .cjs files, so we need to override the default config to bundle .cjs files from auth-helpers using CJS to import the .cjs file
from recap-ts.

* Implement encrypt / decrypt using session sigs

* Use latest js-sdk, use npm instead of yarn

yarn still installs tslib at 1.14.1, which breaks, whereas npm installs at 2.5.0
This commit is contained in:
Howard 2023-05-17 17:14:36 -07:00 committed by GitHub
parent e2d7fe5ecc
commit 81a70959af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 20332 additions and 16454 deletions

15
config-overrides.js Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
webpack: function(config, env) {
config.module.rules = config.module.rules.map(rule => {
if (rule.oneOf instanceof Array) {
rule.oneOf[rule.oneOf.length - 1].exclude = [
/\.(js|mjs|jsx|cjs|ts|tsx)$/,
/\.html$/,
/\.json$/,
];
}
return rule;
});
return config;
},
};

24153
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,14 @@
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@lit-protocol/access-control-conditions": "^2.1.122",
"@lit-protocol/bls-sdk": "^2.1.122",
"@lit-protocol/constants": "^2.1.122",
"@lit-protocol/crypto": "^2.1.122",
"@lit-protocol/lit-node-client": "^2.1.122",
"@lit-protocol/lit-node-client-nodejs": "^2.1.122",
"@lit-protocol/access-control-conditions": "^2.2.0",
"@lit-protocol/auth-helpers": "^2.2.0",
"@lit-protocol/bls-sdk": "^2.2.0",
"@lit-protocol/constants": "^2.2.0",
"@lit-protocol/crypto": "^2.2.0",
"@lit-protocol/lit-node-client": "^2.2.0",
"@lit-protocol/lit-node-client-nodejs": "^2.2.0",
"@lit-protocol/types": "^2.2.0",
"@mui/material": "^5.10.16",
"@react-oauth/google": "^0.4.0",
"@simplewebauthn/browser": "^6.2.2",
@ -31,15 +33,16 @@
},
"devDependencies": {
"prettier": "^1.18.2",
"react-app-rewired": "^2.2.1",
"tslint": "^5.19.0",
"tslint-config-prettier": "^1.18.0",
"typescript": "3.6.2"
},
"scripts": {
"start": "react-scripts start",
"build": "yarn fetchContracts && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"start": "react-app-rewired start",
"build": "yarn fetchContracts && react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject",
"fetchContracts": "node ./tools/getDeployedContracts.mjs"
},
"eslintConfig": {

View File

@ -1,7 +1,9 @@
import * as LitJsSdk_accessControlConditions from "@lit-protocol/access-control-conditions";
import * as LitJsSdk_blsSdk from "@lit-protocol/bls-sdk";
import * as LitJsSdk_authHelpers from "@lit-protocol/auth-helpers";
import * as LitJsSdk_types from "@lit-protocol/types";
import * as LitJsSdk from "@lit-protocol/lit-node-client";
import { AccsDefaultParams, JsonAuthSig } from "@lit-protocol/types";
import { AccsDefaultParams, AuthSig, AuthCallback } from "@lit-protocol/types";
import { Button, ButtonGroup, TextField } from "@mui/material";
import { GoogleLogin } from "@react-oauth/google";
import {
@ -10,6 +12,7 @@ import {
} from "@simplewebauthn/browser";
import base64url from "base64url";
import { ethers, utils } from "ethers";
import { computeAddress } from "ethers/lib/utils";
import { useState } from "react";
import "./App.css";
import { getDomainFromOrigin } from "./utils/string";
@ -36,16 +39,29 @@ function App() {
const [registeredPkpPublicKey, setRegisteredPkpPublicKey] = useState<
string
>("");
const [
authenticatedPkpEthAddress,
setAuthenticatedPkpEthAddress,
] = useState<string>("");
const [authenticatedPkpPublicKey, setAuthenticatedPkpPublicKey] = useState<
string
>("");
const [status, setStatus] = useState("");
const [selectedAuthMethod, setSelectedAuthMethod] = useState(6);
const [webAuthnUsername, setWebAuthnUsername] = useState<string>("");
const [authSig, setAuthSig] = useState<JsonAuthSig | null>(null);
const [authSig, setAuthSig] = useState<AuthSig | null>(null);
const [executeJsSignature, setExecuteJsSignature] = useState<string | null>(
null
);
const [encryptedSymmetricKey, setEncryptedSymmetricKey] = useState<
Uint8Array
>(new Uint8Array());
const [encryptedString, setEncryptedString] = useState<Blob | null>(null);
console.log("STATE", {
authenticatedPkpPublicKey,
authenticatedPkpEthAddress,
});
const handleLoggedInToGoogle = async (
credentialResponse: CredentialResponse
@ -55,18 +71,6 @@ function App() {
credentialResponse,
});
setGoogleCredentialResponse(credentialResponse);
const requestId = await mintPkpUsingRelayerGoogleAuthVerificationEndpoint(
credentialResponse,
setStatus
);
await pollRequestUntilTerminalState(
requestId,
setStatus,
({ pkpEthAddress, pkpPublicKey }) => {
setRegisteredPkpEthAddress(pkpEthAddress);
setRegisteredPkpPublicKey(pkpPublicKey);
}
);
};
return (
@ -98,10 +102,7 @@ function App() {
<div style={{ height: 24 }} />
{selectedAuthMethod === 6 && (
<>
<h3>
Step 1: Log in with Google. Upon OAuth success, we will
mint a PKP on your behalf.
</h3>
<h3>Step 1: Log in with Google.</h3>
<GoogleLogin
onSuccess={handleLoggedInToGoogle}
onError={() => {
@ -109,6 +110,27 @@ function App() {
}}
useOneTap
/>
{googleCredentialResponse && (
<div>
<b>Google Credential Response: </b>
{JSON.stringify(googleCredentialResponse)}
</div>
)}
<h3>Step 2: Use Google Credential to Mint PKP.</h3>
<button
onClick={() =>
handleMintPkpUsingGoogleAuth(
googleCredentialResponse,
setStatus,
({ pkpEthAddress, pkpPublicKey }) => {
setRegisteredPkpEthAddress(pkpEthAddress);
setRegisteredPkpPublicKey(pkpPublicKey);
}
)
}
>
Mint PKP
</button>
{registeredPkpEthAddress && (
<div>
Registered PKP Eth Address:{" "}
@ -116,30 +138,56 @@ function App() {
</div>
)}
<h3>
<s>
Step 2: Use Lit Network to obtain a session sig and
then store an encryption condition.
</s>
(Session Sigs do not work currently.)
Step 3: Generate auth sigs from Lit Nodes, then generate
session sigs for storing an encryption condition.
</h3>
<button
onClick={() =>
handleStoreEncryptionCondition(
<Button
variant="contained"
onClick={async () => {
const {
encryptedString,
encryptedSymmetricKey,
authenticatedPkpPublicKey,
} = await handleStoreEncryptionConditionNodes(
setStatus,
selectedAuthMethod,
googleCredentialResponse,
{
signature: "dummy",
signatureBase: "dummy",
credentialPublicKey: "dummy",
},
registeredPkpEthAddress,
registeredPkpPublicKey
)
}
googleCredentialResponse
);
setEncryptedString(encryptedString);
setEncryptedSymmetricKey(encryptedSymmetricKey);
setAuthenticatedPkpPublicKey(
authenticatedPkpPublicKey
);
setAuthenticatedPkpEthAddress(
publicKeyToAddress(authenticatedPkpPublicKey)
);
}}
>
Encrypt with Lit
</button>
Authenticate + Encrypt with Lit
</Button>
{authenticatedPkpEthAddress && (
<div>
Authenticated PKP Eth Address:{" "}
{authenticatedPkpEthAddress}
</div>
)}
<h3>
Step 4: Retrieve the decrypted symmetric key from Lit
Nodes.
</h3>
<Button
variant="contained"
onClick={async () => {
await handleRetrieveSymmetricKeyNodes(
setStatus,
encryptedSymmetricKey,
encryptedString!,
googleCredentialResponse,
authenticatedPkpEthAddress
);
}}
>
Decrypt
</Button>
</>
)}
{selectedAuthMethod === 3 && (
@ -187,6 +235,9 @@ function App() {
// After authenticating, we can store the pkpPublicKey for executing a
// Lit Action later.
setAuthenticatedPkpPublicKey(pkpPublicKey);
setAuthenticatedPkpEthAddress(
computeAddress(`0x${pkpPublicKey}`)
);
}}
>
Authenticate
@ -234,9 +285,37 @@ function App() {
export default App;
const handleMintPkpUsingGoogleAuth = async (
credentialResponse: CredentialResponse,
setStatusFn: (status: string) => void,
onSuccess: ({
pkpEthAddress,
pkpPublicKey,
}: {
pkpEthAddress: string;
pkpPublicKey: string;
}) => void
) => {
setStatusFn("Minting PKP...");
const requestId = await mintPkpUsingRelayerGoogleAuthVerificationEndpoint(
credentialResponse,
setStatusFn
);
return pollRequestUntilTerminalState(requestId, setStatusFn, onSuccess);
};
async function getLitNodeClient(): Promise<LitJsSdk.LitNodeClient> {
const litNodeClient = new LitJsSdk.LitNodeClient({
litNetwork: "serrano",
});
await litNodeClient.connect();
return litNodeClient;
}
async function handleExecuteJs(
setStatusFn: (status: string) => void,
authSig: JsonAuthSig,
authSig: AuthSig,
pkpPublicKey: string
): Promise<string> {
setStatusFn("Executing JS...");
@ -251,10 +330,7 @@ const go = async () => {
go();
`;
const litNodeClient = new LitJsSdk.LitNodeClient({
litNetwork: "serrano",
});
await litNodeClient.connect();
const litNodeClient = await getLitNodeClient();
const results = await litNodeClient.executeJs({
code: litActionCode,
@ -366,90 +442,158 @@ async function pollRequestUntilTerminalState(
setStatusFn(`Hmm this is taking longer than expected...`);
}
async function handleStoreEncryptionCondition(
async function handleStoreEncryptionConditionNodes(
setStatusFn: (status: string) => void,
selectedAuthMethod: number,
googleCredentialResponse: any,
webAuthnVerificationMaterial: {
signature: string;
signatureBase: string;
credentialPublicKey: string;
},
pkpEthAddress: string,
pkpPublicKey: string
) {
setStatusFn("Storing encryption condition...");
var unifiedAccessControlConditions: AccsDefaultParams[] = [
{
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,
}: any) => {
console.log("authNeededCallback fired");
const authMethods =
selectedAuthMethod === 6
? [
{
authMethodType: 6,
accessToken: googleCredentialResponse.credential,
},
]
: [
{
authMethodType: 3,
accessToken: JSON.stringify(
webAuthnVerificationMaterial
),
},
];
const sessionSig = await litNodeClient.signSessionKey({
sessionKey: uri,
authMethods,
pkpPublicKey,
expiration,
resources,
chain,
});
console.log("got session sig from node and PKP: ", sessionSig);
return sessionSig;
};
googleCredentialResponse: any
): Promise<{
encryptedSymmetricKey: Uint8Array;
encryptedString: Blob;
authenticatedPkpPublicKey: string;
}> {
setStatusFn("Storing encryption condition with the network...");
// get the user a session with it
const litNodeClient = new LitJsSdk.LitNodeClient({
litNetwork: "serrano",
});
await litNodeClient.connect();
const litNodeClient = await getLitNodeClient();
const { encryptedString, symmetricKey } = await LitJsSdk.encryptString(
"this is a secret message"
);
// key parameter - encrypt symmetric key then hash it
const encryptedSymmetricKey = LitJsSdk_blsSdk.wasmBlsSdkHelpers.encrypt(
LitJsSdk.uint8arrayFromString(litNodeClient.subnetPubKey, "base16"),
symmetricKey
);
// get the session sigs
const { sessionSigs, authenticatedPkpPublicKey } = await getSessionSigs(
litNodeClient,
encryptedSymmetricKey,
litNodeClient.generateAuthMethodForGoogleJWT(
googleCredentialResponse.credential
)
);
const pkpEthAddress = publicKeyToAddress(authenticatedPkpPublicKey);
const unifiedAccessControlConditions = getUnifiedAccessControlConditions(
pkpEthAddress
);
console.log(
"unifiedAccessControlConditions: ",
unifiedAccessControlConditions
);
// store the decryption conditions
await litNodeClient.saveEncryptionKey({
unifiedAccessControlConditions,
symmetricKey,
encryptedSymmetricKey,
sessionSigs, // Not actually needed for storing encryption condition.
chain: "ethereum",
});
console.log("encryptedSymmetricKey: ", encryptedSymmetricKey);
return {
encryptedSymmetricKey,
encryptedString,
authenticatedPkpPublicKey,
};
}
async function getSessionSigs(
litNodeClient: LitJsSdk.LitNodeClient,
encryptedSymmetricKey: Uint8Array,
authMethod: LitJsSdk_types.AuthMethod
): Promise<{
sessionSigs: LitJsSdk_types.SessionSigsMap;
authenticatedPkpPublicKey: string;
}> {
let authenticatedPkpPublicKey: string;
// this will be fired if auth is needed. we can use this to prompt the user to sign in
const authNeededCallback: AuthCallback = async ({
resources,
expiration,
statement,
}) => {
console.log("authNeededCallback fired");
// Generate authMethod.
const authMethods = [authMethod];
// Get AuthSig
const { authSig, pkpPublicKey } = await litNodeClient.signSessionKey({
authMethods,
statement,
expiration:
expiration ||
new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), // 24 hours
resources: resources || [],
});
console.log("got session sig from node and PKP: ", {
authSig,
pkpPublicKey,
});
authenticatedPkpPublicKey = pkpPublicKey;
return authSig;
};
const hashedEncryptedSymmetricKeyStr = await hashBytes({
bytes: new Uint8Array(encryptedSymmetricKey),
});
// Construct the LitResource
const litResource = new LitJsSdk_authHelpers.LitAccessControlConditionResource(
hashedEncryptedSymmetricKeyStr
);
// Get the session sigs
const sessionSigs = await litNodeClient.getSessionSigs({
expiration: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), // 24 hours
chain: "ethereum",
resources: [`litEncryptionCondition://*`],
resourceAbilityRequests: [
{
resource: litResource,
ability:
LitJsSdk_authHelpers.LitAbility
.AccessControlConditionDecryption,
},
],
switchChain: false,
authNeededCallback,
});
console.log("sessionSigs before saving encryption key: ", sessionSigs);
console.log("sessionSigs: ", sessionSigs);
console.log("authenticatedPkpPublicKey: ", authenticatedPkpPublicKey!);
return {
sessionSigs,
authenticatedPkpPublicKey: authenticatedPkpPublicKey!,
};
}
// TODO: use when system migrates to reading from chain for access control conditions
async function handleStoreEncryptionConditionRelay(
setStatusFn: (status: string) => void,
pkpEthAddress: string
) {
setStatusFn("Storing encryption condition...");
// get the user a session with it
const litNodeClient = await getLitNodeClient();
const { encryptedZip, symmetricKey } = await LitJsSdk.zipAndEncryptString(
"this is a secret message"
);
// get the ACCs
const unifiedAccessControlConditions = getUnifiedAccessControlConditions(
pkpEthAddress
);
// value parameter - hash unified conditions
const hashedAccessControlConditions = await LitJsSdk_accessControlConditions.hashUnifiedAccessControlConditions(
unifiedAccessControlConditions
@ -486,7 +630,6 @@ async function handleStoreEncryptionCondition(
hashedEncryptedSymmetricKeyStr,
hashedAccessControlConditionsStr,
securityHashStr,
sessionSig: sessionSigs["https://serrano.litgateway.com:7370"],
});
// call centralized conditions relayer to write encryption conditions to chain.
@ -503,8 +646,6 @@ async function handleStoreEncryptionCondition(
chainId: "1",
permanent: false,
capabilityProtocolPrefix: "litEncryptionCondition",
// just choose any one session signature that is generated.
sessionSig: sessionSigs["https://serrano.litgateway.com:7370"],
}),
});
@ -519,6 +660,51 @@ async function handleStoreEncryptionCondition(
}
}
async function handleRetrieveSymmetricKeyNodes(
setStatusFn: (status: string) => void,
encryptedSymmetricKey: Uint8Array,
encryptedString: Blob,
googleCredentialResponse: any,
pkpEthAddress: string
) {
setStatusFn("Retrieving symmetric key...");
const litNodeClient = await getLitNodeClient();
// get the session sigs
const { sessionSigs } = await getSessionSigs(
litNodeClient,
encryptedSymmetricKey,
litNodeClient.generateAuthMethodForGoogleJWT(
googleCredentialResponse.credential
)
);
// get the ACC
const unifiedAccessControlConditions = getUnifiedAccessControlConditions(
pkpEthAddress
);
console.log(
"unifiedAccessControlConditions: ",
unifiedAccessControlConditions
);
const retrievedSymmKey = await litNodeClient.getEncryptionKey({
unifiedAccessControlConditions,
toDecrypt: LitJsSdk.uint8arrayToString(encryptedSymmetricKey, "base16"),
sessionSigs,
});
const decryptedString = await LitJsSdk.decryptString(
encryptedString,
retrievedSymmKey
);
console.log("decrypted string", decryptedString);
}
function publicKeyToAddress(publicKey: string) {
return utils.computeAddress(`0x${publicKey}`);
}
async function hashBytes({ bytes }: { bytes: Uint8Array }): Promise<string> {
const hashOfBytes = await crypto.subtle.digest("SHA-256", bytes);
const hashOfBytesStr = LitJsSdk.uint8arrayToString(
@ -577,10 +763,7 @@ async function hashBytes({ bytes }: { bytes: Uint8Array }): Promise<string> {
// };
// // get the user a session with it
// const litNodeClient = new LitJsSdk.LitNodeClient({
// litNetwork: "serrano",
// });
// await litNodeClient.connect();
// const litNodeClient = await getLitNodeClient();
// const sessionSigs = await litNodeClient.getSessionSigs({
// expiration: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), // 24 hours
@ -704,7 +887,7 @@ const rpcUrl = process.env.REACT_APP_RPC_URL || "http://localhost:8545";
async function handleWebAuthnAuthenticate(
setStatusFn: (status: string) => void
): Promise<{
authSig: JsonAuthSig;
authSig: AuthSig;
pkpPublicKey: string;
}> {
// Fetch latest blockHash
@ -750,10 +933,7 @@ async function handleWebAuthnAuthenticate(
// Call all nodes POST /web/auth/webauthn to generate authSig.
setStatusFn("Verifying WebAuthn authentication against Lit Network...");
const litNodeClient = new LitJsSdk.LitNodeClient({
litNetwork: "serrano",
});
await litNodeClient.connect();
const litNodeClient = await getLitNodeClient();
// Generate authMethod.
const authMethod = litNodeClient.generateAuthMethodForWebAuthn(
@ -769,3 +949,24 @@ async function handleWebAuthnAuthenticate(
return { authSig, pkpPublicKey };
}
function getUnifiedAccessControlConditions(
pkpEthAddress?: string
): AccsDefaultParams[] {
return [
{
conditionType: "evmBasic",
contractAddress: "",
standardContractType: "",
chain: "mumbai",
method: "",
parameters: [":userAddress"],
returnValueTest: {
comparator: "=",
value:
pkpEthAddress ||
"0x3c3CA2BFFfffE532aed2923A34D6c1F9307F8076",
},
},
];
}

12144
yarn.lock

File diff suppressed because it is too large Load Diff