UNPKG

zklogin-plus

Version:

A powerful zkLogin plugin for Sui blockchain - inspired by @mysten/enoki

218 lines 5.95 kB
import { Ed25519Keypair } from '@onelabs/sui/keypairs/ed25519'; import { generateNonce, generateRandomness, jwtToAddress, getExtendedEphemeralPublicKey, genAddressSeed, getZkLoginSignature } from '@onelabs/sui/zklogin'; import { jwtDecode } from 'jwt-decode'; import axios from 'axios'; /** * 网络URL映射 */ export const NETWORK_URLS = { mainnet: 'https://fullnode.mainnet.sui.io', testnet: 'https://fullnode.testnet.sui.io', devnet: 'https://fullnode.devnet.sui.io', }; /** * 默认配置 */ export const DEFAULT_CONFIG = { // proverEndpoint: 'https://prover-dev.mystenlabs.com/v1', proverEndpoint: 'https://zkprover.deltax.online/v1', faucetEndpoint: 'https://faucet-testnet.onelabs.cc/gas', storagePrefix: 'zklogin_plus_', getSaltUrl: 'https://salt.deltax.online/api/userSalt/Google', }; /** * 生成临时密钥对 */ export function generateEphemeralKeyPair(maxEpoch) { const keyPair = Ed25519Keypair.generate(); const randomness = generateRandomness(); const nonce = generateNonce(keyPair.getPublicKey(), maxEpoch, randomness); return { keyPair, maxEpoch, randomness, nonce, }; } /** * 解码JWT令牌 */ export function decodeJwtToken(token) { try { const payload = jwtDecode(token); const isValid = payload.exp ? Date.now() / 1000 < payload.exp : false; const expiresAt = payload.exp ? payload.exp * 1000 : 0; return { token, payload, isValid, expiresAt, }; } catch (error) { throw new Error(`Invalid JWT token: ${error}`); } } /** * 生成用户Salt */ export async function generateUserSalt(getSaltUrl, id_token) { const { data: { data: { salt } } } = await axios.post(getSaltUrl, { jwt: id_token, }); return salt; } /** * 生成ZkLogin地址 */ export function generateZkLoginAddress(jwt, userSalt) { try { const address = jwtToAddress(jwt, userSalt); const payload = jwtDecode(jwt); if (!payload.sub || !payload.aud) { throw new Error('JWT missing required claims (sub, aud)'); } const addressSeed = genAddressSeed(BigInt(userSalt), 'sub', payload.sub, Array.isArray(payload.aud) ? payload.aud[0] : payload.aud).toString(); return { address, addressSeed, }; } catch (error) { throw new Error(`Failed to generate zkLogin address: ${error}`); } } /** * 获取扩展的临时公钥 */ export function getExtendedPublicKey(publicKey) { return getExtendedEphemeralPublicKey(publicKey); } /** * 请求ZK证明 */ export async function requestZkProof(jwt, extendedEphemeralPublicKey, maxEpoch, randomness, userSalt, proverEndpoint) { try { const response = await axios.post(proverEndpoint, { jwt, extendedEphemeralPublicKey, maxEpoch, jwtRandomness: randomness, salt: userSalt, keyClaimName: 'sub', }, { headers: { 'Content-Type': 'application/json', }, timeout: 30000, // 30秒超时 }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`ZK proof request failed: ${error.response?.data?.message || error.message}`); } throw error; } } /** * 生成zkLogin签名 */ export function buildZkLoginSignature(zkProof, maxEpoch, userSignature, addressSeed) { return getZkLoginSignature({ inputs: { ...zkProof, addressSeed, }, maxEpoch, userSignature, }); } /** * 获取当前epoch */ export async function getCurrentEpoch(suiClient) { const systemState = await suiClient.getLatestSuiSystemState(); return Number(systemState.epoch); } /** * 计算最大epoch(当前epoch + 10) */ export function calculateMaxEpoch(currentEpoch) { return currentEpoch + 10; } /** * 获取地址余额 */ export async function getAddressBalance(suiClient, address) { try { const balance = await suiClient.getBalance({ owner: address }); return balance.totalBalance; } catch (error) { return '0'; } } /** * 请求水龙头 */ export async function requestFaucet(address, faucetEndpoint) { try { await axios.post(faucetEndpoint, { FixedAmountRequest: { recipient: address, }, }); } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`Faucet request failed: ${error.response?.data?.message || error.message}`); } throw error; } } /** * 构建OAuth URL */ export function buildOAuthUrl(provider, clientId, redirectUri, nonce, state) { const baseUrls = { google: 'https://accounts.google.com/o/oauth2/v2/auth', facebook: 'https://www.facebook.com/v18.0/dialog/oauth', twitch: 'https://id.twitch.tv/oauth2/authorize', apple: 'https://appleid.apple.com/auth/authorize', }; const params = new URLSearchParams({ client_id: clientId, redirect_uri: redirectUri, response_type: 'id_token', scope: 'openid', nonce, }); if (state) { params.set('state', state); } return `${baseUrls[provider]}?${params.toString()}`; } /** * 验证配置 */ export function validateConfig(config) { if (!config.clientId) { throw new Error('clientId is required'); } if (!config.redirectUri) { throw new Error('redirectUri is required'); } } /** * 错误处理工具 */ export class ZkLoginError extends Error { constructor(message, code, step) { super(message); this.code = code; this.step = step; this.name = 'ZkLoginError'; } } //# sourceMappingURL=index.js.map