diff --git a/Cargo.toml b/Cargo.toml index 2b82e94..668ac44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ license = "MIT" [workspace.dependencies] stwo-prover = { git = "https://github.com/starkware-libs/stwo.git" } +stwo_wasm = { path= "crates/stwo_wasm" } tokio = { version = "1", default-features = false } tracing = { version = "0.1", default-features = false } tracing-subscriber = "0.3" diff --git a/askeladd-dvm-marketplace/src/app/components/ProgramCard.tsx b/askeladd-dvm-marketplace/src/app/components/ProgramCard.tsx new file mode 100644 index 0000000..2924be4 --- /dev/null +++ b/askeladd-dvm-marketplace/src/app/components/ProgramCard.tsx @@ -0,0 +1,490 @@ +import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; +import { Event as NostrEvent, Relay, SimplePool } from 'nostr-tools'; +import React, { useEffect, useMemo, useState } from 'react'; +import { ContractUploadType, IGenerateZKPRequestDVM, JobResultProver, KIND_JOB_REQUEST, KIND_JOB_RESULT, ProgramInternalContractName } from '@/types'; +import { useFetchEvents } from '@/hooks/useFetchEvents'; +import { ASKELADD_RELAY } from '@/constants/relay'; +import init, { verify_stark_proof, verify_stark_proof_wide_fibo, prove_and_verify, stark_proof_wide_fibo, prove_stark_proof_poseidon, verify_stark_proof_poseidon } from "../../pkg" +import { useNostrContext } from '@/context/NostrContext'; +// Define the props for the component +interface TagsCardProps { + event?: NDKEvent | NostrEvent; // Array of array of strings + program?: IGenerateZKPRequestDVM +} +const ProgramCard: React.FC = ({ event, program }) => { + const { fetchEvents, fetchEventsTools, setupSubscriptionNostr } = useFetchEvents() + const { ndk, pool } = useNostrContext() + + const [isOpenForm, setIsOpenForm] = useState(false) + const [logSize, setLogSize] = useState(5); + const [claim, setClaim] = useState(443693538); + const [publicKey, setPublicKey] = useState(); + const [jobId, setJobId] = useState(); + const [error, setError] = useState() + const [starkProof, setStarkProof] = useState() + const [jobEventResult, setJobEventResult] = useState() + const [seeTag, setSeeTag] = useState(false) + const [proof, setProof] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [isInitialized, setIsInitialized] = useState(false); + const [isFetchJob, setIsFetchJob] = useState(false); + const [isLoadingJobResult, setIsLoadingJobResult] = useState(false); + const [isWaitingJob, setIsWaitingJob] = useState(false); + const [timestampJob, setTimestampJob] = useState(); + const [proofStatus, setProofStatus] = useState< + "idle" | "pending" | "received" | "verified" + >("idle"); + const [selectedEvent, setSelectedEvent] = useState() + + let eventIdRequest = useMemo(() => { + return jobId + }, [jobId]) + + // Init wasm module to run_fibonacci_verify + useEffect(() => { + init() + .then(() => setIsInitialized(true)) + .catch((error) => { + console.error("Failed to initialize WASM module:", error); + + }); + }, []); + + useEffect(() => { + // const pool = new SimplePool(); + if (pool) { + runSubscriptionEvent(pool) + } + if (!jobId && !jobEventResult) { + timeoutWaitingForJobResult() + } + }, [jobId, jobEventResult, pool]) + + + const runSubscriptionEvent = (pool: SimplePool, pubkey?: string) => { + + // WebSocket connection setup + // const ws = new WebSocket([ASKELADD_RELAY[0]]); // Replace with your Nostr relay URL + + // ws.onopen = () => { + // // Subscribe to specific events, adjust filters as needed + // ws.send(JSON.stringify({ + // "req": "EVENTS", + // // "filter": { + // // "#e": ["3a5f5b4..."] // Your event criteria here + // // } + // })); + // }; + + // ws.onmessage = (event) => { + // const data = JSON.parse(event.data); + // if (data) { + // if (!jobId) return; + // if (pubkey && data?.pubkey == pubkey) { + // setJobId(data?.id) + // } + // // setEvents(currentEvents => [...currentEvents, data]); + // } + // }; + + // ws.onerror = (error) => { + // console.error("WebSocket error:", error); + // }; + + let poolSubscription = pool.subscribeMany( + ASKELADD_RELAY, + [ + // { + // kinds: [KIND_JOB_REQUEST as NDKKind], + // // since:timestampJob + // // authors: pubkey ? [pubkey] : [] + // }, + { + kinds: [KIND_JOB_RESULT as NDKKind], + // since:timestampJob + }, + ], + { + onevent(event) { + // if (event?.kind == KIND_JOB_REQUEST) { + // if (!jobId) return; + // if (pubkey && event?.pubkey == pubkey) { + // setJobId(event?.id) + // } + // poolSubscription.close(); + // } + if (event?.kind == KIND_JOB_RESULT) { + if (!jobId) return; + let id = jobId ?? eventIdRequest; + if (id && !jobEventResult) { + console.log("Event job result received: ", event?.id); + console.log("event job content result include job: ", id); + let isIncludedJobId = event?.content?.includes(jobId) + let jobEventResultFind = event?.content?.includes(jobId) + console.log("isIncludedJobId", isIncludedJobId); + if (isIncludedJobId) { + console.log("Event JOB_RESULT find", jobEventResultFind); + getDataOfEvent(event); + setJobEventResult(event) + } + } + poolSubscription.close(); + } + }, + onclose: () => { + poolSubscription.close() + }, + oneose() { + poolSubscription.close() + } + } + ) + } + + + const timeoutWaitingForJobResult = async () => { + console.log("waiting timeout job result") + setTimeout(() => { + waitingForJobResult() + }, 5000); + } + + /** Effect to fetch the job result when a job request is sent */ + const waitingForJobResult = async () => { + if (jobEventResult && jobId) return; + fetchEventsProof() + setIsLoading(false); + setIsWaitingJob(false) + } + + const fetchEventsProof = async () => { + console.log("fetch events job result proof") + // if(jobEventResult && jobId)return; + setIsFetchJob(false); + setIsLoadingJobResult(true); + const { events } = await fetchEventsTools({ + kind: KIND_JOB_RESULT, + // since: timestampJob, + // search: jobId + // search: `#${jobId}`, + }) + console.log("events job result", events); + if (!events) return; + let lastEvent = events[events?.length - 1] + if (!lastEvent) return; + let id = jobId ?? eventIdRequest; + if (jobEventResult && jobEventResult?.id == id && proof && proofStatus != "pending") return; + if (id && !jobEventResult) { + let jobEventResultFind = events?.find((e) => e?.content?.includes(id)) + console.log("jobEventResultFind", jobEventResultFind); + let filterJob = events?.filter((e) => e?.id?.includes(id)) + // console.log("filterJob", filterJob); + if (jobEventResultFind?.id) { + console.log("Event JOB_RESULT find", jobEventResultFind); + getDataOfEvent(jobEventResultFind); + setJobEventResult(jobEventResultFind) + } + } + } + + const getDataOfEvent = (lastEvent?: NDKEvent | NostrEvent) => { + if (!lastEvent || !lastEvent?.content) return; + setSelectedEvent(lastEvent); + setProof(lastEvent?.content?.toString()) + const jobProofSerialize: any = JSON.parse(lastEvent?.content) + console.log('jobProofSerialize serialize', jobProofSerialize); + const proofSerialize = jobProofSerialize?.response?.proof; + console.log('proof serialize', proofSerialize); + setStarkProof(proofSerialize); + setProofStatus("received"); + return proofSerialize + } + + const fetchJobRequest = async (pubkey?: string) => { + const { events } = await fetchEventsTools({ + kind: KIND_JOB_REQUEST, + since: timestampJob, + // authors: pubkey ? [pubkey] : [] + }); + console.log("events job request", events); + if (!events) return; + const lastEvent = events[0] + if (!lastEvent?.id) return; + const lastEventId = lastEvent?.id; + if (pubkey && pubkey == lastEvent?.pubkey) { + console.log("lastEventId", lastEventId) + setJobId(lastEventId); + eventIdRequest = lastEventId; + setIsWaitingJob(true) + } + } + + + /** Submit job with JOB_REQUEST 5600 + * - Use extension NIP-7 + * - Default public key demo + * - NDK generate key or import later +*/ + const submitJob = async () => { + try { + + /** Todo better check */ + if (!isLoading && !isOpenForm && Object.entries(form).length == 0) return; + setIsLoading(true); + setIsFetchJob(false); + setJobId(undefined) + setProofStatus("pending"); + setProof(null); + setJobEventResult(undefined); + setError(undefined); + const tags = [ + ['param', 'log_size', logSize.toString()], + ['param', 'claim', claim.toString()], + ['output', 'text/json'] + ]; + + const inputs: Map = new Map(); + { + Object.entries(form).map(([key, value]) => { + inputs.set(key, value as string) + } + ) + } + console.log("inputs", Object.fromEntries(inputs)) + const content = JSON.stringify({ + request: form, + program: { + contract_name: program?.program_params?.contract_name, + internal_contract_name: program?.program_params?.internal_contract_name, + contract_reached: program?.program_params?.contract_reached, + inputs: Object.fromEntries(inputs), + inputs_types: undefined, + inputs_encrypted: undefined + } + }) + // Define the timestamp before which you want to fetch events + setTimestampJob(new Date().getTime()) + console.log("inputs", inputs) + console.log("content", content) + /** Use Nostr extension to send event */ + const pool = new SimplePool(); + if (typeof window !== "undefined" && window.nostr) { + const pubkey = await window.nostr.getPublicKey(); + let created_at = new Date().getTime(); + setPublicKey(pubkey) + const event = await window.nostr.signEvent({ + pubkey: pubkey, + created_at: created_at, + kind: 5600, + tags: tags, + content: content + }) // takes an event object, adds `id`, `pubkey` and `sig` and returns it + // Setup job request to fetch job id + + /** @TODO why the event id is not return? + * - get the last event and fetch job_id event + * - check if events is sent with subscription + * + */ + // let eventID = await relay.publish(event as EventNostr); + const eventID = await Promise.any(pool.publish(ASKELADD_RELAY, event as NostrEvent)); + console.log("eventID", eventID[0]) + await fetchJobRequest(pubkey) + setIsWaitingJob(true); + await timeoutWaitingForJobResult() + + } else { + + /** @TODO flow is user doesn't have NIP-07 extension */ + // let { result, event } = await sendNote({ content, tags, kind: 5600 }) + // console.log("event", event) + // if (event?.sig) { + // setJobId(event?.sig); + // } + // setIsWaitingJob(true) + /** NDK event + * Generate or import private key after + */ + } + } catch (e) { + } finally { + setIsLoading(false); + } + + }; + + const verifyProofHandler = async () => { + try { + if (proof) { + setIsLoading(true); + const inputs: Map = new Map(); + { + Object.entries(form).map(([key, value]) => { + inputs.set(key, value as string) + } + ) + } + if (program?.program_params?.internal_contract_name == ProgramInternalContractName.FibonnacciProvingRequest) { + const prove_result = prove_and_verify(logSize, claim); + console.log("prove_result", prove_result); + const serialised_proof_from_nostr_event = JSON.stringify(starkProof); + console.log("serialised_proof_from_nostr_event", serialised_proof_from_nostr_event); + const verify_result = verify_stark_proof(logSize, claim, serialised_proof_from_nostr_event); + console.log("verify result", verify_result); + console.log("verify message", verify_result.message); + console.log("verify success", verify_result.success); + if (verify_result?.success) { + console.log("is success verify result") + setProofStatus("verified"); + } else { + setError(verify_result?.message) + } + } + + else if (program?.program_params?.internal_contract_name == ProgramInternalContractName.WideFibonnaciProvingRequest) { + let log_n_instances = inputs.get("log_n_instances"); + let log_fibonacci_size = inputs.get("log_fibonacci_size"); + if (!log_n_instances && !log_fibonacci_size) return; + const prove_result = stark_proof_wide_fibo(Number(log_fibonacci_size), Number(log_n_instances)); + console.log("wide fibo prove_result", prove_result); + const serialised_proof_from_nostr_event = JSON.stringify(starkProof); + console.log("serialised_proof_from_nostr_event", serialised_proof_from_nostr_event); + const verify_result = verify_stark_proof_wide_fibo(Number(log_fibonacci_size), Number(log_n_instances), serialised_proof_from_nostr_event); + console.log("verify result", verify_result); + console.log("verify message", verify_result.message); + console.log("verify success", verify_result.success); + if (verify_result?.success) { + console.log("is success verify result") + setProofStatus("verified"); + } else { + setError(verify_result?.message) + } + } + else if (program?.program_params?.internal_contract_name == ProgramInternalContractName?.PoseidonProvingRequest) { + + let log_n_instances = inputs.get("log_n_instances"); + if (!log_n_instances) return; + const prove_result = prove_stark_proof_poseidon(Number(log_n_instances)); + console.log("poseidon prove_result", prove_result); + const serialised_proof_from_nostr_event = JSON.stringify(starkProof); + console.log("serialised_proof_from_nostr_event", serialised_proof_from_nostr_event); + const verify_result = verify_stark_proof_poseidon(Number(log_n_instances), serialised_proof_from_nostr_event); + console.log("verify result", verify_result); + console.log("verify message", verify_result.message); + console.log("verify success", verify_result.success); + if (verify_result?.success) { + console.log("is success verify result") + setProofStatus("verified"); + } else { + setError(verify_result?.message) + } + } + + setIsLoading(false); + setIsFetchJob(true) + } + } catch (e) { + console.log("Verify error", e); + } finally { + setIsLoading(false); + setIsFetchJob(true) + + } + }; + + const date: string | undefined = event?.created_at ? new Date(event?.created_at).toDateString() : undefined + + const params = Object.fromEntries(program?.program_params?.inputs?.entries() ?? []) + + const [form, setForm] = useState({}) + // Handle changes in form inputs + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setForm(prev => ({ + ...prev, + [name]: value + })); + console.log("form", form) + }; + + const program_params = program?.program_params; + return ( +
+ {program_params?.event_id && +

Event id: {program?.program_params?.event_id}

+ } +

