diff --git a/.env.template b/.env.template index 06b12b5..f197b1d 100644 --- a/.env.template +++ b/.env.template @@ -5,4 +5,8 @@ SEED_PHRASE_4= SEED_PHRASE_5= SEED_PHRASE_6= SEED_PHRASE_7= +SEED_PHRASE_8= +SEED_PHRASE_9= +SEED_PHRASE_10= +SEED_PHRASE_11= QA_SLACK_WEBHOOK_URL= \ No newline at end of file diff --git a/.github/workflows/ci-build-and-test.yml b/.github/workflows/ci-build-and-test.yml index c6028c3..818f9b3 100644 --- a/.github/workflows/ci-build-and-test.yml +++ b/.github/workflows/ci-build-and-test.yml @@ -17,6 +17,9 @@ jobs: SEED_PHRASE_6: ${{ secrets.SEED_PHRASE_6 }} SEED_PHRASE_7: ${{ secrets.SEED_PHRASE_7 }} SEED_PHRASE_8: ${{ secrets.SEED_PHRASE_8 }} + SEED_PHRASE_9: ${{ secrets.SEED_PHRASE_9 }} + SEED_PHRASE_10: ${{ secrets.SEED_PHRASE_10 }} + SEED_PHRASE_11: ${{ secrets.SEED_PHRASE_11 }} steps: - name: Checkout code diff --git a/.secrets b/.secrets index b57e511..e19330c 100644 --- a/.secrets +++ b/.secrets @@ -6,6 +6,9 @@ QA_SEED_PHRASE_5=_seed_phrase_5_ QA_SEED_PHRASE_6=_seed_phrase_6_ QA_SEED_PHRASE_7=_seed_phrase_7_ QA_SEED_PHRASE_8=_seed_phrase_8_ +QA_SEED_PHRASE_9=_seed_phrase_9_ +QA_SEED_PHRASE_10=_seed_phrase_10_ +QA_SEED_PHRASE_11=_seed_phrase_11_ QA_SLACK_WEBHOOK_URL=_webhook_url_ RELEASES_PROD_SLACK_WEBHOOK_URL=_releases_prod_webhook_url_ NPM_TOKEN=_npm_token_ \ No newline at end of file diff --git a/DOCS.md b/DOCS.md index 070e5b5..b6ad084 100644 --- a/DOCS.md +++ b/DOCS.md @@ -7,6 +7,7 @@ Currently the following proof verifiers are supported: * FFlonk * Groth16 (BN128, BN254, BLS12-381 elliptic curves) + * Note - Must include `Library` and `CurveType` e.g. `.groth16(Library.gnark, CurveType.bn128)` * Risc0 * Ultraplonk * Space and Time @@ -110,12 +111,16 @@ const { events, transactionResult } = await session 2. Frontend after establishing a session with `withWallet()` ```typescript -const { events, transactionResult } = await session.verify() - .groth16() - .execute({ proofData: { +import {CurveType} from "./index"; + +const {events, transactionResult} = await session.verify() + .groth16(Library.snarkjs, CurveType.bn128) + .execute({ + proofData: { vk: vk, proof: proof, - publicSignals: publicSignals } + publicSignals: publicSignals + } }); events.on('ErrorEvent', (eventData) => { @@ -189,7 +194,7 @@ To await the final result of the transaction, use the transactionResult promise. ```typescript const { events, transactionResult } = await session.verify() - .groth16() + .groth16(Library.gnark, CurveType.bls12381) .execute({ proofData: { vk: vk, proof: proof, diff --git a/README.md b/README.md index d385632..faf1443 100755 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ The `zkverifyjs` package is a TypeScript library designed to facilitate sending Currently the following proof verifiers are supported: - FFlonk - Groth16 (BN128, BN254, BLS12-381 elliptic curves) + - Note - Must include `Library` and `CurveType` e.g. `.groth16(Library.gnark, CurveType.bn128)` - Risc0 - Ultraplonk - Space and Time @@ -126,13 +127,18 @@ const { events, transactionResult } = await session ``` 2. Frontend after establishing a session with `withWallet()` + ```typescript -const { events, transactionResult } = await session.verify() - .groth16() - .execute({ proofData: { +import {CurveType} from "./index"; + +const {events, transactionResult} = await session.verify() + .groth16(Library.snarkjs, CurveType.bn128) + .execute({ + proofData: { vk: vk, proof: proof, - publicSignals: publicSignals } + publicSignals: publicSignals + } }); events.on('ErrorEvent', (eventData) => { @@ -206,7 +212,7 @@ To await the final result of the transaction, use the transactionResult promise. ```typescript const { events, transactionResult } = await session.verify() - .groth16() + .groth16(Library.gnark, CurveType.bls12381) .execute({ proofData: { vk: vk, proof: proof, diff --git a/src/api/format/index.test.ts b/src/api/format/index.test.ts index f8a8824..b1de0ac 100644 --- a/src/api/format/index.test.ts +++ b/src/api/format/index.test.ts @@ -1,6 +1,6 @@ import { ProofProcessor } from '../../types'; import { getProofProcessor } from '../../utils/helpers'; -import { ProofType } from '../../config'; +import { ProofType, Library, CurveType } from '../../config'; import { format } from './index'; jest.mock('../../utils/helpers', () => ({ @@ -10,6 +10,12 @@ jest.mock('../../utils/helpers', () => ({ describe('format', () => { let mockProcessor: ProofProcessor; + const proofOptions = { + proofType: ProofType.groth16, + library: Library.snarkjs, + curve: CurveType.bls12381, + }; + beforeEach(() => { mockProcessor = { formatProof: jest.fn().mockReturnValue('formattedProof'), @@ -26,29 +32,24 @@ describe('format', () => { it('should throw an error if unsupported proofType is provided', () => { (getProofProcessor as jest.Mock).mockReturnValue(null); - expect(() => format(ProofType.groth16, 'proof', 'signals', 'vk')).toThrow( - 'Unsupported proof type: groth16', - ); + expect(() => + format( + { ...proofOptions, proofType: 'unsupportedType' as any }, + 'proof', + 'signals', + 'vk', + ), + ).toThrow('Unsupported proof type: unsupportedType'); }); it('should throw an error if proof, public signals, or verification key is null, undefined, or empty', () => { - expect(() => format(ProofType.groth16, null, 'signals', 'vk')).toThrow( - 'groth16: Proof is required and cannot be null, undefined, or an empty string.', - ); - expect(() => format(ProofType.groth16, 'proof', null, 'vk')).toThrow( - 'groth16: Public signals are required and cannot be null, undefined, or an empty string.', - ); - expect(() => format(ProofType.groth16, 'proof', 'signals', null)).toThrow( - 'groth16: Verification Key must be provided.', - ); - - expect(() => format(ProofType.groth16, '', 'signals', 'vk')).toThrow( + expect(() => format(proofOptions, null, 'signals', 'vk')).toThrow( 'groth16: Proof is required and cannot be null, undefined, or an empty string.', ); - expect(() => format(ProofType.groth16, 'proof', '', 'vk')).toThrow( + expect(() => format(proofOptions, 'proof', null, 'vk')).toThrow( 'groth16: Public signals are required and cannot be null, undefined, or an empty string.', ); - expect(() => format(ProofType.groth16, 'proof', 'signals', '')).toThrow( + expect(() => format(proofOptions, 'proof', 'signals', null)).toThrow( 'groth16: Verification Key must be provided.', ); }); @@ -58,7 +59,7 @@ describe('format', () => { throw new Error('Proof formatting error'); }); - expect(() => format(ProofType.groth16, 'proof', 'signals', 'vk')).toThrow( + expect(() => format(proofOptions, 'proof', 'signals', 'vk')).toThrow( 'Failed to format groth16 proof: Proof formatting error. Proof snippet: "proof..."', ); }); @@ -68,7 +69,7 @@ describe('format', () => { throw new Error('Public signals formatting error'); }); - expect(() => format(ProofType.groth16, 'proof', 'signals', 'vk')).toThrow( + expect(() => format(proofOptions, 'proof', 'signals', 'vk')).toThrow( 'Failed to format groth16 public signals: Public signals formatting error. Public signals snippet: "signals..."', ); }); @@ -78,7 +79,7 @@ describe('format', () => { throw new Error('Verification key formatting error'); }); - expect(() => format(ProofType.groth16, 'proof', 'signals', 'vk')).toThrow( + expect(() => format(proofOptions, 'proof', 'signals', 'vk')).toThrow( 'Failed to format groth16 verification key: Verification key formatting error. Verification key snippet: "vk..."', ); }); @@ -88,17 +89,34 @@ describe('format', () => { throw 'Non-Error failure in proof'; }); - expect(() => format(ProofType.groth16, 'proof', 'signals', 'vk')).toThrow( + expect(() => format(proofOptions, 'proof', 'signals', 'vk')).toThrow( 'Failed to format groth16 proof: Unknown error. Proof snippet: "proof..."', ); }); + it('should return formatted values for non-registered verification key', () => { + const result = format(proofOptions, 'proof', 'signals', 'vk'); + + expect(result.formattedVk).toEqual({ Vk: 'formattedVk' }); + expect(result.formattedProof).toBe('formattedProof'); + expect(result.formattedPubs).toBe('formattedPubs'); + expect(mockProcessor.formatProof).toHaveBeenCalledWith( + 'proof', + proofOptions, + ); + expect(mockProcessor.formatPubs).toHaveBeenCalledWith( + 'signals', + proofOptions, + ); + expect(mockProcessor.formatVk).toHaveBeenCalledWith('vk', proofOptions); + }); + it('should throw a generic error if formatting public signals fails with a non-Error type', () => { (mockProcessor.formatPubs as jest.Mock).mockImplementation(() => { throw 'Non-Error failure in public signals'; }); - expect(() => format(ProofType.groth16, 'proof', 'signals', 'vk')).toThrow( + expect(() => format(proofOptions, 'proof', 'signals', 'vk')).toThrow( 'Failed to format groth16 public signals: Unknown error. Public signals snippet: "signals..."', ); }); @@ -108,41 +126,19 @@ describe('format', () => { throw 'Non-Error failure in verification key'; }); - expect(() => format(ProofType.groth16, 'proof', 'signals', 'vk')).toThrow( + expect(() => format(proofOptions, 'proof', 'signals', 'vk')).toThrow( 'Failed to format groth16 verification key: Unknown error. Verification key snippet: "vk..."', ); }); - it('should return formatted values for non-registered verification key', () => { - const result = format(ProofType.groth16, 'proof', 'signals', 'vk'); - - expect(result.formattedVk).toEqual({ Vk: 'formattedVk' }); - expect(result.formattedProof).toBe('formattedProof'); - expect(result.formattedPubs).toBe('formattedPubs'); - expect(mockProcessor.formatProof).toHaveBeenCalledWith('proof'); - expect(mockProcessor.formatPubs).toHaveBeenCalledWith('signals'); - expect(mockProcessor.formatVk).toHaveBeenCalledWith('vk'); - }); - - it('should return formatted values for registered verification key', () => { - const result = format(ProofType.groth16, 'proof', 'signals', 'vk', true); - - expect(result.formattedVk).toEqual({ Hash: 'vk' }); - expect(result.formattedProof).toBe('formattedProof'); - expect(result.formattedPubs).toBe('formattedPubs'); - expect(mockProcessor.formatProof).toHaveBeenCalledWith('proof'); - expect(mockProcessor.formatPubs).toHaveBeenCalledWith('signals'); - expect(mockProcessor.formatVk).not.toHaveBeenCalled(); - }); - - it('should format public signals correctly when publicSignals is an array', () => { - (mockProcessor.formatPubs as jest.Mock).mockImplementation(() => { - throw new Error('Array public signals formatting error'); + it('should handle non-string vk object correctly', () => { + (mockProcessor.formatVk as jest.Mock).mockImplementation(() => { + throw new Error('Object vk formatting error'); }); expect(() => - format(ProofType.groth16, 'proof', ['signal1', 'signal2'], 'vk'), + format(proofOptions, 'proof', 'signals', { vkField: 'vkValue' }), ).toThrow( - 'Failed to format groth16 public signals: Array public signals formatting error. Public signals snippet: "["signal1","signal2"]..."', + 'Failed to format groth16 verification key: Object vk formatting error. Verification key snippet: "{"vkField":"vkValue"}..."', ); }); @@ -151,31 +147,63 @@ describe('format', () => { throw new Error('Non-string proof error'); }); expect(() => - format(ProofType.groth16, { field: 'value' }, 'signals', 'vk'), + format(proofOptions, { field: 'value' }, 'signals', 'vk'), ).toThrow( 'Failed to format groth16 proof: Non-string proof error. Proof snippet: "{"field":"value"}..."', ); }); - it('should handle non-array, non-string publicSignals object correctly', () => { + it('should format public signals correctly when publicSignals is an array', () => { (mockProcessor.formatPubs as jest.Mock).mockImplementation(() => { - throw new Error('Object public signals error'); + throw new Error('Array public signals formatting error'); }); expect(() => - format(ProofType.groth16, 'proof', { key: 'value' }, 'vk'), + format(proofOptions, 'proof', ['signal1', 'signal2'], 'vk'), ).toThrow( - 'Failed to format groth16 public signals: Object public signals error. Public signals snippet: "[object Object]..."', + 'Failed to format groth16 public signals: Array public signals formatting error. Public signals snippet: "["signal1","signal2"]..."', ); }); - it('should handle non-string vk object correctly', () => { - (mockProcessor.formatVk as jest.Mock).mockImplementation(() => { - throw new Error('Object vk formatting error'); + it('should handle non-array, non-string publicSignals object correctly', () => { + (mockProcessor.formatPubs as jest.Mock).mockImplementation(() => { + throw new Error('Object public signals error'); }); - expect(() => - format(ProofType.groth16, 'proof', 'signals', { vkField: 'vkValue' }), - ).toThrow( - 'Failed to format groth16 verification key: Object vk formatting error. Verification key snippet: "{"vkField":"vkValue"}..."', + expect(() => format(proofOptions, 'proof', { key: 'value' }, 'vk')).toThrow( + 'Failed to format groth16 public signals: Object public signals error. Public signals snippet: "[object Object]..."', + ); + }); + + it('should return formatted values for registered verification key', () => { + const result = format(proofOptions, 'proof', 'signals', 'vk', true); + + expect(result.formattedVk).toEqual({ Hash: 'vk' }); + expect(result.formattedProof).toBe('formattedProof'); + expect(result.formattedPubs).toBe('formattedPubs'); + expect(mockProcessor.formatProof).toHaveBeenCalledWith( + 'proof', + proofOptions, + ); + expect(mockProcessor.formatPubs).toHaveBeenCalledWith( + 'signals', + proofOptions, ); + expect(mockProcessor.formatVk).not.toHaveBeenCalled(); + }); + + it('should handle arrays in publicSignals correctly', () => { + const result = format(proofOptions, 'proof', ['signal1', 'signal2'], 'vk'); + + expect(result.formattedPubs).toBe('formattedPubs'); + expect(mockProcessor.formatPubs).toHaveBeenCalledWith( + ['signal1', 'signal2'], + proofOptions, + ); + }); + + it('should handle non-string verification key correctly', () => { + const vkObject = { key: 'value' }; + const result = format(proofOptions, 'proof', 'signals', vkObject); + + expect(mockProcessor.formatVk).toHaveBeenCalledWith(vkObject, proofOptions); }); }); diff --git a/src/api/format/index.ts b/src/api/format/index.ts index 50d849d..e4dc2a7 100644 --- a/src/api/format/index.ts +++ b/src/api/format/index.ts @@ -1,24 +1,24 @@ import { ProofProcessor } from '../../types'; import { getProofProcessor } from '../../utils/helpers'; -import { ProofType } from '../../config'; import { FormattedProofData } from './types'; +import { ProofOptions } from '../../session/types'; export function format( - proofType: ProofType, + options: ProofOptions, proof: unknown, publicSignals: unknown, vk: unknown, registeredVk?: boolean, ): FormattedProofData { - const processor: ProofProcessor = getProofProcessor(proofType); + const processor: ProofProcessor = getProofProcessor(options.proofType); if (!processor) { - throw new Error(`Unsupported proof type: ${proofType}`); + throw new Error(`Unsupported proof type: ${options.proofType}`); } if (proof === null || proof === undefined || proof === '') { throw new Error( - `${proofType}: Proof is required and cannot be null, undefined, or an empty string.`, + `${options.proofType}: Proof is required and cannot be null, undefined, or an empty string.`, ); } if ( @@ -27,36 +27,36 @@ export function format( publicSignals === '' ) { throw new Error( - `${proofType}: Public signals are required and cannot be null, undefined, or an empty string.`, + `${options.proofType}: Public signals are required and cannot be null, undefined, or an empty string.`, ); } if (vk === null || vk === undefined || vk === '') { - throw new Error(`${proofType}: Verification Key must be provided.`); + throw new Error(`${options.proofType}: Verification Key must be provided.`); } let formattedProof, formattedPubs, formattedVk; try { - formattedProof = processor.formatProof(proof); + formattedProof = processor.formatProof(proof, options); } catch (error) { const proofSnippet = typeof proof === 'string' ? proof.slice(0, 50) : JSON.stringify(proof).slice(0, 50); throw new Error( - `Failed to format ${proofType} proof: ${error instanceof Error ? error.message : 'Unknown error'}. Proof snippet: "${proofSnippet}..."`, + `Failed to format ${options.proofType} proof: ${error instanceof Error ? error.message : 'Unknown error'}. Proof snippet: "${proofSnippet}..."`, ); } try { - formattedPubs = processor.formatPubs(publicSignals); + formattedPubs = processor.formatPubs(publicSignals, options); } catch (error) { const pubsSnippet = Array.isArray(publicSignals) ? JSON.stringify(publicSignals).slice(0, 50) : publicSignals?.toString().slice(0, 50); throw new Error( - `Failed to format ${proofType} public signals: ${error instanceof Error ? error.message : 'Unknown error'}. Public signals snippet: "${pubsSnippet}..."`, + `Failed to format ${options.proofType} public signals: ${error instanceof Error ? error.message : 'Unknown error'}. Public signals snippet: "${pubsSnippet}..."`, ); } @@ -64,7 +64,7 @@ export function format( if (registeredVk) { formattedVk = { Hash: vk }; } else { - formattedVk = { Vk: processor.formatVk(vk) }; + formattedVk = { Vk: processor.formatVk(vk, options) }; } } catch (error) { const vkSnippet = @@ -73,7 +73,7 @@ export function format( : JSON.stringify(vk).slice(0, 50); throw new Error( - `Failed to format ${proofType} verification key: ${error instanceof Error ? error.message : 'Unknown error'}. Verification key snippet: "${vkSnippet}..."`, + `Failed to format ${options.proofType} verification key: ${error instanceof Error ? error.message : 'Unknown error'}. Verification key snippet: "${vkSnippet}..."`, ); } diff --git a/src/api/register/index.test.ts b/src/api/register/index.test.ts index dafeae9..96a1303 100644 --- a/src/api/register/index.test.ts +++ b/src/api/register/index.test.ts @@ -5,7 +5,7 @@ import { handleTransaction } from '../../utils/transactions'; import { AccountConnection } from '../connection/types'; import { VerifyOptions } from '../../session/types'; import { TransactionType, ZkVerifyEvents } from '../../enums'; -import { ProofType } from '../../config'; +import { ProofType, Library, CurveType } from '../../config'; jest.mock('../../utils/helpers', () => ({ getProofPallet: jest.fn(), @@ -35,7 +35,13 @@ describe('registerVk', () => { account: 'mockAccount', } as unknown as AccountConnection; - mockOptions = { proofType: ProofType.fflonk } as VerifyOptions; + mockOptions = { + proofOptions: { + proofType: ProofType.fflonk, + library: Library.snarkjs, + curve: CurveType.bls12381, + }, + } as VerifyOptions; mockVerificationKey = 'mockVerificationKey'; mockProcessor = { @@ -63,9 +69,16 @@ describe('registerVk', () => { mockVerificationKey, ); - expect(getProofProcessor).toHaveBeenCalledWith(mockOptions.proofType); - expect(getProofPallet).toHaveBeenCalledWith(mockOptions.proofType); - expect(mockProcessor.formatVk).toHaveBeenCalledWith(mockVerificationKey); + expect(getProofProcessor).toHaveBeenCalledWith( + mockOptions.proofOptions.proofType, + ); + expect(getProofPallet).toHaveBeenCalledWith( + mockOptions.proofOptions.proofType, + ); + expect(mockProcessor.formatVk).toHaveBeenCalledWith( + mockVerificationKey, + mockOptions.proofOptions, + ); expect(connection.api.tx[mockPallet].registerVk).toHaveBeenCalledWith( 'formattedVk', ); @@ -88,7 +101,9 @@ describe('registerVk', () => { await expect( registerVk(connection, mockOptions, mockVerificationKey), - ).rejects.toThrow(`Unsupported proof type: ${mockOptions.proofType}`); + ).rejects.toThrow( + `Unsupported proof type: ${mockOptions.proofOptions.proofType}`, + ); }); it('should throw an error for invalid verification key', async () => { @@ -117,4 +132,14 @@ describe('registerVk', () => { await expect(transactionResult).rejects.toThrow('Transaction failed'); expect(errorHandler).toHaveBeenCalledWith(mockError); }); + + it('should throw an error if proof pallet is not found', async () => { + (getProofPallet as jest.Mock).mockReturnValue(null); + + await expect( + registerVk(connection, mockOptions, mockVerificationKey), + ).rejects.toThrow( + `Unsupported proof type: ${mockOptions.proofOptions.proofType}`, + ); + }); }); diff --git a/src/api/register/index.ts b/src/api/register/index.ts index fe0484b..b1eaccc 100644 --- a/src/api/register/index.ts +++ b/src/api/register/index.ts @@ -15,13 +15,13 @@ export async function registerVk( events: EventEmitter; transactionResult: Promise; }> { - const { proofType } = options; + const { proofOptions } = options; const emitter = new EventEmitter(); - const processor = await getProofProcessor(proofType); + const processor = await getProofProcessor(proofOptions.proofType); if (!processor) { - throw new Error(`Unsupported proof type: ${proofType}`); + throw new Error(`Unsupported proof type: ${proofOptions.proofType}`); } if (verificationKey == null || verificationKey === '') { throw new Error( @@ -29,10 +29,10 @@ export async function registerVk( ); } - const formattedVk = processor.formatVk(verificationKey); - const pallet = getProofPallet(proofType); + const formattedVk = processor.formatVk(verificationKey, proofOptions); + const pallet = getProofPallet(proofOptions.proofType); if (!pallet) { - throw new Error(`Unsupported proof type: ${proofType}`); + throw new Error(`Unsupported proof type: ${proofOptions.proofType}`); } const registerExtrinsic: SubmittableExtrinsic<'promise'> = diff --git a/src/api/verify/index.test.ts b/src/api/verify/index.test.ts index 0018bbd..9ec44f0 100644 --- a/src/api/verify/index.test.ts +++ b/src/api/verify/index.test.ts @@ -6,7 +6,7 @@ import { AccountConnection, WalletConnection } from '../connection/types'; import { VerifyOptions } from '../../session/types'; import { TransactionType, ZkVerifyEvents } from '../../enums'; import { ProofProcessor } from '../../types'; -import { ProofType } from '../../config'; +import { ProofType, Library, CurveType } from '../../config'; import { VerifyInput } from './types'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { createSubmitProofExtrinsic } from '../extrinsic'; @@ -44,7 +44,11 @@ describe('verify', () => { } as unknown as WalletConnection; mockOptions = { - proofType: ProofType.fflonk, + proofOptions: { + proofType: ProofType.fflonk, + library: Library.snarkjs, + curve: CurveType.bls12381, + }, registeredVk: false, }; @@ -60,10 +64,13 @@ describe('verify', () => { jest.clearAllMocks(); }); - it('should throw an error if proofType is not provided', async () => { + it('should throw an error if proofOptions.proofType is not provided', async () => { const invalidOptions = { ...mockOptions, - proofType: undefined, + proofOptions: { + ...mockOptions.proofOptions, + proofType: undefined as unknown as ProofType, + }, } as Partial; const input: VerifyInput = { proofData: { @@ -80,7 +87,7 @@ describe('verify', () => { emitter, input, ), - ).rejects.toThrow('Proof type is required.'); + ).rejects.toThrow('Error: Unsupported proof type: undefined'); }); it('should throw an error if unsupported proofType is provided', async () => { @@ -95,7 +102,9 @@ describe('verify', () => { await expect( verify(mockAccountConnection, mockOptions, emitter, input), - ).rejects.toThrow(`Unsupported proof type: ${mockOptions.proofType}`); + ).rejects.toThrow( + `Unsupported proof type: ${mockOptions.proofOptions.proofType}`, + ); }); it('should throw an error if proofData is missing proof or publicSignals', async () => { @@ -110,7 +119,7 @@ describe('verify', () => { }, }), ).rejects.toThrow( - `${mockOptions.proofType}: Proof is required and cannot be null, undefined, or an empty string.`, + `${mockOptions.proofOptions.proofType}: Proof is required and cannot be null, undefined, or an empty string.`, ); await expect( @@ -122,7 +131,7 @@ describe('verify', () => { }, }), ).rejects.toThrow( - `${mockOptions.proofType}: Public signals are required and cannot be null, undefined, or an empty string.`, + `${mockOptions.proofOptions.proofType}: Public signals are required and cannot be null, undefined, or an empty string.`, ); }); @@ -142,7 +151,7 @@ describe('verify', () => { await expect( verify(mockAccountConnection, mockOptions, emitter, input), ).rejects.toThrow( - `Failed to format ${mockOptions.proofType} proof: Formatting error. Proof snippet: "proof..."`, + `Failed to format ${mockOptions.proofOptions.proofType} proof: Formatting error. Proof snippet: "proof..."`, ); }); @@ -162,7 +171,7 @@ describe('verify', () => { await expect( verify(mockAccountConnection, mockOptions, emitter, input), ).rejects.toThrow( - `Failed to format ${mockOptions.proofType} public signals: Formatting error. Public signals snippet: "signals..."`, + `Failed to format ${mockOptions.proofOptions.proofType} public signals: Formatting error. Public signals snippet: "signals..."`, ); }); @@ -200,6 +209,30 @@ describe('verify', () => { ); }); + it('should throw an error and emit when transaction submission fails', async () => { + (getProofProcessor as jest.Mock).mockReturnValue(mockProcessor); + (handleTransaction as jest.Mock).mockRejectedValue( + new Error('Transaction error'), + ); + + const input: VerifyInput = { + proofData: { + proof: 'proof', + publicSignals: 'signals', + vk: 'vk', + }, + }; + + const errorListener = jest.fn(); + emitter.on(ZkVerifyEvents.ErrorEvent, errorListener); + + await expect( + verify(mockAccountConnection, mockOptions, emitter, input), + ).rejects.toThrow('Transaction error'); + + expect(errorListener).toHaveBeenCalledWith(new Error('Transaction error')); + }); + it('should handle the transaction with WalletConnection when extrinsic is provided', async () => { const mockExtrinsic = {} as SubmittableExtrinsic<'promise'>; const input: VerifyInput = { extrinsic: mockExtrinsic }; diff --git a/src/api/verify/index.ts b/src/api/verify/index.ts index e1e65a6..dd232f4 100644 --- a/src/api/verify/index.ts +++ b/src/api/verify/index.ts @@ -18,10 +18,6 @@ export const verify = async ( input: VerifyInput, ): Promise => { try { - if (!options.proofType) { - throw new Error('Proof type is required.'); - } - const { api } = connection; let transaction: SubmittableExtrinsic<'promise'>; @@ -29,7 +25,7 @@ export const verify = async ( const { proof, publicSignals, vk } = input.proofData as ProofData; const formattedProofData: FormattedProofData = format( - options.proofType, + options.proofOptions, proof, publicSignals, vk, @@ -38,7 +34,7 @@ export const verify = async ( transaction = createSubmitProofExtrinsic( api, - options.proofType, + options.proofOptions.proofType, formattedProofData, ); } else if ('extrinsic' in input && input.extrinsic) { diff --git a/src/config/index.ts b/src/config/index.ts index bdb071e..e8e795a 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -22,7 +22,12 @@ export enum ProofType { // ADD_NEW_PROOF_TYPE } -export enum Groth16CurveType { +export enum Library { + snarkjs = 'snarkjs', + gnark = 'gnark', +} + +export enum CurveType { bn128 = 'bn128', bn254 = 'bn254', bls12381 = 'bls12381', @@ -31,28 +36,40 @@ export enum Groth16CurveType { interface ProofConfig { pallet: string; processor: ProofProcessor; + requiresLibrary?: boolean; + requiresCurve?: boolean; } export const proofConfigurations: Record = { [ProofType.fflonk]: { pallet: 'settlementFFlonkPallet', processor: FflonkProcessor, + requiresLibrary: false, + requiresCurve: false, }, [ProofType.groth16]: { pallet: 'settlementGroth16Pallet', processor: Groth16Processor, + requiresLibrary: true, + requiresCurve: true, }, [ProofType.risc0]: { pallet: 'settlementRisc0Pallet', processor: Risc0Processor, + requiresLibrary: false, + requiresCurve: false, }, [ProofType.ultraplonk]: { pallet: 'settlementUltraplonkPallet', processor: UltraPlonkProcessor, + requiresLibrary: false, + requiresCurve: false, }, [ProofType.proofofsql]: { pallet: 'settlementProofOfSqlPallet', processor: ProofOfSqlProcessor, + requiresLibrary: false, + requiresCurve: false, }, // ADD_NEW_PROOF_TYPE }; diff --git a/src/index.ts b/src/index.ts index 1945ece..52035fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,11 @@ export { zkVerifySession } from './session'; export { + ProofOptions, VerifyOptions, WalletOptions, zkVerifySessionOptions, } from './session/types'; -export { ProofType, SupportedNetwork, Groth16CurveType } from './config'; +export { ProofType, SupportedNetwork, Library, CurveType } from './config'; export { ZkVerifyEvents, TransactionStatus, TransactionType } from './enums'; export { ProofData, diff --git a/src/proofTypes/groth16/formatter/gnark/index.ts b/src/proofTypes/groth16/formatter/gnark/index.ts new file mode 100644 index 0000000..88914c5 --- /dev/null +++ b/src/proofTypes/groth16/formatter/gnark/index.ts @@ -0,0 +1,150 @@ +import { + Groth16VerificationKey, + Groth16VerificationKeyInput, + Proof, + ProofInput, +} from '../../types'; +import { ProofOptions } from '../../../../session/types'; +import { + extractCurve, + formatG1Point, + formatG2Point, + formatPublicSignals, + getEndianess, + unstringifyBigInts, +} from '../utils'; + +/** + * Formats zk-SNARK proof data for Groth16 using Gnark. + * + * @param {ProofInput} proof - Raw proof data. + * @param {ProofOptions} options - Proof options containing curve information. + * @returns {Proof} - Formatted proof data. + */ +export const formatProof = ( + proof: ProofInput, + options: ProofOptions, +): Proof => { + try { + if (typeof proof !== 'object' || proof === null) { + throw new Error('Invalid proof format: Expected an object.'); + } + + const proofData = unstringifyBigInts(proof) as { + Ar: { X: string; Y: string }; + Bs: { + X: { A0: string; A1: string }; + Y: { A0: string; A1: string }; + }; + Krs: { X: string; Y: string }; + }; + + const curve = extractCurve(options.curve!); + const endianess = getEndianess(curve); + + return { + curve, + proof: { + a: formatG1Point([proofData.Ar.X, proofData.Ar.Y], endianess), + b: formatG2Point( + [ + [proofData.Bs.X.A0, proofData.Bs.X.A1], + [proofData.Bs.Y.A0, proofData.Bs.Y.A1], + ], + endianess, + curve, + ), + c: formatG1Point([proofData.Krs.X, proofData.Krs.Y], endianess), + }, + }; + } catch (error) { + const proofSnippet = JSON.stringify(proof).slice(0, 50); + throw new Error( + `Failed to format ${options.proofType} proof: ${ + error instanceof Error ? error.message : 'Unknown error' + }. Proof snippet: "${proofSnippet}..."`, + ); + } +}; + +/** + * Formats verification key for Groth16 zk-SNARK proof using Gnark. + * + * @param {Groth16VerificationKeyInput} vk - Raw verification key data. + * @param {ProofOptions} options - Proof options containing curve information. + * @returns {Groth16VerificationKey} - Formatted verification key. + */ +export const formatVk = ( + vk: Groth16VerificationKeyInput, + options: ProofOptions, +): Groth16VerificationKey => { + try { + if (typeof vk !== 'object' || vk === null) { + throw new Error('Invalid verification key format: Expected an object.'); + } + + const vkData = unstringifyBigInts(vk) as { + G1: { Alpha: { X: string; Y: string }; K: { X: string; Y: string }[] }; + G2: { + Beta: { X: { A0: string; A1: string }; Y: { A0: string; A1: string } }; + Gamma: { X: { A0: string; A1: string }; Y: { A0: string; A1: string } }; + Delta: { X: { A0: string; A1: string }; Y: { A0: string; A1: string } }; + }; + }; + + const curve = extractCurve(options.curve!); + const endianess = getEndianess(curve); + + return { + curve, + alpha_g1: formatG1Point( + [vkData.G1.Alpha.X, vkData.G1.Alpha.Y], + endianess, + ), + beta_g2: formatG2Point( + [ + [vkData.G2.Beta.X.A0, vkData.G2.Beta.X.A1], + [vkData.G2.Beta.Y.A0, vkData.G2.Beta.Y.A1], + ], + endianess, + curve, + ), + gamma_g2: formatG2Point( + [ + [vkData.G2.Gamma.X.A0, vkData.G2.Gamma.X.A1], + [vkData.G2.Gamma.Y.A0, vkData.G2.Gamma.Y.A1], + ], + endianess, + curve, + ), + delta_g2: formatG2Point( + [ + [vkData.G2.Delta.X.A0, vkData.G2.Delta.X.A1], + [vkData.G2.Delta.Y.A0, vkData.G2.Delta.Y.A1], + ], + endianess, + curve, + ), + gamma_abc_g1: vkData.G1.K.map((point) => + formatG1Point([point.X, point.Y], endianess), + ), + }; + } catch (error) { + const vkSnippet = JSON.stringify(vk).slice(0, 50); + throw new Error( + `Failed to format ${options.proofType} verification key: ${ + error instanceof Error ? error.message : 'Unknown error' + }. VK snippet: "${vkSnippet}..."`, + ); + } +}; + +/** + * Formats an array of public signals. + * + * @param {string[]} pubs - Array of public signals. + * @returns {string[]} - Formatted public signals. + */ +export const formatPubs = (pubs: string[]): string[] => { + return formatPublicSignals(pubs); +}; diff --git a/src/proofTypes/groth16/formatter/index.ts b/src/proofTypes/groth16/formatter/index.ts deleted file mode 100644 index d5a88a9..0000000 --- a/src/proofTypes/groth16/formatter/index.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { - Groth16VerificationKey, - Groth16VerificationKeyInput, - ProofInput, -} from '../types'; -import { Proof } from '../types'; - -/** - * Recursively converts numeric strings and hexadecimal strings in an object, array, or string - * to `bigint`. Handles nested arrays and objects. - * - * @param {unknown} o - The input, which can be a string, array, or object containing numeric or - * hexadecimal string values. - * @returns {unknown} - The transformed input where all numeric and hexadecimal strings are - * converted to `bigint`. Other values remain unchanged. - * - * The function performs the following transformations: - * - If the input is a string containing only digits or a hexadecimal string (starting with `0x`), - * it converts the string to a `bigint`. - * - If the input is an array, it recursively applies the same logic to each element. - * - If the input is an object, it recursively applies the transformation to each property value. - * - If the input is none of the above, it returns the input unchanged. - */ -const unstringifyBigInts = (o: unknown): unknown => { - if (typeof o === 'string' && /^[0-9]+$/.test(o)) return BigInt(o); - if (typeof o === 'string' && /^0x[0-9a-fA-F]+$/.test(o)) return BigInt(o); - if (Array.isArray(o)) return o.map(unstringifyBigInts); - if (typeof o === 'object' && o !== null) { - const result: Record = {}; - for (const key in o) { - if (Object.prototype.hasOwnProperty.call(o, key)) { - result[key] = unstringifyBigInts((o as Record)[key]); - } - } - return result; - } - return o; -}; - -/** - * Formats zk-SNARK proof data for Groth16. - * - * @param {ProofInput} proof - Raw proof data. - * @returns {Proof} - Formatted proof data. - */ -export const formatProof = (proof: ProofInput): Proof => { - const proofData = unstringifyBigInts(proof) as ProofInput; - const curve = extractCurve(proofData); - const endianess = getEndianess(curve); - - return { - curve, - proof: { - a: formatG1Point(proofData.pi_a, endianess), - b: formatG2Point(proofData.pi_b, endianess, curve), - c: formatG1Point(proofData.pi_c, endianess), - }, - }; -}; - -/** - * Formats verification key for Groth16 zk-SNARK proof. - * - * @param {Groth16VerificationKeyInput} vk - Raw verification key data. - * @returns {Groth16VerificationKey} - Formatted verification key. - */ -export const formatVk = ( - vk: Groth16VerificationKeyInput, -): Groth16VerificationKey => { - const vkData = unstringifyBigInts(vk) as Groth16VerificationKeyInput; - const curve = extractCurve(vkData); - const endianess = getEndianess(curve); - - return { - curve, - alpha_g1: formatG1Point(vkData.vk_alpha_1, endianess), - beta_g2: formatG2Point(vkData.vk_beta_2, endianess, curve), - gamma_g2: formatG2Point(vkData.vk_gamma_2, endianess, curve), - delta_g2: formatG2Point(vkData.vk_delta_2, endianess, curve), - gamma_abc_g1: vkData.IC.map((x: string[]) => formatG1Point(x, endianess)), - }; -}; - -/** - * Formats an array of public signals. - * - * @param {string[]} pubs - Array of public signals. - * @returns {string[]} - Formatted public signals. - */ -export const formatPubs = (pubs: string[]): string[] => { - if (!Array.isArray(pubs) || pubs.some(() => false)) { - throw new Error( - 'Invalid public signals format: Expected an array of strings.', - ); - } - return pubs.map(formatScalar); -}; - -/** - * Extracts and normalizes curve type. - * - * @param {ProofInput | Groth16VerificationKeyInput} input - Input containing curve field. - * @returns {string} - Normalized curve type ('Bn254' or 'bls12381'). - */ -const extractCurve = (input: { curve: string }): string => { - const curve = input.curve.toLowerCase(); - if (curve === 'bn128' || curve === 'bn254') return 'bn254'; - if (curve === 'bls12381' || curve === 'bls12_381') return 'Bls12_381'; - throw new Error(`Unsupported curve: ${curve}`); -}; - -/** - * Determines endianess based on the curve type. - * - * @param {string} curve - Curve type ('Bn254' or 'Bls12381'). - * @returns {'LE' | 'BE'} - Endianess ('LE' or 'BE'). - */ -const getEndianess = (curve: string): 'LE' | 'BE' => { - return curve.toLowerCase() === 'bn254' ? 'LE' : 'BE'; -}; - -/** - * Converts bigint to a little-endian hexadecimal string. - * - * @param {bigint} value - Bigint value. - * @param {number} length - Length of resulting hex string in bytes. - * @returns {string} - Little-endian hexadecimal representation. - */ -export const toLittleEndianHex = (value: bigint, length: number): string => { - return ( - '0x' + - value - .toString(16) - .padStart(length * 2, '0') - .match(/.{1,2}/g)! - .reverse() - .join('') - ); -}; - -/** - * Converts bigint to a big-endian hexadecimal string. - * - * @param {bigint} value - Bigint value. - * @param {number} length - Length of resulting hex string in bytes. - * @returns {string} - Big-endian hexadecimal representation. - */ -const toBigEndianHex = (value: bigint, length: number): string => { - return '0x' + value.toString(16).padStart(length * 2, '0'); -}; - -/** - * Formats G1 point based on endianess. - * - * @param {string[]} point - Coordinates of the G1 point. - * @param {'LE' | 'BE'} endianess - Endianess of the curve. - * @returns {string} - Formatted G1 point as hex string. - */ -const formatG1Point = (point: string[], endianess: 'LE' | 'BE'): string => { - const [x, y] = [BigInt(point[0]), BigInt(point[1])]; - const length = endianess === 'BE' ? 48 : 32; - return endianess === 'LE' - ? toLittleEndianHex(x, length) + toLittleEndianHex(y, length).slice(2) - : toBigEndianHex(x, length) + toBigEndianHex(y, length).slice(2); -}; - -/** - * Formats G2 point based on endianess and curve type. - * - * @param {string[][]} point - Coordinates of the G2 point. - * @param {'LE' | 'BE'} endianess - Endianess of the curve. - * @param {string} curve - Curve type. - * @returns {string} - Formatted G2 point as hex string. - */ -const formatG2Point = ( - point: string[][], - endianess: 'LE' | 'BE', - curve: string, -): string => { - const [x1, x2, y1, y2] = [ - BigInt(point[0][0]), - BigInt(point[0][1]), - BigInt(point[1][0]), - BigInt(point[1][1]), - ]; - const length = endianess === 'BE' ? 48 : 32; - const formattedX = - curve === 'Bls12_381' - ? endianess === 'LE' - ? toLittleEndianHex(x2, length) + toLittleEndianHex(x1, length).slice(2) - : toBigEndianHex(x2, length) + toBigEndianHex(x1, length).slice(2) - : endianess === 'LE' - ? toLittleEndianHex(x1, length) + toLittleEndianHex(x2, length).slice(2) - : toBigEndianHex(x1, length) + toBigEndianHex(x2, length).slice(2); - const formattedY = - curve === 'Bls12_381' - ? endianess === 'LE' - ? toLittleEndianHex(y2, length) + toLittleEndianHex(y1, length).slice(2) - : toBigEndianHex(y2, length) + toBigEndianHex(y1, length).slice(2) - : endianess === 'LE' - ? toLittleEndianHex(y1, length) + toLittleEndianHex(y2, length).slice(2) - : toBigEndianHex(y1, length) + toBigEndianHex(y2, length).slice(2); - - return formattedX + formattedY.slice(2); -}; - -/** - * Formats a scalar value as little-endian hexadecimal string. - * - * @param {string} scalar - Scalar value to format. - * @returns {string} - Formatted scalar as little-endian hex. - */ -const formatScalar = (scalar: string): string => - toLittleEndianHex(BigInt(scalar), 32); diff --git a/src/proofTypes/groth16/formatter/snarkjs/index.ts b/src/proofTypes/groth16/formatter/snarkjs/index.ts new file mode 100644 index 0000000..db05353 --- /dev/null +++ b/src/proofTypes/groth16/formatter/snarkjs/index.ts @@ -0,0 +1,75 @@ +import { + Groth16VerificationKey, + Groth16VerificationKeyInput, + Proof, + ProofInput, +} from '../../types'; +import { ProofOptions } from '../../../../session/types'; +import { + extractCurve, + formatG1Point, + formatG2Point, + formatPublicSignals, + getEndianess, + unstringifyBigInts, +} from '../utils'; + +/** + * Formats zk-SNARK proof data for Groth16. + * + * @param {ProofInput} proof - Raw proof data. + * @param {ProofOptions} options - Proof options containing curve information. + * @returns {Proof} - Formatted proof data. + */ +export const formatProof = ( + proof: ProofInput, + options: ProofOptions, +): Proof => { + const proofData = unstringifyBigInts(proof) as ProofInput; + const curve = extractCurve(options.curve!); + const endianess = getEndianess(curve); + + return { + curve, + proof: { + a: formatG1Point(proofData.pi_a, endianess), + b: formatG2Point(proofData.pi_b, endianess, curve), + c: formatG1Point(proofData.pi_c, endianess), + }, + }; +}; + +/** + * Formats verification key for Groth16 zk-SNARK proof. + * + * @param {Groth16VerificationKeyInput} vk - Raw verification key data. + * @param {ProofOptions} options - Proof options containing curve information. + * @returns {Groth16VerificationKey} - Formatted verification key. + */ +export const formatVk = ( + vk: Groth16VerificationKeyInput, + options: ProofOptions, +): Groth16VerificationKey => { + const vkData = unstringifyBigInts(vk) as Groth16VerificationKeyInput; + const curve = extractCurve(options.curve!); + const endianess = getEndianess(curve); + + return { + curve, + alpha_g1: formatG1Point(vkData.vk_alpha_1, endianess), + beta_g2: formatG2Point(vkData.vk_beta_2, endianess, curve), + gamma_g2: formatG2Point(vkData.vk_gamma_2, endianess, curve), + delta_g2: formatG2Point(vkData.vk_delta_2, endianess, curve), + gamma_abc_g1: vkData.IC.map((x: string[]) => formatG1Point(x, endianess)), + }; +}; + +/** + * Formats an array of public signals. + * + * @param {string[]} pubs - Array of public signals. + * @returns {string[]} - Formatted public signals. + */ +export const formatPubs = (pubs: string[]): string[] => { + return formatPublicSignals(pubs); +}; diff --git a/src/proofTypes/groth16/formatter/utils.ts b/src/proofTypes/groth16/formatter/utils.ts new file mode 100644 index 0000000..e27c8a9 --- /dev/null +++ b/src/proofTypes/groth16/formatter/utils.ts @@ -0,0 +1,119 @@ +import { CurveType } from '../../../config'; + +/** + * Recursively converts numeric strings and hexadecimal strings in an object, array, or string + * to `bigint`. Handles nested arrays and objects. + */ +export const unstringifyBigInts = (o: unknown): unknown => { + if (typeof o === 'string' && /^[0-9]+$/.test(o)) return BigInt(o); + if (typeof o === 'string' && /^0x[0-9a-fA-F]+$/.test(o)) return BigInt(o); + if (Array.isArray(o)) return o.map(unstringifyBigInts); + if (typeof o === 'object' && o !== null) { + const result: Record = {}; + for (const key in o) { + if (Object.prototype.hasOwnProperty.call(o, key)) { + result[key] = unstringifyBigInts((o as Record)[key]); + } + } + return result; + } + return o; +}; + +/** + * Determines endianess based on the curve type. + */ +export const getEndianess = (curve: string): 'LE' | 'BE' => { + return curve.toLowerCase() === 'bn254' ? 'LE' : 'BE'; +}; + +/** + * Extracts and normalizes curve type. + */ +export const extractCurve = (curve: CurveType): string => { + if (curve === CurveType.bn128 || curve === CurveType.bn254) return 'bn254'; + if (curve === CurveType.bls12381) return 'Bls12_381'; + throw new Error(`Unsupported curve: ${curve}`); +}; + +/** + * Converts bigint to a hexadecimal string based on endianess. + */ +export const toHex = ( + value: bigint, + length: number, + endianess: 'LE' | 'BE', +): string => { + const hex = value.toString(16).padStart(length * 2, '0'); + const reversed = hex + .match(/.{1,2}/g)! + .reverse() + .join(''); + return `0x${endianess === 'LE' ? reversed : hex}`; +}; + +/** + * Formats a G1 point based on endianess and curve type. + */ +export const formatG1Point = ( + point: string[], + endianess: 'LE' | 'BE', +): string => { + const [x, y] = [BigInt(point[0]), BigInt(point[1])]; + return ( + toHex(x, endianess === 'LE' ? 32 : 48, endianess) + + toHex(y, endianess === 'LE' ? 32 : 48, endianess).slice(2) + ); +}; + +/** + * Formats a G2 point based on endianess and curve type. + */ +export const formatG2Point = ( + point: string[][], + endianess: 'LE' | 'BE', + curve: string, +): string => { + const [x1, x2, y1, y2] = [ + BigInt(point[0][0]), + BigInt(point[0][1]), + BigInt(point[1][0]), + BigInt(point[1][1]), + ]; + + const formatX = + curve === 'Bls12_381' + ? [x2.toString(), x1.toString()] // bls12381 uses (x2, x1) + : [x1.toString(), x2.toString()]; // bn254 uses (x1, x2) + + const formatY = + curve === 'Bls12_381' + ? [y2.toString(), y1.toString()] // bls12381 uses (y2, y1) + : [y1.toString(), y2.toString()]; // bn254 uses (y1, y2) + + return ( + formatG1Point(formatX, endianess) + + formatG1Point(formatY, endianess).slice(2) + ); +}; + +/** + * Formats a scalar as little-endian hexadecimal string. + */ +export const formatScalar = (scalar: string): string => + toHex(BigInt(scalar), 32, 'LE'); + +/** + * Formats an array of public signals. + * + * @param {string[]} pubs - Array of public signals. + * @returns {string[]} - Formatted public signals. + */ +export const formatPublicSignals = (pubs: string[]): string[] => { + if (!Array.isArray(pubs) || pubs.some(() => false)) { + throw new Error( + 'Invalid public signals format: Expected an array of strings.', + ); + } + return pubs.map(formatScalar); +}; diff --git a/src/proofTypes/groth16/processor/index.ts b/src/proofTypes/groth16/processor/index.ts index a3844af..c52a494 100644 --- a/src/proofTypes/groth16/processor/index.ts +++ b/src/proofTypes/groth16/processor/index.ts @@ -1,40 +1,69 @@ import { + Formatter, Groth16VerificationKey, Groth16VerificationKeyInput, Proof, ProofInput, } from '../types'; -import * as formatter from '../formatter'; import { ProofProcessor } from '../../../types'; +import { ProofOptions } from '../../../session/types'; class Groth16Processor implements ProofProcessor { /** - * Formats the zk-SNARK proof based on the curve. + * Dynamically selects the appropriate formatter module based on the provided library option. + * + * @param {ProofOptions} options - The proof options containing the library type. + * @throws {Error} If the library is unsupported or the module cannot be loaded. + * @returns {Object} The formatter module corresponding to the specified library. + */ + private getFormatter(options: ProofOptions): Formatter { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const formatter = require(`../formatter/${options.library}`) as Formatter; + return formatter; + } catch (error) { + throw new Error( + `Unsupported or missing library: ${options.library} : ${error}`, + ); + } + } + + /** + * Formats the zk-SNARK proof using the appropriate formatter for the specified library. * * @param {ProofInput} proof - The raw proof input data. - * @returns {Proof} - The formatted proof. + * @param {ProofOptions} options - The proof options containing the library and other details. + * @returns {Proof} The formatted proof data. */ - formatProof(proof: ProofInput): Proof { - return formatter.formatProof(proof); + formatProof(proof: ProofInput, options: ProofOptions): Proof { + const formatter = this.getFormatter(options); + return formatter.formatProof(proof, options); } /** - * Formats the zk-SNARK verification key based on the curve. + * Formats the verification key using the appropriate formatter for the specified library. * - * @param {Groth16VerificationKeyInput} vk - The raw verification key input. - * @returns {Groth16VerificationKey} - The formatted verification key. + * @param {Groth16VerificationKeyInput} vk - The raw verification key input data. + * @param {ProofOptions} options - The proof options containing the library and other details. + * @returns {Groth16VerificationKey} The formatted verification key. */ - formatVk(vk: Groth16VerificationKeyInput): Groth16VerificationKey { - return formatter.formatVk(vk); + formatVk( + vk: Groth16VerificationKeyInput, + options: ProofOptions, + ): Groth16VerificationKey { + const formatter = this.getFormatter(options); + return formatter.formatVk(vk, options); } /** - * Formats the public inputs based on the curve. + * Formats the public inputs using the appropriate formatter for the specified library. * - * @param {string[]} pubs - The array of public inputs. - * @returns {string[]} - The formatted public inputs. + * @param {string[]} pubs - The array of public input strings. + * @param {ProofOptions} options - The proof options containing the library and other details. + * @returns {string[]} The formatted public inputs. */ - formatPubs(pubs: string[]): string[] { + formatPubs(pubs: string[], options: ProofOptions): string[] { + const formatter = this.getFormatter(options); return formatter.formatPubs(pubs); } } diff --git a/src/proofTypes/groth16/types.ts b/src/proofTypes/groth16/types.ts index c40af44..3823227 100644 --- a/src/proofTypes/groth16/types.ts +++ b/src/proofTypes/groth16/types.ts @@ -1,3 +1,5 @@ +import { ProofOptions } from '../../session/types'; + export interface Groth16VerificationKeyInput { curve: string; vk_alpha_1: string[]; @@ -17,7 +19,6 @@ export interface Groth16VerificationKey { } export interface ProofInput { - curve: string; pi_a: string[]; pi_b: string[][]; pi_c: string[]; @@ -33,3 +34,12 @@ export interface Proof { curve?: string; proof: ProofInner; } + +export interface Formatter { + formatProof(proof: ProofInput, options: ProofOptions): Proof; + formatVk( + vk: Groth16VerificationKeyInput, + options: ProofOptions, + ): Groth16VerificationKey; + formatPubs(pubs: string[]): string[]; +} diff --git a/src/session/builders/register/index.ts b/src/session/builders/register/index.ts index 3fcff33..da4d932 100644 --- a/src/session/builders/register/index.ts +++ b/src/session/builders/register/index.ts @@ -1,10 +1,13 @@ -import { VerifyOptions } from '../../types'; -import { ProofType } from '../../../config'; +import { ProofOptions, VerifyOptions } from '../../types'; +import { CurveType, Library, ProofType } from '../../../config'; import { EventEmitter } from 'events'; import { VKRegistrationTransactionInfo } from '../../../types'; export type RegisterKeyMethodMap = { - [K in keyof typeof ProofType]: () => RegisterKeyBuilder; + [K in keyof typeof ProofType]: ( + library?: Library, + curve?: CurveType, + ) => RegisterKeyBuilder; }; export class RegisterKeyBuilder { @@ -19,9 +22,9 @@ export class RegisterKeyBuilder { events: EventEmitter; transactionResult: Promise; }>, - proofType: ProofType, + proofOptions: ProofOptions, ) { - this.options = { proofType }; + this.options = { proofOptions }; } nonce(nonce: number): this { diff --git a/src/session/builders/verify/index.ts b/src/session/builders/verify/index.ts index d2fefd3..1cbe561 100644 --- a/src/session/builders/verify/index.ts +++ b/src/session/builders/verify/index.ts @@ -1,11 +1,14 @@ -import { VerifyOptions } from '../../types'; -import { ProofType } from '../../../config'; +import { ProofOptions, VerifyOptions } from '../../types'; +import { CurveType, Library, ProofType } from '../../../config'; import { EventEmitter } from 'events'; import { VerifyTransactionInfo } from '../../../types'; import { VerifyInput } from '../../../api/verify/types'; export type ProofMethodMap = { - [K in keyof typeof ProofType]: () => VerificationBuilder; + [K in keyof typeof ProofType]: ( + library?: Library, + curve?: CurveType, + ) => VerificationBuilder; }; export class VerificationBuilder { @@ -22,9 +25,9 @@ export class VerificationBuilder { events: EventEmitter; transactionResult: Promise; }>, - proofType: ProofType, + proofOptions: ProofOptions, ) { - this.options = { proofType }; + this.options = { proofOptions }; } nonce(nonce: number): this { diff --git a/src/session/index.ts b/src/session/index.ts index a0251de..8b5b586 100644 --- a/src/session/index.ts +++ b/src/session/index.ts @@ -1,5 +1,5 @@ import '@polkadot/api-augment'; // Required for api.query.system.account responses -import { zkVerifySessionOptions, VerifyOptions } from './types'; +import { zkVerifySessionOptions, VerifyOptions, ProofOptions } from './types'; import { verify } from '../api/verify'; import { accountInfo } from '../api/accountInfo'; import { startSession, startWalletSession } from '../api/start'; @@ -33,7 +33,7 @@ import { import { ApiPromise, WsProvider } from '@polkadot/api'; import { KeyringPair } from '@polkadot/keyring/types'; import { registerVk } from '../api/register'; -import { ProofType, SupportedNetwork } from '../config'; +import { CurveType, Library, ProofType, SupportedNetwork } from '../config'; import { ProofMethodMap, VerificationBuilder } from './builders/verify'; import { RegisterKeyBuilder, RegisterKeyMethodMap } from './builders/register'; import { NetworkBuilder, SupportedNetworkMap } from './builders/network'; @@ -42,6 +42,7 @@ import { SubmittableExtrinsic } from '@polkadot/api/types'; import { format } from '../api/format'; import { FormattedProofData } from '../api/format/types'; import { ExtrinsicCostEstimate } from '../api/estimate/types'; +import { validateProofTypeOptions } from './validator'; /** * zkVerifySession class provides an interface to zkVerify, direct access to the Polkadot.js API. @@ -123,13 +124,28 @@ export class zkVerifySession { */ verify(): ProofMethodMap { const builderMethods: Partial< - Record VerificationBuilder> + Record< + keyof typeof ProofType, + (library?: Library, curve?: CurveType) => VerificationBuilder + > > = {}; for (const proofType in ProofType) { if (Object.prototype.hasOwnProperty.call(ProofType, proofType)) { - builderMethods[proofType as keyof typeof ProofType] = () => - this.createVerifyBuilder(proofType as ProofType); + builderMethods[proofType as keyof typeof ProofType] = ( + library?: Library, + curve?: CurveType, + ) => { + const proofOptions: ProofOptions = { + proofType: proofType as ProofType, + library, + curve, + }; + + validateProofTypeOptions(proofOptions); + + return this.createVerifyBuilder(proofOptions); + }; } } @@ -145,13 +161,26 @@ export class zkVerifySession { */ registerVerificationKey(): RegisterKeyMethodMap { const builderMethods: Partial< - Record RegisterKeyBuilder> + Record< + keyof typeof ProofType, + (library?: Library, curve?: CurveType) => RegisterKeyBuilder + > > = {}; for (const proofType in ProofType) { if (Object.prototype.hasOwnProperty.call(ProofType, proofType)) { - builderMethods[proofType as keyof typeof ProofType] = () => - this.createRegisterKeyBuilder(proofType as ProofType); + builderMethods[proofType as keyof typeof ProofType] = ( + library?: Library, + curve?: CurveType, + ) => { + const proofOptions: ProofOptions = { + proofType: proofType as ProofType, + library, + curve, + }; + + return this.createRegisterKeyBuilder(proofOptions); + }; } } @@ -163,11 +192,13 @@ export class zkVerifySession { * The builder allows for chaining options and finally executing the verification process. * * @param {ProofType} proofType - The type of proof to be used. + * @param {Library} [library] - The optional library to be used, if required by the proof type. + * @param {CurveType} [curve] - The optional curve to be used, if required by the proof type. * @returns {VerificationBuilder} A new instance of `VerificationBuilder`. * @private */ - private createVerifyBuilder(proofType: ProofType): VerificationBuilder { - return new VerificationBuilder(this.executeVerify.bind(this), proofType); + private createVerifyBuilder(proofOptions: ProofOptions): VerificationBuilder { + return new VerificationBuilder(this.executeVerify.bind(this), proofOptions); } /** @@ -178,10 +209,12 @@ export class zkVerifySession { * @returns {RegisterKeyBuilder} A new instance of `RegisterKeyBuilder`. * @private */ - private createRegisterKeyBuilder(proofType: ProofType): RegisterKeyBuilder { + private createRegisterKeyBuilder( + proofOptions: ProofOptions, + ): RegisterKeyBuilder { return new RegisterKeyBuilder( this.executeRegisterVerificationKey.bind(this), - proofType, + proofOptions, ); } @@ -365,13 +398,13 @@ export class zkVerifySession { * @throws {Error} - Throws an error if formatting fails. */ async format( - proofType: ProofType, + proofOptions: ProofOptions, proof: unknown, publicSignals: unknown, vk: unknown, registeredVk?: boolean, ): Promise { - return format(proofType, proof, publicSignals, vk, registeredVk); + return format(proofOptions, proof, publicSignals, vk, registeredVk); } /** diff --git a/src/session/types.ts b/src/session/types.ts index 1c9fc94..66c33da 100644 --- a/src/session/types.ts +++ b/src/session/types.ts @@ -1,4 +1,4 @@ -import { ProofType, SupportedNetwork } from '../config'; +import { CurveType, Library, ProofType, SupportedNetwork } from '../config'; export interface zkVerifySessionOptions { host: SupportedNetwork; @@ -13,8 +13,14 @@ export interface WalletOptions { } export interface VerifyOptions { - proofType: ProofType; - registeredVk?: boolean; + proofOptions: ProofOptions; nonce?: number; waitForNewAttestationEvent?: boolean; + registeredVk?: boolean; +} + +export interface ProofOptions { + proofType: ProofType; + library?: Library; + curve?: CurveType; } diff --git a/src/session/validator/index.ts b/src/session/validator/index.ts new file mode 100644 index 0000000..ec8b5a4 --- /dev/null +++ b/src/session/validator/index.ts @@ -0,0 +1,37 @@ +import { proofConfigurations } from '../../config'; +import { ProofOptions } from '../types'; + +/** + * Validates the library and curve type for a given proof type. + * @throws {Error} - If the validation fails. + * @param options + */ +export function validateProofTypeOptions(options: ProofOptions): void { + if (!options.proofType) { + throw new Error('Proof type is required.'); + } + + const proofConfig = proofConfigurations[options.proofType]; + + if (options.library && !proofConfig.requiresLibrary) { + throw new Error( + `Library cannot be set for proof type '${options.proofType}'.`, + ); + } + if (!options.library && proofConfig.requiresLibrary) { + throw new Error( + `Library must be specified for proof type '${options.proofType}'.`, + ); + } + + if (options.curve && !proofConfig.requiresCurve) { + throw new Error( + `Curve type cannot be set for proof type '${options.proofType}'.`, + ); + } + if (!options.curve && proofConfig.requiresCurve) { + throw new Error( + `Curve type must be specified for proof type '${options.proofType}'.`, + ); + } +} diff --git a/src/types.ts b/src/types.ts index 0402d62..041dd16 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,9 +2,9 @@ import { TransactionStatus } from './enums'; import { ProofType } from './config'; export interface ProofProcessor { - formatProof(proof: unknown, publicSignals?: string[]): unknown; - formatVk(vkJson: unknown): unknown; - formatPubs(pubs: unknown): unknown; + formatProof(proof: unknown, options?: unknown): unknown; + formatVk(vkJson: unknown, options?: unknown): unknown; + formatPubs(pubs: unknown, options?: unknown): unknown; } export interface ProofData { diff --git a/src/utils/transactions/index.ts b/src/utils/transactions/index.ts index 90b6f28..9902774 100644 --- a/src/utils/transactions/index.ts +++ b/src/utils/transactions/index.ts @@ -108,7 +108,7 @@ export const handleTransaction = async ( transactionType: TransactionType, ): Promise => { const { - proofType, + proofOptions: { proofType }, waitForNewAttestationEvent: shouldWaitForAttestation = false, nonce, } = options; diff --git a/tests/common/data/groth16_gnark_bls12381.json b/tests/common/data/groth16_gnark_bls12381.json new file mode 100644 index 0000000..23ef5af --- /dev/null +++ b/tests/common/data/groth16_gnark_bls12381.json @@ -0,0 +1,92 @@ +{ + "proof": { + "Ar": { + "X": "1856217396163966335519007214204542103667728884486088217698799232919984492251923363982413917292325597718301003106668", + "Y": "2978791541085434527243357519822840626177324360276812310711705069777486104537125451860830800420208039774722850598975" + }, + "Krs": { + "X": "370884036393643515877107102750117939060411448064883807624640458811606787100203226691221216248359481291299316917858", + "Y": "3254420584904717495274426415874209833166017633161957686925993897239800952274744644108075428273942187672701844739582" + }, + "Bs": { + "X": { + "A0": "3417419742740944831845523141480733216744775405861895980643695170832636026003934394013222348574422853123066409249585", + "A1": "2021052036547909843940530710668351952061005392765935264471653101225279948603090440276991602834050876291769453475230" + }, + "Y": { + "A0": "2950135275604378049132955765054313080884351735535642024924174499694205722705769297010578256010533764511373258026449", + "A1": "263664756396818048386394128489192281904388643703370022331060495833200722073585577827956176793881025576641517679957" + } + }, + "Commitments": [], + "CommitmentPok": { + "X": 0, + "Y": 0 + } + }, + "publicSignals": [ "10", "1" ], + "vk": { + "G1": { + "Alpha": { + "X": "980962184117964562858037960556044560014176058264612917795085808287213424799917203711428138172927318074795300803381", + "Y": "1957069226433797866778558958007534146486057024464483826412967771588638212622390992776368078484088230306275732098440" + }, + "Beta": { + "X": "3298792724777564310479738837334700484857206506925953113027932982447070165791558380851200432159086996410665393668822", + "Y": "3202219294737980161441460855465650968159657977109323653765084801970848877670985634120284189040594129430620367779043" + }, + "Delta": { + "X": "761930430367262701250563395124610244677790325589285346310932608043872623904111881638488120002168976085530093586969", + "Y": "546480114709420185914392818421529146565051476994463693410748913957788078840479366490076895326574969836369592683908" + }, + "K": [ + { + "X": "1376880397597394627909486701214825299144429766930750577488607048516760669447035173035777075325486185730275565446817", + "Y": "784767586332596169025602311233725981445961379572098268705008326377019636835281207182962371136055724677562287007242" + }, + { + "X": "2788923244189065832987818243953540979692758159751071781724213116256981213898779407213416118184018764433035092108871", + "Y": "839970182015978168174554177524620235735074986011697202899336383464607002336405182721601090985806539216362355530163" + }, + { + "X": "1415126390631555046484663233491560565580501373610418773780963220759187066994869203448969199653234556198517131708249", + "Y": "656397556997969279249604603811466771976177482127085743782848949247203911419575517834102195476521307488908721850253" + } + ] + }, + "G2": { + "Beta": { + "X": { + "A0": "9729713938599235194563927613576728148108368153155839270911391097511936499600892213768600112706267373680685687696", + "A1": "1126932745523509337863652802495821811323608584505474772858786193180892690997501143982045072169571743536454992818196" + }, + "Y": { + "A0": "257251294202496098953466534426757314787333203212750543785303229396444484496694251407087536272807562213197767754276", + "A1": "30916259389360671275830945211728866277260283365245753152594541202000815513876122972607269790486475191057820765542" + } + }, + "Delta": { + "X": { + "A0": "2547213837344344856764946993195493866919543908691423708011623068047696709699743166417280811505560317064736982536504", + "A1": "3780573458693156911864484497248924242420704773068505917624816623474741370339085159672586026883454096888758591013901" + }, + "Y": { + "A0": "3325347381040524727154708054102106804496873053498503807614184667845910756961465227280538447618681577149510240168974", + "A1": "3943902748049350431116271563997344852472518277381165854836178030251323254454745766651595592303782994065381168175835" + } + }, + "Gamma": { + "X": { + "A0": "3160573848737488071688898698668133820343122348293130845445148916583288327914316080198356115168249775251544818517604", + "A1": "3051483544394812984944455752456757866399587540346854370560387217735655251019793553916623454359843004344486484399575" + }, + "Y": { + "A0": "1014705324715982802603919695571921526445114375642153005865112482111582110461195729948789860354908313128893790565692", + "A1": "3259231060635303154597313136854988241104590567229169403069493419898995886998594726482826435178018002098650537942712" + } + } + }, + "CommitmentKeys": [], + "PublicAndCommitmentCommitted": [] + } +} \ No newline at end of file diff --git a/tests/common/data/groth16_gnark_bn128.json b/tests/common/data/groth16_gnark_bn128.json new file mode 100644 index 0000000..4ae1b7b --- /dev/null +++ b/tests/common/data/groth16_gnark_bn128.json @@ -0,0 +1,92 @@ +{ + "proof": { + "Ar": { + "X": "6166896532025157427385862847003937733716266649214936495688620316635338510420", + "Y": "13322057568232790708986086756757845236885042054493072679020832668093565238135" + }, + "Krs": { + "X": "3090662454728767229441842240569690740429325315418833357806607293251488274211", + "Y": "18491987533494662212000279752127754886762332487327220011538122224342983241031" + }, + "Bs": { + "X": { + "A0": "19425871376241257566134767176241761651088959723250895313016409566400869237075", + "A1": "3447906360414177972399928368054057753378793816663485030223283035294404172377" + }, + "Y": { + "A0": "6950016641000248359809064728659177629544888710901939378826750518827514812751", + "A1": "4573317407221083210746608612848990705109608998742698993059055064275246686034" + } + }, + "Commitments": [], + "CommitmentPok": { + "X": 0, + "Y": 0 + } + }, + "publicSignals": [ "10", "1" ], + "vk": { + "G1": { + "Alpha": { + "X": "4509697195827663393278084370097392886383780292212374112114037466158938072596", + "Y": "807268964371155637412540996024006185280303412930903945366410901274824283129" + }, + "Beta": { + "X": "15105168741362586077231326712519808784707413253212496530336122736986860528332", + "Y": "21100676148472340434866194655115329506675554454192004626884294173403381522189" + }, + "Delta": { + "X": "2093326030963753433107302330598455278124495282717113575295356325021108209024", + "Y": "684743426362779202952995645020276894950498801353682626661693121910638395745" + }, + "K": [ + { + "X": "9707532300699726166425739147877198423461754346024426597571751493429240534318", + "Y": "21095279585628465176748490228492664750105646817757065571943881467597602871532" + }, + { + "X": "13886916796712957063669155679467461386718737675092716852006808876845630116428", + "Y": "7176744907238290647906297112666128329478193076490791486719102113104029555900" + }, + { + "X": "21298346455087063698707637604495231609220729124833669566400763257508133878023", + "Y": "14127252133927523291233520881695229442830432251247111910519646097006326044011" + } + ] + }, + "G2": { + "Beta": { + "X": { + "A0": "15775293550698857265994266718356179874463938415327126502603774314707041995940", + "A1": "19545490082610750493924344800299052026887449690415156397383152129862147692336" + }, + "Y": { + "A0": "3953976156028255336716206395977660896315365177793777082842385006635510179808", + "A1": "21213521096868625268641586074688981215763378713535102193333547495609204086293" + } + }, + "Delta": { + "X": { + "A0": "7680127727881184075848720112465940082921348281775944506569246772652760336031", + "A1": "20306330124956640111536999818012451717990712930955379696672069915886381174689" + }, + "Y": { + "A0": "15170494616706104155525814261453213908972580804443366354793995367093502619523", + "A1": "13293833750091554790031488444615908693846697736409167787295444448001990511457" + } + }, + "Gamma": { + "X": { + "A0": "12633034605766279753091178628294742379895797411052737963843680093165095399719", + "A1": "6229198214240993019609952234898020121945961592522897808960819337617181294962" + }, + "Y": { + "A0": "18217995588843698904771424608673390210122927432000869351184539586172558935955", + "A1": "20428638581065730622738950820785109126109929306593060626350964177297530777057" + } + } + }, + "CommitmentKeys": [], + "PublicAndCommitmentCommitted": [] + } +} \ No newline at end of file diff --git a/tests/common/data/groth16_gnark_bn254.json b/tests/common/data/groth16_gnark_bn254.json new file mode 100644 index 0000000..4ae1b7b --- /dev/null +++ b/tests/common/data/groth16_gnark_bn254.json @@ -0,0 +1,92 @@ +{ + "proof": { + "Ar": { + "X": "6166896532025157427385862847003937733716266649214936495688620316635338510420", + "Y": "13322057568232790708986086756757845236885042054493072679020832668093565238135" + }, + "Krs": { + "X": "3090662454728767229441842240569690740429325315418833357806607293251488274211", + "Y": "18491987533494662212000279752127754886762332487327220011538122224342983241031" + }, + "Bs": { + "X": { + "A0": "19425871376241257566134767176241761651088959723250895313016409566400869237075", + "A1": "3447906360414177972399928368054057753378793816663485030223283035294404172377" + }, + "Y": { + "A0": "6950016641000248359809064728659177629544888710901939378826750518827514812751", + "A1": "4573317407221083210746608612848990705109608998742698993059055064275246686034" + } + }, + "Commitments": [], + "CommitmentPok": { + "X": 0, + "Y": 0 + } + }, + "publicSignals": [ "10", "1" ], + "vk": { + "G1": { + "Alpha": { + "X": "4509697195827663393278084370097392886383780292212374112114037466158938072596", + "Y": "807268964371155637412540996024006185280303412930903945366410901274824283129" + }, + "Beta": { + "X": "15105168741362586077231326712519808784707413253212496530336122736986860528332", + "Y": "21100676148472340434866194655115329506675554454192004626884294173403381522189" + }, + "Delta": { + "X": "2093326030963753433107302330598455278124495282717113575295356325021108209024", + "Y": "684743426362779202952995645020276894950498801353682626661693121910638395745" + }, + "K": [ + { + "X": "9707532300699726166425739147877198423461754346024426597571751493429240534318", + "Y": "21095279585628465176748490228492664750105646817757065571943881467597602871532" + }, + { + "X": "13886916796712957063669155679467461386718737675092716852006808876845630116428", + "Y": "7176744907238290647906297112666128329478193076490791486719102113104029555900" + }, + { + "X": "21298346455087063698707637604495231609220729124833669566400763257508133878023", + "Y": "14127252133927523291233520881695229442830432251247111910519646097006326044011" + } + ] + }, + "G2": { + "Beta": { + "X": { + "A0": "15775293550698857265994266718356179874463938415327126502603774314707041995940", + "A1": "19545490082610750493924344800299052026887449690415156397383152129862147692336" + }, + "Y": { + "A0": "3953976156028255336716206395977660896315365177793777082842385006635510179808", + "A1": "21213521096868625268641586074688981215763378713535102193333547495609204086293" + } + }, + "Delta": { + "X": { + "A0": "7680127727881184075848720112465940082921348281775944506569246772652760336031", + "A1": "20306330124956640111536999818012451717990712930955379696672069915886381174689" + }, + "Y": { + "A0": "15170494616706104155525814261453213908972580804443366354793995367093502619523", + "A1": "13293833750091554790031488444615908693846697736409167787295444448001990511457" + } + }, + "Gamma": { + "X": { + "A0": "12633034605766279753091178628294742379895797411052737963843680093165095399719", + "A1": "6229198214240993019609952234898020121945961592522897808960819337617181294962" + }, + "Y": { + "A0": "18217995588843698904771424608673390210122927432000869351184539586172558935955", + "A1": "20428638581065730622738950820785109126109929306593060626350964177297530777057" + } + } + }, + "CommitmentKeys": [], + "PublicAndCommitmentCommitted": [] + } +} \ No newline at end of file diff --git a/tests/common/data/groth16_bls12381.json b/tests/common/data/groth16_snarkjs_bls12381.json similarity index 100% rename from tests/common/data/groth16_bls12381.json rename to tests/common/data/groth16_snarkjs_bls12381.json diff --git a/tests/common/data/groth16_bn128.json b/tests/common/data/groth16_snarkjs_bn128.json similarity index 100% rename from tests/common/data/groth16_bn128.json rename to tests/common/data/groth16_snarkjs_bn128.json diff --git a/tests/common/data/groth16_bn254.json b/tests/common/data/groth16_snarkjs_bn254.json similarity index 100% rename from tests/common/data/groth16_bn254.json rename to tests/common/data/groth16_snarkjs_bn254.json diff --git a/tests/common/runners.ts b/tests/common/runners.ts index 3389117..ea32e1b 100644 --- a/tests/common/runners.ts +++ b/tests/common/runners.ts @@ -1,84 +1,77 @@ -import { Groth16CurveType, ProofType } from "../../src"; +import { CurveType, Library, ProofOptions, ProofType } from "../../src"; import { - curveTypes, getSeedPhrase, loadProofAndVK, performVerifyTransaction, performVKRegistrationAndVerification } from "./utils"; +const logTestDetails = (proofOptions: ProofOptions, testType: string) => { + const { proofType, library, curve } = proofOptions; + const details = [library && `library: ${library}`, curve && `curve: ${curve}`].filter(Boolean).join(", "); + console.log(`Running ${testType} for ${proofType}${details ? ` with ${details}` : ""}`); +}; + export const runVerifyTest = async ( - proofType: ProofType, + proofOptions: ProofOptions, withAttestation: boolean = false, checkExistence: boolean = false, - seedPhrase: string, - curve?: string + seedPhrase: string ) => { - if (proofType === ProofType.groth16 && curve) { - console.log(`Running ${proofType} test with curve: ${curve}`); - const { proof, vk } = loadProofAndVK(proofType, curve); - await performVerifyTransaction(seedPhrase, proofType, proof.proof, proof.publicSignals, vk, withAttestation, checkExistence); - } else { - console.log(`Running ${proofType} test`); - const { proof, vk } = loadProofAndVK(proofType); - await performVerifyTransaction(seedPhrase, proofType, proof.proof, proof.publicSignals, vk, withAttestation, checkExistence); - } + logTestDetails(proofOptions, "verification test"); + const { proof, vk } = loadProofAndVK(proofOptions); + await performVerifyTransaction(seedPhrase, proofOptions, proof.proof, proof.publicSignals, vk, withAttestation, checkExistence); }; -export const runVKRegistrationTest = async (proofType: ProofType, seedPhrase: string) => { - if (proofType === ProofType.groth16) { - for (const curve of curveTypes) { - console.log(`Running VK registration for ${proofType} with curve: ${curve}`); - const { proof, vk } = loadProofAndVK(proofType, curve); - await performVKRegistrationAndVerification(seedPhrase, proofType, proof.proof, proof.publicSignals, vk); - } - } else { - console.log(`Running VK registration for ${proofType}`); - const { proof, vk } = loadProofAndVK(proofType); - await performVKRegistrationAndVerification(seedPhrase, proofType, proof.proof, proof.publicSignals, vk); - } +export const runVKRegistrationTest = async (proofOptions: ProofOptions, seedPhrase: string) => { + logTestDetails(proofOptions, "VK registration"); + const { proof, vk } = loadProofAndVK(proofOptions); + await performVKRegistrationAndVerification(seedPhrase, proofOptions, proof.proof, proof.publicSignals, vk); }; -export const runAllProofTests = async ( +const generateTestPromises = ( proofTypes: ProofType[], - curveTypes: Groth16CurveType[], - withAttestation: boolean -) => { - const testPromises: Promise[] = []; + curveTypes: CurveType[], + libraries: Library[], + runTest: (proofOptions: ProofOptions, seedPhrase: string) => Promise +): Promise[] => { + const promises: Promise[] = []; let seedIndex = 0; proofTypes.forEach((proofType) => { if (proofType === ProofType.groth16) { - curveTypes.forEach((curve) => { - console.log(`${proofType} ${curve} Seed Index: ${seedIndex}`); - const seedPhrase = getSeedPhrase(seedIndex++); - testPromises.push(runVerifyTest(proofType, withAttestation, false, seedPhrase, curve)); + libraries.forEach((library) => { + curveTypes.forEach((curve) => { + const seedPhrase = getSeedPhrase(seedIndex++); + promises.push(runTest({ proofType, curve, library }, seedPhrase)); + }); }); } else { - console.log(`${proofType} Seed Index: ${seedIndex}`); const seedPhrase = getSeedPhrase(seedIndex++); - testPromises.push(runVerifyTest(proofType, withAttestation, false, seedPhrase)); + promises.push(runTest({ proofType }, seedPhrase)); } }); - await Promise.allSettled(testPromises); + return promises; }; -export const runAllVKRegistrationTests = async (proofTypes: ProofType[], curveTypes: Groth16CurveType[]) => { - const testPromises: Promise[] = []; - let seedIndex = 0; - - proofTypes.forEach((proofType) => { - if (proofType === ProofType.groth16) { - curveTypes.forEach((curve) => { - const seedPhrase = getSeedPhrase(seedIndex++); - testPromises.push(runVKRegistrationTest(proofType, seedPhrase)); - }); - } else { - const seedPhrase = getSeedPhrase(seedIndex++); - testPromises.push(runVKRegistrationTest(proofType, seedPhrase)); - } - }); +export const runAllProofTests = async ( + proofTypes: ProofType[], + curveTypes: CurveType[], + libraries: Library[], + withAttestation: boolean +) => { + const testPromises = generateTestPromises(proofTypes, curveTypes, libraries, (proofOptions, seedPhrase) => + runVerifyTest(proofOptions, withAttestation, false, seedPhrase) + ); + await Promise.all(testPromises); +}; - await Promise.allSettled(testPromises); -}; \ No newline at end of file +export const runAllVKRegistrationTests = async ( + proofTypes: ProofType[], + curveTypes: CurveType[], + libraries: Library[] +) => { + const testPromises = generateTestPromises(proofTypes, curveTypes, libraries, runVKRegistrationTest); + await Promise.all(testPromises); +}; diff --git a/tests/common/utils.ts b/tests/common/utils.ts index b233ffe..8609cfb 100644 --- a/tests/common/utils.ts +++ b/tests/common/utils.ts @@ -4,7 +4,7 @@ import { TransactionInfo, TransactionStatus, VerifyTransactionInfo, - VKRegistrationTransactionInfo, Groth16CurveType + VKRegistrationTransactionInfo, CurveType, Library, ProofOptions } from '../../src'; import { handleCommonEvents, @@ -21,10 +21,11 @@ export interface ProofData { } export const proofTypes = Object.keys(ProofType).map((key) => ProofType[key as keyof typeof ProofType]); -export const curveTypes = Object.keys(Groth16CurveType).map((key) => Groth16CurveType[key as keyof typeof Groth16CurveType]); +export const curveTypes = Object.keys(CurveType).map((key) => CurveType[key as keyof typeof CurveType]); +export const libraries = Object.keys(Library).map((key) => Library[key as keyof typeof Library]); // ADD_NEW_PROOF_TYPE -// One Seed Phrase per proof type / curve combo. NOTE: SEED_PHRASE_8 used by unit tests and will need updating when new verifier added. +// One Seed Phrase per proof type / curve combo. NOTE: SEED_PHRASE_11 used by unit tests and will need updating when new verifier added. const seedPhrases = [ process.env.SEED_PHRASE_1, process.env.SEED_PHRASE_2, @@ -33,6 +34,9 @@ const seedPhrases = [ process.env.SEED_PHRASE_5, process.env.SEED_PHRASE_6, process.env.SEED_PHRASE_7, + process.env.SEED_PHRASE_8, + process.env.SEED_PHRASE_9, + process.env.SEED_PHRASE_10, ]; export const getSeedPhrase = (index: number): string => { @@ -45,22 +49,28 @@ export const getSeedPhrase = (index: number): string => { return seedPhrase; }; -export const loadProofData = (proofType: ProofType, curve?: string): ProofData => { - const fileName = curve ? `${proofType}_${curve}` : proofType; +export const loadProofData = (proofOptions: ProofOptions): ProofData => { + const { proofType, curve, library } = proofOptions; + + const fileName = [proofType, library, curve].filter(Boolean).join('_'); const dataPath = path.join(__dirname, 'data', `${fileName}.json`); + return JSON.parse(fs.readFileSync(dataPath, 'utf8')); }; -export const loadVerificationKey = (proofType: ProofType, curve?: string): string => { +export const loadVerificationKey = (proofOptions: ProofOptions): string => { + const { proofType, curve, library } = proofOptions; + if (proofType === ProofType.ultraplonk) { const vkPath = path.join(__dirname, 'data', 'ultraplonk_vk.bin'); return fs.readFileSync(vkPath).toString('hex'); - } else { - const fileName = curve ? `${proofType}_${curve}` : proofType; - const dataPath = path.join(__dirname, 'data', `${fileName}.json`); - const proofData: ProofData = JSON.parse(fs.readFileSync(dataPath, 'utf8')); - return proofData.vk!; } + + const fileName = [proofType, library, curve].filter(Boolean).join('_'); + const dataPath = path.join(__dirname, 'data', `${fileName}.json`); + const proofData: ProofData = JSON.parse(fs.readFileSync(dataPath, 'utf8')); + + return proofData.vk!; }; export const validateEventResults = (eventResults: EventResults, expectAttestation: boolean): void => { @@ -80,7 +90,7 @@ export const validateEventResults = (eventResults: EventResults, expectAttestati export const performVerifyTransaction = async ( seedPhrase: string, - proofType: ProofType, + proofOptions: ProofOptions, proof: any, publicSignals: any, vk: string, @@ -89,25 +99,26 @@ export const performVerifyTransaction = async ( ): Promise<{ eventResults: EventResults; transactionInfo: VerifyTransactionInfo }> => { const session = await zkVerifySession.start().Testnet().withAccount(seedPhrase); - console.log(`${proofType} Executing transaction...`); - const verifier = session.verify()[proofType](); + console.log(`${proofOptions.proofType} Executing transaction with library: ${proofOptions.library}, curve: ${proofOptions.curve}...`); + const verifier = session.verify()[proofOptions.proofType](proofOptions.library, proofOptions.curve); const verify = withAttestation ? verifier.waitForPublishedAttestation() : verifier; - const { events, transactionResult } = await verify.execute({ proofData: { - proof: proof, - publicSignals: publicSignals, - vk: vk - } + const { events, transactionResult } = await verify.execute({ + proofData: { + proof: proof, + publicSignals: publicSignals, + vk: vk, + }, }); const eventResults = withAttestation - ? handleEventsWithAttestation(events, proofType, 'verify') - : handleCommonEvents(events, proofType, 'verify'); + ? handleEventsWithAttestation(events, proofOptions.proofType, 'verify') + : handleCommonEvents(events, proofOptions.proofType, 'verify'); - console.log(`${proofType} Transaction result received. Validating...`); + console.log(`${proofOptions.proofType} Transaction result received. Validating...`); const transactionInfo: VerifyTransactionInfo = await transactionResult; - validateVerifyTransactionInfo(transactionInfo, proofType, withAttestation); + validateVerifyTransactionInfo(transactionInfo, proofOptions.proofType, withAttestation); validateEventResults(eventResults, withAttestation); if (validatePoe) { @@ -119,44 +130,62 @@ export const performVerifyTransaction = async ( return { eventResults, transactionInfo }; }; - export const performVKRegistrationAndVerification = async ( seedPhrase: string, - proofType: ProofType, + proofOptions: ProofOptions, proof: any, publicSignals: any, vk: string ): Promise => { const session = await zkVerifySession.start().Testnet().withAccount(seedPhrase); - console.log(`${proofType} Executing VK registration...`); - const { events: registerEvents, transactionResult: registerTransactionResult } = await session.registerVerificationKey()[proofType]().execute(vk); + console.log( + `${proofOptions.proofType} Executing VK registration with library: ${proofOptions.library}, curve: ${proofOptions.curve}...` + ); + + const { events: registerEvents, transactionResult: registerTransactionResult } = + await session + .registerVerificationKey()[proofOptions.proofType]( + proofOptions.library, + proofOptions.curve + ) + .execute(vk); - const registerResults = handleCommonEvents(registerEvents, proofType, 'vkRegistration'); + const registerResults = handleCommonEvents( + registerEvents, + proofOptions.proofType, + 'vkRegistration' + ); const vkTransactionInfo: VKRegistrationTransactionInfo = await registerTransactionResult; - validateVKRegistrationTransactionInfo(vkTransactionInfo, proofType); + validateVKRegistrationTransactionInfo(vkTransactionInfo, proofOptions.proofType); validateEventResults(registerResults, false); - console.log(`${proofType} Executing verification using registered VK...`); - const { events: verifyEvents, transactionResult: verifyTransactionResult } = await session.verify()[proofType]() - .withRegisteredVk() - .execute({ proofData: { - proof: proof, - publicSignals: publicSignals, - vk: vkTransactionInfo.statementHash! - } - }); - const verifyResults = handleCommonEvents(verifyEvents, proofType, 'verify'); + console.log( + `${proofOptions.proofType} Executing verification using registered VK with library: ${proofOptions.library}, curve: ${proofOptions.curve}...` + ); + + const { events: verifyEvents, transactionResult: verifyTransactionResult } = + await session + .verify()[proofOptions.proofType](proofOptions.library, proofOptions.curve) + .withRegisteredVk() + .execute({ + proofData: { + proof: proof, + publicSignals: publicSignals, + vk: vkTransactionInfo.statementHash!, + }, + }); + + const verifyResults = handleCommonEvents(verifyEvents, proofOptions.proofType, 'verify'); const verifyTransactionInfo: VerifyTransactionInfo = await verifyTransactionResult; - validateVerifyTransactionInfo(verifyTransactionInfo, proofType, false); + validateVerifyTransactionInfo(verifyTransactionInfo, proofOptions.proofType, false); validateEventResults(verifyResults, false); await session.close(); }; - export const validateTransactionInfo = ( transactionInfo: TransactionInfo, expectedProofType: string @@ -215,16 +244,9 @@ export const validatePoE = async ( expect(proofDetails.leaf).toBeDefined(); }; -export const loadProofAndVK = (proofType: ProofType, curve?: string) => { - if (proofType === ProofType.groth16 && curve) { - return { - proof: loadProofData(proofType, curve), - vk: loadVerificationKey(proofType, curve) - }; - } else { - return { - proof: loadProofData(proofType), - vk: loadVerificationKey(proofType) - }; - } +export const loadProofAndVK = (proofOptions: ProofOptions) => { + return { + proof: loadProofData(proofOptions), + vk: loadVerificationKey(proofOptions) + }; }; \ No newline at end of file diff --git a/tests/errors.test.ts b/tests/errors.test.ts index 1700579..7294669 100644 --- a/tests/errors.test.ts +++ b/tests/errors.test.ts @@ -1,8 +1,7 @@ import fs from 'fs'; import path from 'path'; -import { zkVerifySession } from '../src'; -import { ZkVerifyEvents } from "../src"; -import { getSeedPhrase } from "./common/utils"; +import {CurveType, Library, ZkVerifyEvents, zkVerifySession} from '../src'; +import {getSeedPhrase} from "./common/utils"; jest.setTimeout(180000); @@ -32,12 +31,12 @@ describe('verify with bad data - Groth16', () => { const { publicSignals, vk } = groth16Data; // ADD_NEW_PROOF_TYPE - // Uses SEED_PHRASE_8 but increment as needed if new proof types have been added, this should run without affecting the other tests. - session = await zkVerifySession.start().Testnet().withAccount(getSeedPhrase(7)); + // Uses SEED_PHRASE_11 but increment as needed if new proof types have been added, this should run without affecting the other tests. + session = await zkVerifySession.start().Testnet().withAccount(getSeedPhrase(10)); let errorEventEmitted = false; const { events, transactionResult } = await session.verify() - .groth16().execute({ + .groth16(Library.snarkjs, CurveType.bn128).execute({ proofData: { proof: badProof, publicSignals: publicSignals, @@ -64,12 +63,12 @@ describe('verify with bad data - Groth16', () => { const { proof, publicSignals, vk } = groth16Data; // ADD NEW_PROOF_TYPE - // Uses SEED_PHRASE_8 - increment after adding new proof types - session = await zkVerifySession.start().Testnet().withAccount(getSeedPhrase(7)); + // Uses SEED_PHRASE_11 - increment after adding new proof types + session = await zkVerifySession.start().Testnet().withAccount(getSeedPhrase(10)); let errorEventEmitted = false; const { events, transactionResult } = await session.verify() - .groth16().execute({ + .groth16(Library.snarkjs, CurveType.bn254).execute({ proofData: { proof: proof, publicSignals: publicSignals, diff --git a/tests/session.test.ts b/tests/session.test.ts index d71125f..7464ff5 100644 --- a/tests/session.test.ts +++ b/tests/session.test.ts @@ -1,7 +1,7 @@ -import { zkVerifySession } from '../src'; -import { EventEmitter } from 'events'; -import { ProofMethodMap } from "../src/session/builders/verify"; -import { getSeedPhrase } from "./common/utils"; +import {CurveType, Library, zkVerifySession} from '../src'; +import {EventEmitter} from 'events'; +import {ProofMethodMap} from "../src/session/builders/verify"; +import {getSeedPhrase} from "./common/utils"; describe('zkVerifySession class', () => { let session: zkVerifySession; @@ -90,7 +90,7 @@ describe('zkVerifySession class', () => { session = await zkVerifySession.start().Testnet().readOnly(); expect(session.readOnly).toBe(true); await expect( - session.verify().groth16().execute({ proofData: { + session.verify().groth16(Library.snarkjs, CurveType.bn128).execute({ proofData: { proof: 'proofData', publicSignals: 'publicSignals', vk: 'vk' @@ -166,7 +166,7 @@ describe('zkVerifySession class', () => { vk: 'vk' } }), - session.verify().groth16().execute({ proofData: { + session.verify().groth16(Library.snarkjs, CurveType.bls12381).execute({ proofData: { proof: 'proofData', publicSignals: 'publicSignals', vk: 'vk' diff --git a/tests/utils.test.ts b/tests/utils.test.ts index d49f626..fa524da 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,5 +1,5 @@ -import { ExtrinsicCostEstimate, ProofType, zkVerifySession } from '../src'; -import { getSeedPhrase } from "./common/utils"; +import {CurveType, ExtrinsicCostEstimate, Library, ProofType, zkVerifySession} from '../src'; +import {getSeedPhrase} from "./common/utils"; import path from "path"; import fs from "fs"; @@ -19,11 +19,11 @@ describe('zkVerifySession - estimateCost', () => { }); async function getTestExtrinsic() { - const dataPath = path.join(__dirname, 'common/data', 'groth16_bn128.json'); + const dataPath = path.join(__dirname, 'common/data', 'groth16_snarkjs_bn128.json'); const groth16Data = JSON.parse(fs.readFileSync(dataPath, 'utf8')); const formattedProofData = await session.format( - ProofType.groth16, + { proofType: ProofType.groth16, library: Library.snarkjs, curve: CurveType.bn128 }, groth16Data.proof, groth16Data.publicSignals, groth16Data.vk diff --git a/tests/verify.test.ts b/tests/verify.test.ts index d980067..4496315 100644 --- a/tests/verify.test.ts +++ b/tests/verify.test.ts @@ -1,18 +1,17 @@ -import { proofTypes, curveTypes } from './common/utils'; +import { proofTypes, curveTypes, libraries } from './common/utils'; import { runAllProofTests, runAllVKRegistrationTests } from "./common/runners"; -jest.setTimeout(300000); - +jest.setTimeout(500000); describe('zkVerify proof user journey tests', () => { test('should verify all proof types and respond on finalization without waiting for Attestation event', async () => { - await runAllProofTests(proofTypes, curveTypes, false); + await runAllProofTests(proofTypes, curveTypes, libraries, false); }); test('should verify all proof types, wait for Attestation event, and then check proof of existence', async () => { - await runAllProofTests(proofTypes, curveTypes, true); + await runAllProofTests(proofTypes, curveTypes, libraries,true); }); test('should register VK and verify the proof using the VK hash for all proof types', async () => { - await runAllVKRegistrationTests(proofTypes, curveTypes); + await runAllVKRegistrationTests(proofTypes, curveTypes, libraries); }); });