@@ -5,6 +5,14 @@ import { DEFAULT_TOML_CONFIG } from '../actions/constants'
55import NoirParser from './noirParser'
66import { extractNameFromKey } from '@remix-ui/helper'
77import axios from 'axios'
8+ import JSZip from 'jszip'
9+ import { VerifierInputs } from '../types'
10+
11+ interface NoirAbi {
12+ parameters : { name : string , type : any , visibility : 'public' | 'private' } [ ]
13+ return_type ?: { visibility : 'public' | 'private' }
14+ }
15+
816export class NoirPluginClient extends PluginClient {
917 public internalEvents : EventManager
1018 public parser : NoirParser
@@ -17,7 +25,7 @@ export class NoirPluginClient extends PluginClient {
1725
1826 constructor ( ) {
1927 super ( )
20- this . methods = [ 'init' , 'parse' , 'compile' ]
28+ this . methods = [ 'init' , 'parse' , 'compile' , 'generateProof' ]
2129 createClient ( this )
2230 this . internalEvents = new EventManager ( )
2331 this . parser = new NoirParser ( )
@@ -71,6 +79,10 @@ export class NoirPluginClient extends PluginClient {
7179 }
7280 }
7381
82+ private bytesToHex ( bytes : Uint8Array ) : string {
83+ return Array . from ( bytes , byte => byte . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
84+ }
85+
7486 generateRequestID ( ) : string {
7587 const timestamp = Math . floor ( Date . now ( ) / 1000 )
7688 const random = Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 )
@@ -151,9 +163,10 @@ export class NoirPluginClient extends PluginClient {
151163 const { compiledJson, proverToml } = response . data
152164
153165 const buildPath = projectRoot === '/' ? 'build' : `${ projectRoot } /build`
154-
155166 this . call ( 'fileManager' , 'writeFile' , `${ buildPath } /program.json` , compiledJson )
156- this . call ( 'fileManager' , 'writeFile' , `${ buildPath } /prover.toml` , proverToml )
167+
168+ const proverTomlPath = projectRoot === '/' ? 'Prover.toml' : `${ projectRoot } /Prover.toml`
169+ this . call ( 'fileManager' , 'writeFile' , proverTomlPath , proverToml )
157170
158171 this . internalEvents . emit ( 'noir_compiling_done' )
159172 this . emit ( 'statusChanged' , { key : 'succeed' , title : 'Noir circuit compiled successfully' , type : 'success' } )
@@ -169,6 +182,140 @@ export class NoirPluginClient extends PluginClient {
169182 }
170183 }
171184
185+ async generateProof ( path : string ) : Promise < void > {
186+ const requestID = this . generateRequestID ( )
187+ console . log ( `[${ requestID } ] New proof generation request for: ${ path } ` )
188+
189+ // this.internalEvents.emit('noir_proofing_start')
190+ this . internalEvents . emit ( 'noir_proofing_start' )
191+ this . emit ( 'statusChanged' , { key : 'loading' , title : 'Generating Proof...' , type : 'info' } )
192+ this . call ( 'terminal' , 'log' , { type : 'log' , value : 'Generating proof for ' + path } )
193+
194+ let projectRoot : string | null = null
195+
196+ try {
197+ if ( this . ws . readyState !== WebSocket . OPEN ) {
198+ throw new Error ( 'WebSocket connection not open. Cannot generate proof.' )
199+ }
200+
201+ projectRoot = await this . findProjectRoot ( path )
202+ if ( projectRoot === null ) {
203+ throw new Error ( `Invalid project structure for '${ path } '. Could not find project root.` )
204+ }
205+
206+ // @ts -ignore
207+ const zippedProject : Blob = await this . call ( 'fileManager' , 'download' , projectRoot , false )
208+ const formData = new FormData ( )
209+ formData . append ( 'file' , zippedProject , `${ extractNameFromKey ( path ) } .zip` )
210+
211+ this . ws . send ( JSON . stringify ( { requestId : requestID } ) )
212+
213+ // @ts -ignore
214+ const response = await axios . post ( `${ BASE_URL } /generate-proof-with-verifier?requestId=${ requestID } ` , formData , {
215+ responseType : 'blob'
216+ } )
217+
218+ if ( response . status !== 200 ) {
219+ try {
220+ const errorJson = JSON . parse ( await response . data . text ( ) )
221+ throw new Error ( errorJson . error || `Backend returned status ${ response . status } ` )
222+ } catch ( parseError ) {
223+ throw new Error ( `Backend returned status ${ response . status } : ${ response . statusText } ` )
224+ }
225+ }
226+
227+ const receivedBlob = response . data
228+ this . call ( 'terminal' , 'log' , { type : 'log' , value : 'Received proof artifacts. Extracting files...' } )
229+
230+ const zip = await JSZip . loadAsync ( receivedBlob )
231+ const buildPath = projectRoot === '/' ? 'build' : `${ projectRoot } /build`
232+
233+ const filesToSave = {
234+ 'proof' : { path : `${ buildPath } /proof` , type : 'hex' } ,
235+ 'vk' : { path : `${ buildPath } /vk` , type : 'hex' } ,
236+ 'verifier/solidity/Verifier.sol' : { path : 'contracts/Verifier.sol' , type : 'string' } ,
237+ 'program.json' : { path : `${ buildPath } /program.json` , type : 'string' } ,
238+ }
239+
240+ for ( const [ zipPath , info ] of Object . entries ( filesToSave ) ) {
241+ const file = zip . file ( zipPath )
242+ if ( file ) {
243+ let content : string ;
244+ if ( info . type === 'hex' ) {
245+ const bytes = await file . async ( 'uint8array' ) ;
246+ content = this . bytesToHex ( bytes ) ;
247+ } else {
248+ content = await file . async ( 'string' ) ;
249+ }
250+ await this . call ( 'fileManager' , 'writeFile' , info . path , content )
251+ // @ts -ignore
252+ this . call ( 'terminal' , 'log' , { type : 'log' , value : `Wrote artifact: ${ info . path } ` } )
253+ }
254+ }
255+
256+
257+ this . call ( 'terminal' , 'log' , { type : 'log' , value : 'Formatting Verifier.sol inputs...' } )
258+
259+ const proofFile = zip . file ( 'formatted_proof.txt' ) ;
260+ const inputsFile = zip . file ( 'formatted_public_inputs.json' ) ;
261+
262+ if ( ! proofFile || ! inputsFile ) {
263+ throw new Error ( "Formatted proof or public inputs not found in zip response from backend." ) ;
264+ }
265+
266+ const formattedProof = await proofFile . async ( 'string' ) ;
267+ const formattedPublicInputsStr = await inputsFile . async ( 'string' ) ;
268+ const formattedPublicInputs = JSON . parse ( formattedPublicInputsStr ) ;
269+
270+ const verifierInputs : VerifierInputs = {
271+ proof : formattedProof ,
272+ publicInputs : formattedPublicInputs
273+ }
274+
275+ this . internalEvents . emit ( 'noir_proofing_done' , verifierInputs )
276+
277+ this . emit ( 'statusChanged' , { key : 'succeed' , title : 'Proof generated successfully' , type : 'success' } )
278+ this . call ( 'terminal' , 'log' , { type : 'log' , value : 'Proof generation and file extraction complete.' } )
279+ // this.internalEvents.emit('noir_proofing_done')
280+
281+ } catch ( e ) {
282+ console . error ( `[${ requestID } ] Proof generation failed:` , e )
283+ let errorMsg = e . message || 'Unknown error during proof generation'
284+
285+ if ( e . response && e . response . data ) {
286+ try {
287+ let errorData = e . response . data
288+
289+ if ( e . response . data instanceof Blob ) {
290+ const errorText = await e . response . data . text ( )
291+ errorData = JSON . parse ( errorText )
292+ }
293+
294+ if ( errorData . error ) {
295+ errorMsg = errorData . error
296+ } else if ( typeof errorData === 'string' ) {
297+ errorMsg = errorData
298+ }
299+ } catch ( parseError ) {
300+ console . error ( 'Failed to parse backend error response:' , parseError )
301+ errorMsg = e . response . statusText || e . message
302+ }
303+ }
304+ this . internalEvents . emit ( 'noir_proofing_errored' , e )
305+ this . call ( 'terminal' , 'log' , { type : 'error' , value : errorMsg } )
306+ // this.internalEvents.emit('noir_proofing_errored', new Error(errorMsg))
307+
308+ if ( projectRoot !== null ) {
309+ try {
310+ const buildPath = projectRoot === '/' ? 'build' : `${ projectRoot } /build`
311+ await this . call ( 'fileManager' , 'writeFile' , `${ buildPath } /proof_error.log` , errorMsg )
312+ } catch ( logError ) {
313+ console . error ( 'Failed to write error log file:' , logError )
314+ }
315+ }
316+ }
317+ }
318+
172319 async parse ( path : string , content ?: string ) : Promise < void > {
173320 if ( ! content ) content = await this . call ( 'fileManager' , 'readFile' , path )
174321 const result = this . parser . parseNoirCode ( content )
0 commit comments