mirror of
https://github.com/placeholder-soft/storytime.git
synced 2026-01-12 07:14:55 +08:00
feat: ZKLoginClient
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -66,4 +66,6 @@ node_modules/
|
||||
.env
|
||||
|
||||
.envrc.override
|
||||
build
|
||||
build
|
||||
|
||||
.gitconfig
|
||||
@@ -10,22 +10,26 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mysten/dapp-kit": "0.10.2",
|
||||
"@mysten/sui.js": "0.48.0",
|
||||
"@mysten/zklogin": "^0.3.9",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/themes": "^2.0.0",
|
||||
"@tanstack/react-query": "^5.0.0",
|
||||
"axios": "^1.6.2",
|
||||
"dnum-cjs": "^2.9.0",
|
||||
"fabric": "^5.3.0",
|
||||
"firebase": "^10.7.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"model": "../model",
|
||||
"query-string": "^8.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^6.20.1",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"styled-components": "^6.1.1",
|
||||
"styled-normalize": "^8.1.0",
|
||||
"@mysten/dapp-kit": "0.10.2",
|
||||
"@mysten/sui.js": "0.48.0",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/themes": "^2.0.0",
|
||||
"@tanstack/react-query": "^5.0.0"
|
||||
"styled-normalize": "^8.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fabric": "^5.3.6",
|
||||
@@ -41,4 +45,4 @@
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-pages": "^0.32.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
69
app/pnpm-lock.yaml
generated
69
app/pnpm-lock.yaml
generated
@@ -11,6 +11,9 @@ dependencies:
|
||||
'@mysten/sui.js':
|
||||
specifier: 0.48.0
|
||||
version: 0.48.0
|
||||
'@mysten/zklogin':
|
||||
specifier: ^0.3.9
|
||||
version: 0.3.9
|
||||
'@radix-ui/colors':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@@ -26,15 +29,24 @@ dependencies:
|
||||
axios:
|
||||
specifier: ^1.6.2
|
||||
version: 1.6.2
|
||||
dnum-cjs:
|
||||
specifier: ^2.9.0
|
||||
version: 2.9.0
|
||||
fabric:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
firebase:
|
||||
specifier: ^10.7.1
|
||||
version: 10.7.1
|
||||
jwt-decode:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
model:
|
||||
specifier: ../model
|
||||
version: link:../model
|
||||
query-string:
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
@@ -946,6 +958,16 @@ packages:
|
||||
'@wallet-standard/core': 1.0.3
|
||||
dev: false
|
||||
|
||||
/@mysten/zklogin@0.3.9:
|
||||
resolution: {integrity: sha512-mOdw0Gjbst0epu7TJJRkh5xb5nXKTTLAvpn3x9jKopvq2np/sIlLiZYjNtUR2aKHM+pIg8EKZLubh/vfKZuP6g==}
|
||||
dependencies:
|
||||
'@mysten/bcs': 0.9.0
|
||||
'@mysten/sui.js': 0.48.0
|
||||
'@noble/hashes': 1.3.2
|
||||
jose: 4.15.4
|
||||
poseidon-lite: 0.2.0
|
||||
dev: false
|
||||
|
||||
/@noble/curves@1.2.0:
|
||||
resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==}
|
||||
dependencies:
|
||||
@@ -2998,6 +3020,11 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/decode-uri-component@0.4.1:
|
||||
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==}
|
||||
engines: {node: '>=14.16'}
|
||||
dev: false
|
||||
|
||||
/decompress-response@4.2.1:
|
||||
resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3091,6 +3118,12 @@ packages:
|
||||
path-type: 4.0.0
|
||||
dev: true
|
||||
|
||||
/dnum-cjs@2.9.0:
|
||||
resolution: {integrity: sha512-truheGbCvjrsPGTKvig62+eECxgPWupjvAMyVKeiBVCDNY0klj+icBTOaZhYQX1tjuNjiZ9FI12ZMJAy8IsWnw==}
|
||||
dependencies:
|
||||
from-exponential: 1.1.1
|
||||
dev: false
|
||||
|
||||
/doctrine@3.0.0:
|
||||
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -3371,6 +3404,11 @@ packages:
|
||||
to-regex-range: 5.0.1
|
||||
dev: true
|
||||
|
||||
/filter-obj@5.1.0:
|
||||
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==}
|
||||
engines: {node: '>=14.16'}
|
||||
dev: false
|
||||
|
||||
/find-up@5.0.0:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3450,6 +3488,10 @@ packages:
|
||||
mime-types: 2.1.35
|
||||
dev: false
|
||||
|
||||
/from-exponential@1.1.1:
|
||||
resolution: {integrity: sha512-VBE7f5OVnYwdgB3LHa+Qo29h8qVpxhVO9Trlc+AWm+/XNAgks1tAwMFHb33mjeiof77GglsJzeYF7OqXrROP/A==}
|
||||
dev: false
|
||||
|
||||
/fs-minipass@2.1.0:
|
||||
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -3852,6 +3894,10 @@ packages:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
dev: true
|
||||
|
||||
/jose@4.15.4:
|
||||
resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==}
|
||||
dev: false
|
||||
|
||||
/js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
dev: false
|
||||
@@ -3930,6 +3976,11 @@ packages:
|
||||
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
|
||||
dev: true
|
||||
|
||||
/jwt-decode@4.0.0:
|
||||
resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==}
|
||||
engines: {node: '>=18'}
|
||||
dev: false
|
||||
|
||||
/keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
dependencies:
|
||||
@@ -4268,6 +4319,10 @@ packages:
|
||||
pathe: 1.1.1
|
||||
dev: true
|
||||
|
||||
/poseidon-lite@0.2.0:
|
||||
resolution: {integrity: sha512-vivDZnGmz8W4G/GzVA72PXkfYStjilu83rjjUfpL4PueKcC8nfX6hCPh2XhoC5FBgC6y0TA3YuUeUo5YCcNoig==}
|
||||
dev: false
|
||||
|
||||
/postcss-value-parser@4.2.0:
|
||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||
dev: false
|
||||
@@ -4318,6 +4373,15 @@ packages:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
/query-string@8.1.0:
|
||||
resolution: {integrity: sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==}
|
||||
engines: {node: '>=14.16'}
|
||||
dependencies:
|
||||
decode-uri-component: 0.4.1
|
||||
filter-obj: 5.1.0
|
||||
split-on-first: 3.0.0
|
||||
dev: false
|
||||
|
||||
/querystringify@2.2.0:
|
||||
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
|
||||
requiresBuild: true
|
||||
@@ -4624,6 +4688,11 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/split-on-first@3.0.0:
|
||||
resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/stop-iteration-iterator@1.0.0:
|
||||
resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Container, Flex, Heading, Text } from "@radix-ui/themes";
|
||||
import { OwnedObjects } from "./OwnedObjects";
|
||||
import { Mint } from "./Mint";
|
||||
import { Query } from "./Query";
|
||||
import { ZKLogin } from "./ZKLogin";
|
||||
|
||||
export function WalletStatus() {
|
||||
const account = useCurrentAccount();
|
||||
@@ -23,6 +24,8 @@ export function WalletStatus() {
|
||||
<OwnedObjects />
|
||||
<h1>Mint</h1>
|
||||
<Mint />
|
||||
<h1>zklogin</h1>
|
||||
<ZKLogin />
|
||||
<h1>Query</h1>
|
||||
<Query />
|
||||
</Container>
|
||||
|
||||
225
app/src/components/ZKLogin.tsx
Normal file
225
app/src/components/ZKLogin.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ZKLoginStore, client, upsertSalt } from "./zklogin.store";
|
||||
import queryString from "query-string";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { useSuiClientQuery } from "@mysten/dapp-kit";
|
||||
import { MIST_PER_SUI } from "@mysten/sui.js/utils";
|
||||
import * as dn from "dnum-cjs";
|
||||
import { TransactionBlock } from "@mysten/sui.js/transactions";
|
||||
|
||||
function getTransactionBlock(sender: string): TransactionBlock {
|
||||
const txb = new TransactionBlock();
|
||||
txb.setSender(sender);
|
||||
// txb.setGasPrice(5);
|
||||
// txb.setGasBudget(5_000_000);
|
||||
|
||||
txb.moveCall({
|
||||
target: `0xebc67aa17051eaea7c373e5b72c267dcd7267ce060e79479559eee3eaee3f49b::story_nft_display::mint`,
|
||||
arguments: [
|
||||
txb.pure("image 2"),
|
||||
txb.pure(
|
||||
"premium_photo-1669324357471-e33e71e3f3d8?q=80&w=4140&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
return txb;
|
||||
}
|
||||
|
||||
export const ZKLogin = () => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [address, setAddress] = useState<string>(
|
||||
"0xedecedb35529bcbfb7edd620634b748d15e3af0c9734dd520b3d275017213b96"
|
||||
);
|
||||
|
||||
const [digest, setDigest] = useState<string>("");
|
||||
|
||||
const [suiToken, setSuiToken] = useState<bigint>(1n);
|
||||
|
||||
const [store, setStore] = useState<ZKLoginStore>(new ZKLoginStore());
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const oauthParams = queryString.parse(location.hash);
|
||||
if (
|
||||
oauthParams &&
|
||||
oauthParams.id_token &&
|
||||
store.info?.id_token !== oauthParams.id_token
|
||||
) {
|
||||
const salt = await upsertSalt(oauthParams.id_token as string);
|
||||
const store = new ZKLoginStore({
|
||||
salt,
|
||||
id_token: oauthParams.id_token as string,
|
||||
});
|
||||
setStore(store);
|
||||
}
|
||||
})();
|
||||
}, [location.hash, store.info?.id_token]);
|
||||
|
||||
const { data: addressBalance } = useSuiClientQuery(
|
||||
"getBalance",
|
||||
{
|
||||
owner: store.client ? store.client.userAddress : "",
|
||||
},
|
||||
{
|
||||
enabled: store.client ? Boolean(store.client.userAddress) : false,
|
||||
refetchInterval: 1500,
|
||||
}
|
||||
);
|
||||
|
||||
if (store == null) {
|
||||
return <div>loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
store.resetStorage();
|
||||
navigate("/debug");
|
||||
}}
|
||||
>
|
||||
reset config
|
||||
</button>
|
||||
</div>
|
||||
<div>userAddress: {store.client ? store.client.userAddress : ""}</div>
|
||||
<div>
|
||||
Balance:{" "}
|
||||
{addressBalance?.totalBalance
|
||||
? dn.format(
|
||||
[
|
||||
BigInt(addressBalance.totalBalance),
|
||||
MIST_PER_SUI.toString().length - 1,
|
||||
],
|
||||
6
|
||||
)
|
||||
: "0.000000"}{" "}
|
||||
SUI
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
Google Auth Status: {store.client ? "logged in" : "not logged in"}
|
||||
</div>
|
||||
{store.client == null && (
|
||||
<button
|
||||
onClick={() => {
|
||||
store.signInWithGoogle();
|
||||
}}
|
||||
>
|
||||
Login with ZKLogin
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{store.client && (
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
store.client?.requestTestSUIToken();
|
||||
}}
|
||||
>
|
||||
request test SUI token
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{store.client && (
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 20,
|
||||
padding: 20,
|
||||
}}
|
||||
>
|
||||
<hr />
|
||||
<div
|
||||
style={{
|
||||
padding: 20,
|
||||
border: "1px solid black",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
transaction address:
|
||||
<input
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
value={address}
|
||||
onChange={(e) => {
|
||||
setAddress(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>transaction digest: {digest}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
sui token
|
||||
<input
|
||||
value={suiToken.toString()}
|
||||
onChange={(e) => {
|
||||
const parseInt = Number.parseInt(e.target.value);
|
||||
if (Number.isNaN(parseInt)) {
|
||||
setSuiToken(0n);
|
||||
return;
|
||||
}
|
||||
setSuiToken(BigInt(parseInt));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const txb = new TransactionBlock();
|
||||
|
||||
const [coin] = txb.splitCoins(txb.gas, [
|
||||
suiToken * 1000000000n,
|
||||
]);
|
||||
txb.transferObjects([coin], address);
|
||||
txb.setSender(store.client!.userAddress);
|
||||
|
||||
const { bytes, signature } = await txb.sign({
|
||||
client,
|
||||
signer: store.ephemeralKeyPair,
|
||||
});
|
||||
|
||||
const res = await client.executeTransactionBlock({
|
||||
transactionBlock: bytes,
|
||||
signature: store.client!.genZkLoginSignature(signature),
|
||||
});
|
||||
setDigest(res.digest);
|
||||
}}
|
||||
>
|
||||
execute transaction token
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const tx = getTransactionBlock(store.client!.userAddress);
|
||||
|
||||
const { bytes, signature } = await tx.sign({
|
||||
client,
|
||||
signer: store.ephemeralKeyPair,
|
||||
});
|
||||
|
||||
const res = await client.executeTransactionBlock({
|
||||
transactionBlock: bytes,
|
||||
signature: store.client!.genZkLoginSignature(signature),
|
||||
});
|
||||
|
||||
setDigest(res.digest);
|
||||
}}
|
||||
>
|
||||
execute mint
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
272
app/src/components/zklogin.store.tsx
Normal file
272
app/src/components/zklogin.store.tsx
Normal file
@@ -0,0 +1,272 @@
|
||||
import { SuiClient, getFullnodeUrl } from "@mysten/sui.js/client";
|
||||
import { SerializedSignature } from "@mysten/sui.js/cryptography";
|
||||
import { Ed25519Keypair } from "@mysten/sui.js/keypairs/ed25519";
|
||||
import { fromB64 } from "@mysten/sui.js/utils";
|
||||
import {
|
||||
genAddressSeed,
|
||||
generateNonce,
|
||||
generateRandomness,
|
||||
getExtendedEphemeralPublicKey,
|
||||
getZkLoginSignature,
|
||||
jwtToAddress,
|
||||
} from "@mysten/zklogin";
|
||||
import { jwtDecode, JwtPayload } from "jwt-decode";
|
||||
import { auth, db } from "../firebase";
|
||||
import { doc, getDoc, setDoc } from "firebase/firestore";
|
||||
import { GoogleAuthProvider, signInWithCredential } from "firebase/auth";
|
||||
|
||||
const CLIENT_ID =
|
||||
"78105453039-rc9sdmkol2dej5u365dfgq6bjeopmu0f.apps.googleusercontent.com";
|
||||
const REDIRECT_URI = "http://localhost:5173/debug";
|
||||
|
||||
export const SUI_DEVNET_FAUCET = "https://faucet.devnet.sui.io/gas";
|
||||
|
||||
export const PROVER_URL = "https://prover-dev.mystenlabs.com/v1";
|
||||
|
||||
export const client = new SuiClient({ url: getFullnodeUrl("devnet") });
|
||||
|
||||
export type PartialZkLoginSignature = Omit<
|
||||
Parameters<typeof getZkLoginSignature>["0"]["inputs"],
|
||||
"addressSeed"
|
||||
>;
|
||||
|
||||
export class ZKLoginStore {
|
||||
public initialized = false;
|
||||
|
||||
ephemeralKeyPair!: Ed25519Keypair;
|
||||
epoch?: {
|
||||
currentEpoch: number;
|
||||
maxEpoch: number;
|
||||
};
|
||||
nonce?: {
|
||||
randomness: string;
|
||||
currentNonce: string;
|
||||
};
|
||||
|
||||
client?: ZKLoginClient;
|
||||
|
||||
constructor(readonly info?: { salt: string; id_token: string }) {
|
||||
this.initailize();
|
||||
}
|
||||
|
||||
resetStorage() {
|
||||
window.sessionStorage.removeItem("ephemeralKeyPair");
|
||||
window.sessionStorage.removeItem("randomness");
|
||||
}
|
||||
|
||||
private async initailize() {
|
||||
this.genEphemeralKeyPair();
|
||||
await this.getEpoch();
|
||||
this.genNone();
|
||||
|
||||
if (this.info != null) {
|
||||
this.client = new ZKLoginClient(this, this.info.salt, this.info.id_token);
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
private get publicKey() {
|
||||
return this.ephemeralKeyPair.getPublicKey();
|
||||
}
|
||||
|
||||
get extendedEphemeralPublicKey() {
|
||||
return getExtendedEphemeralPublicKey(this.ephemeralKeyPair.getPublicKey());
|
||||
}
|
||||
|
||||
private genEphemeralKeyPair() {
|
||||
const ephemeralKeyPair = window.sessionStorage.getItem("ephemeralKeyPair");
|
||||
|
||||
if (ephemeralKeyPair) {
|
||||
this.ephemeralKeyPair = Ed25519Keypair.fromSecretKey(
|
||||
fromB64(ephemeralKeyPair)
|
||||
);
|
||||
} else {
|
||||
this.ephemeralKeyPair = Ed25519Keypair.generate();
|
||||
window.sessionStorage.setItem(
|
||||
"ephemeralKeyPair",
|
||||
this.ephemeralKeyPair.export().privateKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async getEpoch() {
|
||||
const { epoch } = await client.getLatestSuiSystemState();
|
||||
this.epoch = {
|
||||
currentEpoch: Number(epoch),
|
||||
maxEpoch: Number(epoch) + 10,
|
||||
};
|
||||
}
|
||||
|
||||
private genNone() {
|
||||
if (this.epoch == null) {
|
||||
throw new Error("epoch is not defined");
|
||||
}
|
||||
|
||||
let randomness = window.sessionStorage.getItem("randomness");
|
||||
if (randomness === null) {
|
||||
randomness = generateRandomness();
|
||||
window.sessionStorage.setItem("randomness", randomness);
|
||||
}
|
||||
|
||||
const nonce = generateNonce(
|
||||
this.publicKey,
|
||||
this.epoch.maxEpoch,
|
||||
randomness
|
||||
);
|
||||
|
||||
this.nonce = {
|
||||
randomness,
|
||||
currentNonce: nonce,
|
||||
};
|
||||
}
|
||||
|
||||
async signInWithGoogle() {
|
||||
if (this.nonce == null) {
|
||||
throw new Error("nonce is not defined");
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
client_id: CLIENT_ID,
|
||||
redirect_uri: REDIRECT_URI,
|
||||
response_type: "id_token",
|
||||
scope: "openid email",
|
||||
nonce: this.nonce.currentNonce,
|
||||
});
|
||||
const loginURL = `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
|
||||
// window.location.replace(loginURL);
|
||||
window.location.href = loginURL;
|
||||
}
|
||||
}
|
||||
|
||||
class ZKLoginClient {
|
||||
private readonly jwtPayload: JwtPayload;
|
||||
|
||||
private partialZkLoginSignature: PartialZkLoginSignature | undefined;
|
||||
|
||||
constructor(
|
||||
readonly store: ZKLoginStore,
|
||||
readonly salt: string,
|
||||
readonly id_token: string
|
||||
) {
|
||||
this.jwtPayload = jwtDecode(id_token);
|
||||
|
||||
console.log(this.jwtPayload);
|
||||
|
||||
this.initailize();
|
||||
}
|
||||
|
||||
private async initailize() {
|
||||
await this.genPartialZkLoginSignature();
|
||||
}
|
||||
|
||||
get userAddress() {
|
||||
const zkLoginUserAddress = jwtToAddress(this.id_token, this.salt);
|
||||
return zkLoginUserAddress;
|
||||
}
|
||||
|
||||
async requestTestSUIToken() {
|
||||
await fetch(SUI_DEVNET_FAUCET, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
mode: "no-cors",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
FixedAmountRequest: {
|
||||
recipient: this.userAddress,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
private async genPartialZkLoginSignature() {
|
||||
if (!this.id_token) {
|
||||
throw new Error("jwtString is not defined");
|
||||
}
|
||||
|
||||
if (!this.store.nonce || !this.store.epoch) {
|
||||
throw new Error("nonce or epoch is not defined");
|
||||
}
|
||||
|
||||
const zkProofResult = await fetch(PROVER_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jwt: this.id_token,
|
||||
extendedEphemeralPublicKey: this.store.extendedEphemeralPublicKey,
|
||||
maxEpoch: this.store.epoch.maxEpoch,
|
||||
jwtRandomness: this.store.nonce.randomness,
|
||||
salt: this.salt,
|
||||
keyClaimName: "sub",
|
||||
}),
|
||||
}).then((res) => res.json());
|
||||
|
||||
this.partialZkLoginSignature = zkProofResult as PartialZkLoginSignature;
|
||||
}
|
||||
|
||||
private get addressSeed() {
|
||||
if (this.jwtPayload == null) {
|
||||
throw new Error("jwtPayload is not defined");
|
||||
}
|
||||
|
||||
return genAddressSeed(
|
||||
BigInt(this.salt),
|
||||
"sub",
|
||||
this.jwtPayload.sub!,
|
||||
this.jwtPayload.aud as string
|
||||
).toString();
|
||||
}
|
||||
|
||||
genZkLoginSignature(signature: string) {
|
||||
if (!this.store.epoch) {
|
||||
throw new Error("epoch is not defined");
|
||||
}
|
||||
|
||||
if (!this.partialZkLoginSignature) {
|
||||
throw new Error("partialZkLoginSignature is not defined");
|
||||
}
|
||||
|
||||
return getZkLoginSignature({
|
||||
inputs: {
|
||||
...this.partialZkLoginSignature,
|
||||
addressSeed: this.addressSeed,
|
||||
},
|
||||
maxEpoch: this.store.epoch.maxEpoch,
|
||||
userSignature: signature,
|
||||
}) as SerializedSignature;
|
||||
}
|
||||
}
|
||||
|
||||
export const upsertSalt = async (id_token: string) => {
|
||||
const credential = GoogleAuthProvider.credential(id_token);
|
||||
await signInWithCredential(auth, credential);
|
||||
|
||||
if (auth.currentUser?.uid == null) {
|
||||
throw new Error("auth.currentUser?.uid is not defined");
|
||||
}
|
||||
const docRef = doc(db, "users", auth.currentUser.uid);
|
||||
const docSnap = await getDoc(docRef);
|
||||
|
||||
if (docSnap.exists()) {
|
||||
const data = docSnap.data();
|
||||
|
||||
if (data?.salt) {
|
||||
return data.salt;
|
||||
}
|
||||
}
|
||||
|
||||
const salt = generateRandomness();
|
||||
|
||||
setDoc(
|
||||
docRef,
|
||||
{
|
||||
salt,
|
||||
},
|
||||
{ merge: true }
|
||||
);
|
||||
|
||||
return salt;
|
||||
};
|
||||
Reference in New Issue
Block a user