Skip to content

Commit

Permalink
feat: optimistic proof verification (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
rushby authored Dec 13, 2024
1 parent e144c7a commit 66c7056
Show file tree
Hide file tree
Showing 26 changed files with 727 additions and 193 deletions.
10 changes: 1 addition & 9 deletions .github/workflows/ci-publish-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ on:
jobs:
build-and-publish:
runs-on: ubuntu-latest
env:
SEED_PHRASE_1: ${{ secrets.SEED_PHRASE_1 }}
SEED_PHRASE_2: ${{ secrets.SEED_PHRASE_2 }}
SEED_PHRASE_3: ${{ secrets.SEED_PHRASE_3 }}
SEED_PHRASE_4: ${{ secrets.SEED_PHRASE_4 }}
SEED_PHRASE_5: ${{ secrets.SEED_PHRASE_5 }}
SEED_PHRASE_6: ${{ secrets.SEED_PHRASE_6 }}
SEED_PHRASE_7: ${{ secrets.SEED_PHRASE_7 }}
SEED_PHRASE_8: ${{ secrets.SEED_PHRASE_8 }}

steps:
- name: Checkout code
Expand Down Expand Up @@ -66,6 +57,7 @@ jobs:
release_notes=$(cat release_notes.md | sed ':a;N;$!ba;s/\n/\\n/g' | sed 's/"/\\"/g')
echo "release_notes=$release_notes" >> $GITHUB_ENV
echo "current_tag=$current_tag" >> $GITHUB_ENV
- name: Publish to npm
id: publish
Expand Down
3 changes: 1 addition & 2 deletions DEV_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ npm install ./path-to-package/zkverifyjs-0.2.0.tgz

1. Update `src/config/index.ts`
2. Add a new proof to src/proofTypes including processor and formatter, and add export to `src/proofTypes/index.ts`
3. Add new `SEED_PHRASE_*` environment variable to ensure parallel test runs continue to work.
4. Also note that the unit tests require an additional seed phrase (proof types / curve combo + 1)
3. Adding new `SEED_PHRASE_*` environment variables will provide more throughput for tests if they are locked waiting for one to become available from the `WalletPool`

- Search for `ADD_NEW_PROOF_TYPE` in the codebase.

Expand Down
23 changes: 20 additions & 3 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,29 @@ console.log(JSON.stringify(transactionInfo.attestationEvent)) // Attestation Eve
import { zkVerifySession, ZkVerifyEvents, TransactionStatus, VerifyTransactionInfo } from 'zkverifyjs';

async function executeVerificationTransaction(proof: unknown, publicSignals: unknown, vk: unknown) {
// Start a new zkVerifySession on our testnet (replace 'your-seed-phrase' with actual value)
// Start a new zkVerifySession on a Custom network (replace 'your-seed-phrase' with actual value)
const session = await zkVerifySession.start()
.Testnet()
.Custom('ws://my-custom-node')
.withAccount('your-seed-phrase');

// Optimistically verify the proof (requires Custom node running in unsafe mode for dryRun() call)
const { success, message } = session.optimisticVerify()
.risc0()
.execute({ proofData: {
vk: vk,
proof: proof,
publicSignals: publicSignals }
});;

if(!success) {
throw new Error("Optimistic Proof Verification Failed")
}

// Add additional dApp logic using fast response from zkVerify
// Your logic here
// Your logic here

// Execute the verification transaction
// Execute the verification transaction on zkVerify chain
const { events, transactionResult } = await session.verify().risc0()
.waitForPublishedAttestation()
.execute({ proofData: {
Expand Down
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,29 @@ console.log(JSON.stringify(transactionInfo.attestationEvent)) // Attestation Eve
import { zkVerifySession, ZkVerifyEvents, TransactionStatus, VerifyTransactionInfo } from 'zkverifyjs';

async function executeVerificationTransaction(proof: unknown, publicSignals: unknown, vk: unknown) {
// Start a new zkVerifySession on our testnet (replace 'your-seed-phrase' with actual value)
// Start a new zkVerifySession on a Custom network (replace 'your-seed-phrase' with actual value)
const session = await zkVerifySession.start()
.Testnet()
.Custom('ws://my-custom-node')
.withAccount('your-seed-phrase');

// Optimistically verify the proof (requires Custom node running in unsafe mode for dryRun() call)
const { success, message } = session.optimisticVerify()
.risc0()
.execute({ proofData: {
vk: vk,
proof: proof,
publicSignals: publicSignals }
});;

if(!success) {
throw new Error("Optimistic Proof Verification Failed")
}

// Add additional dApp logic using fast response from zkVerify
// Your logic here
// Your logic here

// Execute the verification transaction
// Execute the verification transaction on zkVerify chain
const { events, transactionResult } = await session.verify().risc0()
.waitForPublishedAttestation()
.execute({ proofData: {
Expand Down
14 changes: 12 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zkverifyjs",
"version": "0.4.0",
"version": "0.5.0",
"description": "Submit proofs to zkVerify and query proof state with ease using our npm package.",
"author": "Horizen Labs <[email protected]>",
"license": "GPL-3.0",
Expand Down Expand Up @@ -85,6 +85,7 @@
"@types/web3": "^1.2.2",
"@typescript-eslint/eslint-plugin": "^8.2.0",
"@typescript-eslint/parser": "^8.2.0",
"async-mutex": "^0.5.0",
"conventional-changelog-cli": "^5.0.0",
"eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0",
Expand Down
18 changes: 13 additions & 5 deletions src/api/account/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { setupAccount } from './index';
import { getSeedPhrase } from '../../../tests/common/utils';
import { walletPool } from '../../../tests/common/walletPool';

describe('setupAccount', () => {
beforeAll(async () => {
await cryptoWaitReady();
});

it('should return a KeyringPair when provided with a valid seed phrase', () => {
const account = setupAccount(getSeedPhrase(0));
it('should return a KeyringPair when provided with a valid seed phrase', async () => {
let wallet: string | undefined;
try {
wallet = await walletPool.acquireWallet();
const account = setupAccount(wallet);

expect(account).toBeDefined();
expect(account.publicKey).toBeDefined();
expect(account).toBeDefined();
expect(account.publicKey).toBeDefined();
} finally {
if (wallet) {
await walletPool.releaseWallet(wallet);
}
}
});

it('should throw an error with a custom message when an invalid seed phrase is provided', () => {
Expand Down
2 changes: 2 additions & 0 deletions src/api/extrinsic/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ describe('extrinsic utilities', () => {
proofParams.formattedVk,
proofParams.formattedProof,
proofParams.formattedPubs,
null, // TODO: Aggregate pallet (domain_id)
);
expect(extrinsic.toHex()).toBe('0x1234');
});
Expand Down Expand Up @@ -97,6 +98,7 @@ describe('extrinsic utilities', () => {
proofParams.formattedVk,
proofParams.formattedProof,
proofParams.formattedPubs,
null, // TODO: Aggregate pallet (domain_id)
);
expect(hex).toBe('0x1234');
});
Expand Down
1 change: 1 addition & 0 deletions src/api/extrinsic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const createSubmitProofExtrinsic = (
params.formattedVk,
params.formattedProof,
params.formattedPubs,
null, // TODO: Update with aggregate pallet functionality (domain_id)
);
} catch (error: unknown) {
throw new Error(formatError(error, proofType, params));
Expand Down
74 changes: 74 additions & 0 deletions src/api/optimisticVerify/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { AccountConnection, WalletConnection } from '../connection/types';
import { createSubmitProofExtrinsic } from '../extrinsic';
import { format } from '../format';
import { ProofData } from '../../types';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { FormattedProofData } from '../format/types';
import { ProofOptions } from '../../session/types';
import { VerifyInput } from '../verify/types';
import { interpretDryRunResponse } from '../../utils/helpers';
import { ApiPromise } from '@polkadot/api';

export const optimisticVerify = async (
connection: AccountConnection | WalletConnection,
proofOptions: ProofOptions,
input: VerifyInput,
): Promise<{ success: boolean; message: string }> => {
const { api } = connection;

try {
const transaction = buildTransaction(api, proofOptions, input);

const submittableExtrinsicHex = transaction.toHex();
const dryRunResult = await api.rpc.system.dryRun(submittableExtrinsicHex);
const { success, message } = await interpretDryRunResponse(
api,
dryRunResult.toHex(),
);

return { success, message };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
message: `Optimistic verification failed: ${errorMessage}`,
};
}
};

/**
* Builds a transaction from the provided input.
* @param api - The Polkadot.js API instance.
* @param proofOptions - Options for the proof.
* @param input - Input for the verification (proofData or extrinsic).
* @returns A SubmittableExtrinsic ready for dryRun.
* @throws If input is invalid or cannot be formatted.
*/
const buildTransaction = (
api: ApiPromise,
proofOptions: ProofOptions,
input: VerifyInput,
): SubmittableExtrinsic<'promise'> => {
if ('proofData' in input && input.proofData) {
const { proof, publicSignals, vk } = input.proofData as ProofData;
const formattedProofData: FormattedProofData = format(
proofOptions,
proof,
publicSignals,
vk,
);
return createSubmitProofExtrinsic(
api,
proofOptions.proofType,
formattedProofData,
);
}

if ('extrinsic' in input && input.extrinsic) {
return input.extrinsic;
}

throw new Error(
`Invalid input provided. Expected either 'proofData' or 'extrinsic'. Received: ${JSON.stringify(input)}`,
);
};
26 changes: 25 additions & 1 deletion src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const zkvRpc = {
description: 'Get the Merkle root and path of a stored proof',
params: [
{
name: 'attestation_id',
name: 'root_id',
type: 'u64',
},
{
Expand All @@ -106,4 +106,28 @@ export const zkvRpc = {
type: 'MerkleProof',
},
},
aggregate: {
statementPath: {
description: 'Get the Merkle root and path of a aggregate statement',
params: [
{
name: 'at',
type: 'BlockHash',
},
{
name: 'domain_id',
type: 'u32',
},
{
name: 'aggregation_id',
type: 'u64',
},
{
name: 'statement',
type: 'H256',
},
],
type: 'MerkleProof',
},
},
};
8 changes: 4 additions & 4 deletions src/proofTypes/groth16/formatter/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,13 @@ export const formatG2Point = (

const formatX =
curve === 'Bls12_381'
? [x2.toString(), x1.toString()] // bls12381 uses (x2, x1)
: [x1.toString(), x2.toString()]; // bn254 uses (x1, x2)
? [x2.toString(), x1.toString()]
: [x1.toString(), x2.toString()];

const formatY =
curve === 'Bls12_381'
? [y2.toString(), y1.toString()] // bls12381 uses (y2, y1)
: [y1.toString(), y2.toString()]; // bn254 uses (y1, y2)
? [y2.toString(), y1.toString()]
: [y1.toString(), y2.toString()];

return (
formatG1Point(formatX, endianess) +
Expand Down
31 changes: 31 additions & 0 deletions src/session/builders/optimisticVerify/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ProofOptions } from '../../types';
import { VerifyInput } from '../../../api/verify/types';
import { CurveType, Library, ProofType } from '../../../config';

export type OptimisticProofMethodMap = {
[K in keyof typeof ProofType]: (
library?: Library,
curve?: CurveType,
) => OptimisticVerificationBuilder;
};

export class OptimisticVerificationBuilder {
constructor(
private readonly executeOptimisticVerify: (
proofOptions: ProofOptions,
input: VerifyInput,
) => Promise<{ success: boolean; message: string }>,
private readonly proofOptions: ProofOptions,
) {}

/**
* Executes the optimistic verification process.
* @param {VerifyInput} input - Input for the verification, either proofData or an extrinsic.
* @returns {Promise<{ success: boolean; message: string }>} Resolves with an object indicating success or failure and any message.
*/
async execute(
input: VerifyInput,
): Promise<{ success: boolean; message: string }> {
return this.executeOptimisticVerify(this.proofOptions, input);
}
}
Loading

0 comments on commit 66c7056

Please sign in to comment.