Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions .claude/skills/wasm-bench-browserstack/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
name: wasm-bench-browserstack
description: Run on-demand bb.js Chonk prove+verify benchmarks on BrowserStack real mobile devices using pinned IVC inputs.
---

# BrowserStack bb.js Chonk Benchmark

Use this skill when asked to benchmark bb.js Chonk proving on real browsers or
mobile devices. This is an on-demand benchmark, not a per-PR CI gate.

## Scope

- Benchmark full Chonk prove plus verify through `window.proveChonk` and
`window.verifyChonk`.
- Use the pinned Chonk IVC input archive from
`barretenberg/cpp/scripts/test_chonk_standalone_vks_havent_changed.sh`.
- Default device coverage is old iOS, new iOS, old Android, and new Android.
Override the matrix for focused device work.
- Results are JSONL rows, one row per input per device, including BrowserStack
requested capabilities and browser feature probes for SAB, threads, SIMD, and
hardware concurrency.

## Prerequisites

From the repository root:

```bash
cd barretenberg/cpp
./scripts/test_chonk_standalone_vks_havent_changed.sh --download_pinned_inputs

cd ../ts
yarn build:wasm
yarn build:browser

cd ../acir_tests
yarn install
yarn workspace browser-test-app build
```

Set BrowserStack credentials:

```bash
export BROWSERSTACK_USER_NAME=...
export BROWSERSTACK_ACCESS_KEY=...
```

`BROWSERSTACK_USERNAME` is also accepted for the username, and
`BROWSERSTACK_KEY` is also accepted for the access key.

## Run

```bash
cd barretenberg/acir_tests
yarn workspace headless-test browserstack:chonk-bench \
--output browserstack-chonk-bench.jsonl
```

The default flow set is:

- `ecdsar1+transfer_0_recursions+sponsored_fpc`
- `ecdsar1+transfer_1_recursions+sponsored_fpc`
- `ecdsar1+token_bridge_claim_private+sponsored_fpc`

Override flows:

```bash
yarn workspace headless-test browserstack:chonk-bench \
--flow ecdsar1+transfer_1_recursions+sponsored_fpc \
--flow ecdsar1+transfer_0_recursions+sponsored_fpc
```

Use explicit input files:

```bash
yarn workspace headless-test browserstack:chonk-bench \
--input /path/to/ivc-inputs-a.msgpack \
--input /path/to/ivc-inputs-b.msgpack
```

Override the device matrix with JSON or a JSON file:

```json
[
{
"name": "ios-17",
"capabilities": {
"browserName": "safari",
"bstack:options": {
"deviceName": "iPhone 15",
"osVersion": "17",
"realMobile": "true"
}
}
}
]
```

Then:

```bash
yarn workspace headless-test browserstack:chonk-bench --matrix ./matrix.json
```

## Output

Each JSONL row contains:

- `device`
- `requested_capabilities`
- `browser_features`
- `input`
- `input_bytes`
- `proof_bytes`
- `verification_key_bytes`
- `prove_ms`
- `verify_ms`
- `total_ms`
- `verified`

Keep the raw JSONL with the PR or gist so later benchmark comparisons can reuse
the same flow/device inputs.
31 changes: 31 additions & 0 deletions barretenberg/acir_tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The easiest way to find how to run specific test(s):
```

This will show you the exact commands used in CI. For example:

```
c5f89...:ISOLATE=1 scripts/bb_prove_sol_verify.sh assert_statement --disable_zk
c5f89...:ISOLATE=1 scripts/bb_prove_sol_verify.sh assert_statement
Expand All @@ -35,6 +36,7 @@ c5f89... scripts/bb_prove.sh assert_statement
```

You can run any of these commands directly (ignore the hash prefix):

```bash
scripts/bb_prove.sh assert_statement
```
Expand All @@ -44,3 +46,32 @@ Programmatically, you can also do from root:
```bash
./barretenberg/acir_tests/bootstrap.sh test_cmds | grep assert_statement | ci3/parallelize
```

## BrowserStack Chonk Benchmark

The headless browser harness can run on-demand bb.js Chonk prove+verify
benchmarks on BrowserStack real devices. It serves the local
`browser-test-app/dest` bundle through BrowserStack Local, so the benchmark uses
the exact local bb.js build and pinned Chonk inputs without uploading artifacts.

```bash
cd barretenberg/cpp
./scripts/test_chonk_standalone_vks_havent_changed.sh --download_pinned_inputs

cd ../ts
yarn build:wasm
yarn build:browser

cd ../acir_tests
yarn workspace browser-test-app build