{program?.program_params?.contract_name?.toString()}

+

Deployed: {program?.program_params?.contract_reached == ContractUploadType.InternalAskeladd && "Internal Program"}

+ {isLoading &&
} + + {isOpenForm && + +
+ {Object.entries(form).map(([key, value]) => ( +

{`${key}: ${value}`}

+ ))} + + {Object.entries(params).map((e, i) => { + return ( +
+

{e?.[1]}

+ +
+ ) + })} +
+ } + + {jobId && ( +
+

Job ID: {jobId}

+

Status: {proofStatus}

+ + {error &&

Error: {error}

} + {proof && ( +
+

Proof received:

+
+                                {proof}
+                            
+ {starkProof && ( +

+ Proof of work nonce: {starkProof?.commitment_scheme_proof?.proof_of_work?.nonce} +

+ )} + +
+ )} +
+ )} + + + +
+ ) +}; + +export default ProgramCard; diff --git a/askeladd-dvm-marketplace/src/app/components/description/index.tsx b/askeladd-dvm-marketplace/src/app/components/description/index.tsx new file mode 100644 index 0000000..18d0838 --- /dev/null +++ b/askeladd-dvm-marketplace/src/app/components/description/index.tsx @@ -0,0 +1,38 @@ +import { useState } from "react"; + +export const HowItWork = () => { + + const [openHowItWork, setOpenHowItWork] = useState(false); + + + return( +
setOpenHowItWork(!openHowItWork)} + className="max-w-sm cursor-pointer my-5 p-1 m-1 whitespace-pre-line break-words" + > +

How the ASKELADD DVM ZK works?

+ {!openHowItWork && + + } + {openHowItWork && + <> +
+

As an User

+

User send a JOB_REQUEST with different params on the Nostr event.

+

It can change with all STWO Prover enabled on the Marketplace

+

You need theses params on the Nostr event:

+

Inputs

+ +

Request: {JSON.stringify({ + "claim": "413300", + "log_size": "5" + })} " The input of the Program

+

Tags: {`[ + ["param", "input_name", "value"] // The input of the Program + ]`}

+
+ + + } +
+ ) +} \ No newline at end of file diff --git a/askeladd-dvm-marketplace/src/app/components/Navbar.tsx b/askeladd-dvm-marketplace/src/app/components/layout/Navbar.tsx similarity index 64% rename from askeladd-dvm-marketplace/src/app/components/Navbar.tsx rename to askeladd-dvm-marketplace/src/app/components/layout/Navbar.tsx index 7fd10d1..d9161ac 100644 --- a/askeladd-dvm-marketplace/src/app/components/Navbar.tsx +++ b/askeladd-dvm-marketplace/src/app/components/layout/Navbar.tsx @@ -18,19 +18,25 @@ const Navbar: React.FC = () => { data-collapse-toggle="navbar-sticky" type="button" className="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-sticky"> Open main menu {/* } -
setOpenHowItWork(!openHowItWork)} - className="max-w-sm cursor-pointer my-5 p-1 m-1 whitespace-pre-line" - > -

How the ASKELADD DVM ZK works?

- {!openHowItWork && - - } - {openHowItWork && - <> -
-

As an User

-

User send a JOB_REQUEST with different params on the Nostr event.

-

It can change with all STWO Prover enabled on the Marketplace

-

You need theses params on the Nostr event:

-

Inputs

- -

Request: {JSON.stringify({ - "claim": "413300", - "log_size": "5" - })} " The input of the Program

-

Tags: {`[ - ["param", "input_name", "value"] // The input of the Program - ]`}

-
- - - } -
+