zklogin-plus
Version:
A powerful zkLogin plugin for Sui blockchain - inspired by @mysten/enoki
218 lines • 5.95 kB
JavaScript
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