export BROWSERSTACK_USER_NAME=...
export BROWSERSTACK_ACCESS_KEY=...
yarn workspace headless-test browserstack:chonk-bench \
--output browserstack-chonk-bench.jsonl
```

The default matrix covers old and new iOS plus old and new Android. Use
`--matrix ./matrix.json` to pass BrowserStack capabilities for a custom device
set, `--flow <name>` to pick pinned flow folders, or `--input <path>` for
explicit `ivc-inputs.msgpack` files.
17 changes: 17 additions & 0 deletions barretenberg/acir_tests/browser-test-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,24 @@ function installChonkGlobal() {
return { proof, verificationKey };
}

async function verifyChonk(
proof: Uint8Array,
verificationKey: Uint8Array,
threads = 1
): Promise<boolean> {
const { AztecClientBackend } = await import("@aztec/bb.js");

const bb = await Barretenberg.new({ threads, logger: bbLogger });
const backend = new AztecClientBackend([], bb, []);
try {
return await backend.verify(proof, verificationKey);
} finally {
await bb.destroy();
}
}

(window as any).proveChonk = proveChonk;
(window as any).verifyChonk = verifyChonk;
}

installChonkGlobal();
Expand Down
10 changes: 8 additions & 2 deletions barretenberg/acir_tests/headless-test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@
"license": "MIT",
"type": "module",
"scripts": {
"start": "ts-node-esm ./src/index.ts"
"start": "ts-node-esm ./src/index.ts",
"browserstack:chonk-bench": "node --loader ts-node/esm ./src/browserstack-bench.ts",
"test:browserstack-bench": "node --loader ts-node/esm --test ./src/browserstack-bench.test.ts"
},
"dependencies": {
"browserstack-local": "^1.5.13",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"playwright": "1.49.0",
"puppeteer": "^24.22.3"
"puppeteer": "^24.22.3",
"selenium-webdriver": "^4.43.0"
},
"devDependencies": {
"@types/node": "20",
"@types/selenium-webdriver": "^4.35.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.2"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import assert from 'node:assert/strict';
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

import {
DEFAULT_DEVICE_MATRIX,
parseDeviceMatrix,
requireBrowserStackUsername,
resolveInputFiles,
startBenchServer,
} from './browserstack-bench.js';

const customMatrix = parseDeviceMatrix(
JSON.stringify([
{
name: 'old-ios',
capabilities: {
browserName: 'safari',
'bstack:options': {
deviceName: 'iPhone 11',
osVersion: '15',
realMobile: 'true',
},
},
},
]),
);

assert.equal(customMatrix.length, 1);
assert.equal(customMatrix[0].name, 'old-ios');
assert.equal(customMatrix[0].capabilities.browserName, 'safari');
assert.equal(customMatrix[0].capabilities['bstack:options'].deviceName, 'iPhone 11');

const resolvedFlows = resolveInputFiles(
'/repo/yarn-project/end-to-end/example-app-ivc-inputs-out',
['flow-a', 'flow-b'],
[],
);
assert.deepEqual(resolvedFlows, [
{
label: 'flow-a',
path: '/repo/yarn-project/end-to-end/example-app-ivc-inputs-out/flow-a/ivc-inputs.msgpack',
route: '/inputs/0-flow-a.msgpack',
},
{
label: 'flow-b',
path: '/repo/yarn-project/end-to-end/example-app-ivc-inputs-out/flow-b/ivc-inputs.msgpack',
route: '/inputs/1-flow-b.msgpack',
},
]);

const resolvedExplicitInputs = resolveInputFiles('/ignored', [], ['/tmp/a.msgpack', '/tmp/b.msgpack']);
assert.deepEqual(
resolvedExplicitInputs.map(input => ({ label: input.label, route: input.route })),
[
{ label: 'a', route: '/inputs/0-a.msgpack' },
{ label: 'b', route: '/inputs/1-b.msgpack' },
],
);

assert.equal(DEFAULT_DEVICE_MATRIX.length, 4);
assert.deepEqual(
DEFAULT_DEVICE_MATRIX.map(device => device.name),
['old-ios', 'new-ios', 'old-android', 'new-android'],
);

assert.equal(requireBrowserStackUsername({ BROWSERSTACK_USER_NAME: 'preferred-name' }), 'preferred-name');
assert.equal(requireBrowserStackUsername({ BROWSERSTACK_USERNAME: 'legacy-name' }), 'legacy-name');
assert.throws(() => requireBrowserStackUsername({}), /BROWSERSTACK_USER_NAME or BROWSERSTACK_USERNAME/);

const tempRoot = mkdtempSync(join(tmpdir(), 'browserstack-bench-test-'));
const distPath = join(tempRoot, 'dist');
const inputPath = join(tempRoot, 'ivc-inputs.msgpack');
mkdirSync(distPath, { recursive: true });
writeFileSync(join(distPath, 'index.html'), '<html>ok</html>');
writeFileSync(inputPath, Buffer.from([1, 2, 3, 4]));

const { port, server } = await startBenchServer(distPath, [
{ label: 'flow-a', path: inputPath, route: '/inputs/0-flow-a.msgpack' },
]);
try {
const indexResponse = await fetch(`http://127.0.0.1:${port}/index.html`);
assert.equal(indexResponse.status, 200);
assert.equal(indexResponse.headers.get('cross-origin-opener-policy'), 'same-origin');
assert.equal(indexResponse.headers.get('cross-origin-embedder-policy'), 'require-corp');
assert.equal(await indexResponse.text(), '<html>ok</html>');

const inputResponse = await fetch(`http://127.0.0.1:${port}/inputs/0-flow-a.msgpack`);
assert.equal(inputResponse.status, 200);
assert.deepEqual([...new Uint8Array(await inputResponse.arrayBuffer())], [1, 2, 3, 4]);

const traversalResponse = await fetch(`http://127.0.0.1:${port}/../package.json`);
assert.equal(traversalResponse.status, 404);
} finally {
server.close();
}
Loading
Loading