diff --git a/examples/keystore-management/src/components/RLNMembershipRegistration.tsx b/examples/keystore-management/src/components/RLNMembershipRegistration.tsx
index 79cdbf2..842cbc2 100644
--- a/examples/keystore-management/src/components/RLNMembershipRegistration.tsx
+++ b/examples/keystore-management/src/components/RLNMembershipRegistration.tsx
@@ -1,14 +1,18 @@
"use client";
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
import { useRLN } from '../contexts/RLNContext';
import { useWallet } from '../contexts/WalletContext';
-import { DecryptedCredentials } from '@waku/rln';
+import { DecryptedCredentials, IdentityCredential } from '@waku/rln';
+import { usePasskey } from '@/contexts/usePasskey';
export default function RLNMembershipRegistration() {
- const { registerMembership, isInitialized, isStarted, rateMinLimit, rateMaxLimit, error, initializeRLN } = useRLN();
+ const { registerMembership, isInitialized, isStarted, rateMinLimit, rateMaxLimit, error, initializeRLN, rln } = useRLN();
const { isConnected, address, chainId } = useWallet();
-
+ const { createPasskey, hasPasskey, getPasskey, getPasskeyCredential } = usePasskey();
+
+ const [identity, setIdentity] = useState();
+
const [rateLimit, setRateLimit] = useState(rateMinLimit);
const [isRegistering, setIsRegistering] = useState(false);
const [isInitializing, setIsInitializing] = useState(false);
@@ -20,6 +24,17 @@ export default function RLNMembershipRegistration() {
credentials?: DecryptedCredentials;
}>({});
+ const handleReadPasskey = () => {
+ if (rln && hasPasskey()) {
+ const seed = getPasskey();
+ getPasskeyCredential(seed!);
+ const _identity = rln.zerokit.generateSeededIdentityCredential(seed!);
+ console.log(_identity);
+ setIdentity(_identity);
+ }
+ };
+
+ console.log(identity);
const isLineaSepolia = chainId === 59141;
const handleRateLimitChange = (e: React.ChangeEvent) => {
@@ -65,7 +80,7 @@ export default function RLNMembershipRegistration() {
warning: 'Please check your wallet to sign the registration message.'
});
- const result = await registerMembership(rateLimit);
+ const result = await registerMembership(rateLimit, createPasskey);
setRegistrationResult({
...result,
credentials: result.credentials
@@ -139,6 +154,19 @@ export default function RLNMembershipRegistration() {
{isInitializing ? "Initializing..." : "Initialize RLN"}
)}
+
{error && (
{error}
@@ -251,7 +279,7 @@ export default function RLNMembershipRegistration() {
Your RLN membership is now registered and can be used with your Waku node.
- {registrationResult.credentials && (
+ {(registrationResult.credentials && !hasPasskey()) && (
Your RLN Credentials:
@@ -269,7 +297,7 @@ export default function RLNMembershipRegistration() {
ID Trapdoor: {Buffer.from(registrationResult.credentials.identity.IDTrapdoor).toString('hex')}
-
Membership:
+ {/*
Membership:
Chain ID: {registrationResult.credentials.membership.chainId}
@@ -278,7 +306,42 @@ export default function RLNMembershipRegistration() {
Tree Index: {registrationResult.credentials.membership.treeIndex}
+
*/}
+
+
+ These credentials are your proof of membership. Store them securely.
+
+
+ )}
+
+ {identity && (
+
+
Your RLN Credentials:
+
+
Identity:
+
+ ID Commitment: {Buffer.from(identity.IDCommitment).toString('hex')}
+
+ ID Secret Hash: {Buffer.from(identity.IDSecretHash).toString('hex')}
+
+
+ ID Nullifier: {Buffer.from(identity.IDNullifier).toString('hex')}
+
+
+ ID Trapdoor: {Buffer.from(identity.IDTrapdoor).toString('hex')}
+
+
+ {/*
Membership:
+
+ Chain ID: {registrationResult.credentials.membership.chainId}
+
+
+ Contract Address: {registrationResult.credentials.membership.address}
+
+
+ Tree Index: {registrationResult.credentials.membership.treeIndex}
+
*/}
These credentials are your proof of membership. Store them securely.
diff --git a/examples/keystore-management/src/contexts/RLNContext.tsx b/examples/keystore-management/src/contexts/RLNContext.tsx
index 5ffda15..4994b4d 100644
--- a/examples/keystore-management/src/contexts/RLNContext.tsx
+++ b/examples/keystore-management/src/contexts/RLNContext.tsx
@@ -6,7 +6,7 @@ import { useWallet } from './WalletContext';
import { ethers } from 'ethers';
// Constants
-const SIGNATURE_MESSAGE = "Sign this message to generate your RLN credentials";
+// const SIGNATURE_MESSAGE = "Sign this message to generate your RLN credentials";
const ERC20_ABI = [
"function allowance(address owner, address spender) view returns (uint256)",
"function approve(address spender, uint256 amount) returns (bool)",
@@ -25,7 +25,7 @@ interface RLNContextType {
isStarted: boolean;
error: string | null;
initializeRLN: () => Promise;
- registerMembership: (rateLimit: number) => Promise<{ success: boolean; error?: string; credentials?: DecryptedCredentials }>;
+ registerMembership: (rateLimit: number, createPasskey: (s: ethers.Signer) => Promise) => Promise<{ success: boolean; error?: string; credentials?: DecryptedCredentials }>;
rateMinLimit: number;
rateMaxLimit: number;
}
@@ -148,7 +148,7 @@ export function RLNProvider({ children }: { children: ReactNode }) {
}
};
- const registerMembership = async (rateLimit: number) => {
+ const registerMembership = async (rateLimit: number, createPasskey: (s: ethers.Signer) => Promise) => {
console.log("registerMembership called with rate limit:", rateLimit);
if (!rln || !isStarted) {
@@ -223,22 +223,20 @@ export function RLNProvider({ children }: { children: ReactNode }) {
console.log("Token allowance already sufficient");
}
- // Generate signature for identity
- const message = `${SIGNATURE_MESSAGE} ${Date.now()}`;
- const signature = await signer.signMessage(message);
+ const seed = await createPasskey(signer);
- const _credentials = await rln.registerMembership({signature: signature});
- if (!_credentials) {
- throw new Error("Failed to register membership: No credentials returned");
- }
- if (!_credentials.identity) {
- throw new Error("Failed to register membership: Missing identity information");
- }
- if (!_credentials.membership) {
- throw new Error("Failed to register membership: Missing membership information");
- }
+ // const _credentials = await rln.registerMembership({signature: seed});
+ // if (!_credentials) {
+ // throw new Error("Failed to register membership: No credentials returned");
+ // }
+ // if (!_credentials.identity) {
+ // throw new Error("Failed to register membership: Missing identity information");
+ // }
+ // if (!_credentials.membership) {
+ // throw new Error("Failed to register membership: Missing membership information");
+ // }
- return { success: true, credentials: _credentials };
+ return { success: true, credentials: null };
} catch (err) {
let errorMsg = "Failed to register membership";
if (err instanceof Error) {
@@ -258,7 +256,7 @@ export function RLNProvider({ children }: { children: ReactNode }) {
} else {
console.log("Wallet not connected or no signer available, skipping RLN initialization");
}
- }, [isConnected, signer]);
+ }, [isConnected, signer, initializeRLN]);
// Debug log for state changes
useEffect(() => {
diff --git a/examples/keystore-management/src/contexts/usePasskey.ts b/examples/keystore-management/src/contexts/usePasskey.ts
new file mode 100644
index 0000000..77ae090
--- /dev/null
+++ b/examples/keystore-management/src/contexts/usePasskey.ts
@@ -0,0 +1,124 @@
+import { useState, useEffect } from 'react';
+
+export const usePasskey = () => {
+ const [passkeyId, setPasskeyId] = useState(null);
+
+ // Load passkey when component mounts
+ // useEffect(() => {
+ // const loadPasskey = async () => {
+ // const storedPasskeyId = localStorage.getItem('rlnPasskeyId');
+
+ // if (storedPasskeyId) {
+ // // Try to get the credential from navigator.credentials
+ // try {
+ // const credential = await getPasskeyCredential(storedPasskeyId);
+ // if (credential) {
+ // setPasskeyId(storedPasskeyId);
+ // }
+ // } catch (error) {
+ // console.error("Failed to retrieve passkey:", error);
+ // // If the credential can't be found, clear localStorage
+ // localStorage.removeItem('rlnPasskeyId');
+ // }
+ // }
+ // };
+
+ // loadPasskey();
+ // }, []);
+
+ // Check if a passkey exists
+ const hasPasskey = (): boolean => {
+ return localStorage.getItem('rlnPasskeyId') !== null;
+ };
+
+ const getPasskey = (): string | null => {
+ const storedPasskeyId = localStorage.getItem('rlnPasskeyId');
+ return storedPasskeyId;
+ };
+
+ const getPasskeyCredential = async (credentialId: string): Promise => {
+ try {
+ const idBuffer = Uint8Array.from(
+ atob(credentialId.replace(/-/g, '+').replace(/_/g, '/')),
+ c => c.charCodeAt(0)
+ );
+
+ // Create get options for the credential
+ const getOptions = {
+ publicKey: {
+ challenge: new Uint8Array(32),
+ allowCredentials: [{
+ id: idBuffer,
+ type: 'public-key',
+ }],
+ userVerification: 'required',
+ timeout: 60000
+ }
+ };
+
+ // Generate random values for the challenge
+ window.crypto.getRandomValues(getOptions.publicKey.challenge);
+
+ // Get the credential
+ const credential = await navigator.credentials.get(getOptions as any) as PublicKeyCredential;
+ return credential;
+ } catch (error) {
+ console.error("Error retrieving passkey:", error);
+ return null;
+ }
+ };
+
+ // Create a new passkey
+ const createPasskey = async (signer: any): Promise => {
+ // Generate a random challenge for the passkey
+ const challenge = new Uint8Array(32);
+ window.crypto.getRandomValues(challenge);
+
+ // Create credential options for the passkey
+ const credentialCreationOptions = {
+ publicKey: {
+ challenge: challenge,
+ rp: {
+ name: "RLN Membership",
+ id: window.location.hostname
+ },
+ user: {
+ id: new Uint8Array([...new TextEncoder().encode(await signer.getAddress())]),
+ name: "RLN Membership Passkey",
+ displayName: "RLN Membership Passkey"
+ },
+ pubKeyCredParams: [
+ { type: "public-key", alg: -7 }, // ES256
+ { type: "public-key", alg: -257 } // RS256
+ ],
+ authenticatorSelection: {
+ authenticatorAttachment: "platform",
+ requireResidentKey: true,
+ userVerification: "required"
+ },
+ timeout: 60000
+ }
+ };
+
+ const credential = await navigator.credentials.create(credentialCreationOptions as any) as PublicKeyCredential;
+
+ if (!credential) {
+ throw new Error("Failed to create passkey");
+ }
+
+ // Store credential ID in state and localStorage
+ setPasskeyId(credential.id);
+ localStorage.setItem('rlnPasskeyId', credential.id);
+
+ return credential.id;
+ };
+
+ // Return the methods and state for passkey management
+ return {
+ hasPasskey,
+ getPasskey,
+ passkeyId,
+ createPasskey,
+ getPasskeyCredential
+ };
+};
\ No newline at end of file