Skip to content

Commit c57c932

Browse files
committed
add debug info to webapp output
Signed-off-by: Pau Escrich <p4u@dabax.net>
1 parent 54765d9 commit c57c932

6 files changed

Lines changed: 201 additions & 11 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ getrandom = "0.2"
4444

4545
[target.'cfg(target_arch = "wasm32")'.dependencies]
4646
wasm-bindgen = "0.2"
47+
js-sys = "0.3"
4748
getrandom = { version = "0.2", features = ["js"] }
4849
console_error_panic_hook = "0.1"
4950

src/wasm.rs

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
1414
use ecgfp5::curve::Point;
1515
use ecgfp5::scalar::Scalar;
16+
use js_sys::Date;
1617
use p3_field::{PrimeCharacteristicRing, PrimeField64};
1718
use p3_goldilocks::Goldilocks;
1819
use wasm_bindgen::prelude::*;
@@ -44,6 +45,43 @@ pub fn generate_keypair(sk_bytes: &[u8]) -> Vec<u8> {
4445
out
4546
}
4647

48+
#[wasm_bindgen]
49+
pub struct ProveFullResult {
50+
proof_data: Vec<u8>,
51+
decode_ms: f64,
52+
trace_ms: f64,
53+
prove_ms: f64,
54+
serialize_ms: f64,
55+
}
56+
57+
#[wasm_bindgen]
58+
impl ProveFullResult {
59+
#[wasm_bindgen(getter, js_name = proofData)]
60+
pub fn proof_data(&self) -> Vec<u8> {
61+
self.proof_data.clone()
62+
}
63+
64+
#[wasm_bindgen(getter, js_name = decodeMs)]
65+
pub fn decode_ms(&self) -> f64 {
66+
self.decode_ms
67+
}
68+
69+
#[wasm_bindgen(getter, js_name = traceMs)]
70+
pub fn trace_ms(&self) -> f64 {
71+
self.trace_ms
72+
}
73+
74+
#[wasm_bindgen(getter, js_name = proveMs)]
75+
pub fn prove_ms(&self) -> f64 {
76+
self.prove_ms
77+
}
78+
79+
#[wasm_bindgen(getter, js_name = serializeMs)]
80+
pub fn serialize_ms(&self) -> f64 {
81+
self.serialize_ms
82+
}
83+
}
84+
4785
/// Generate a full 8-field ballot proof (the main entry point for voting).
4886
///
4987
/// All byte arrays use u64 little-endian encoding. Returns the serialized
@@ -67,6 +105,29 @@ pub fn prove_full(
67105
weight: &[u8],
68106
ballot_mode: &[u8],
69107
) -> Result<Vec<u8>, JsValue> {
108+
Ok(prove_full_detailed(
109+
k_bytes,
110+
fields,
111+
pk_bytes,
112+
process_id,
113+
address,
114+
weight,
115+
ballot_mode,
116+
)?
117+
.proof_data)
118+
}
119+
120+
#[wasm_bindgen]
121+
pub fn prove_full_detailed(
122+
k_bytes: &[u8],
123+
fields: &[u8],
124+
pk_bytes: &[u8],
125+
process_id: &[u8],
126+
address: &[u8],
127+
weight: &[u8],
128+
ballot_mode: &[u8],
129+
) -> Result<ProveFullResult, JsValue> {
130+
let decode_start = now_ms();
70131
let k = Scalar::decode_reduce(k_bytes);
71132
let pk = decode_pk(pk_bytes).map_err(|e| JsValue::from_str(&e))?;
72133

@@ -120,10 +181,24 @@ pub fn prove_full(
120181
weight: Goldilocks::from_u64(w % Goldilocks::ORDER_U64),
121182
packed_ballot_mode: bm,
122183
};
184+
let decode_ms = now_ms() - decode_start;
185+
186+
let trace_start = now_ms();
187+
let (trace, public_values, _outputs) = crate::trace::generate_full_ballot_trace(&inputs);
188+
let trace_ms = now_ms() - trace_start;
123189

124-
let (ballot_proof, _outputs) = crate::prove_full_ballot(&inputs);
190+
let prove_start = now_ms();
191+
let config = crate::config::make_prover_config();
192+
let air = crate::air::BallotAir::new();
193+
let proof = p3_uni_stark::prove(&config, &air, trace, &public_values);
194+
let prove_ms = now_ms() - prove_start;
125195

196+
let ballot_proof = crate::BallotProof {
197+
proof,
198+
public_values,
199+
};
126200
// Serialize proof + public values
201+
let serialize_start = now_ms();
127202
let proof_bytes = postcard::to_allocvec(&ballot_proof.proof)
128203
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
129204

@@ -139,7 +214,18 @@ pub fn prove_full(
139214
out.extend_from_slice(&proof_len.to_le_bytes());
140215
out.extend_from_slice(&proof_bytes);
141216
out.extend_from_slice(&pv_bytes);
142-
Ok(out)
217+
let serialize_ms = now_ms() - serialize_start;
218+
Ok(ProveFullResult {
219+
proof_data: out,
220+
decode_ms,
221+
trace_ms,
222+
prove_ms,
223+
serialize_ms,
224+
})
225+
}
226+
227+
fn now_ms() -> f64 {
228+
Date::now()
143229
}
144230

145231
/// Verify a ballot proof from its wire-format byte representation.

webapp/src/main.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// the call() helper which returns a promise resolved when the worker replies.
66

77
import { normalizeChoices, packBallotMode } from './ballot_config.js';
8+
import { bytesToHex, formatProofPreview, summarizeTimings } from './proof_ui.js';
89

910
const logEl = document.getElementById('log');
1011
const metricKeygenEl = document.getElementById('metric-keygen');
@@ -71,11 +72,6 @@ function hexToBytes(hex) {
7172
return bytes;
7273
}
7374

74-
// Convert Uint8Array to lowercase hex string.
75-
function bytesToHex(bytes) {
76-
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
77-
}
78-
7975
// Encode a JS number as 8 bytes in little-endian u64 format.
8076
function u64ToLeBytes(val) {
8177
const buf = new ArrayBuffer(8);
@@ -142,6 +138,7 @@ window.doProve = async function() {
142138
setMetric(metricKeygenEl, `${keygenMs.toFixed(1)} ms`);
143139

144140
// Build binary inputs
141+
const packStart = performance.now();
145142
const kBytes = hexToBytes(kHex);
146143
const fields = new Uint8Array(8 * 8);
147144
for (let i = 0; i < 8; i++) {
@@ -158,6 +155,7 @@ window.doProve = async function() {
158155

159156
const weightBytes = u64ToLeBytes(weight);
160157
const ballotMode = packBallotMode(config);
158+
const inputPackMs = performance.now() - packStart;
161159

162160
log('\n--- Full Ballot Proof ---', 'info');
163161
log(`Choices: [${choices.slice(0, config.numFields).join(', ')}]`);
@@ -172,17 +170,35 @@ window.doProve = async function() {
172170
});
173171
proofData = result.proofData;
174172
const elapsed = performance.now() - start;
173+
const renderStart = performance.now();
174+
const timings = summarizeTimings({
175+
inputPackMs,
176+
workerWallMs: result.timings?.workerWallMs ?? elapsed,
177+
wasmDecodeMs: result.timings?.wasmDecodeMs ?? 0,
178+
wasmTraceMs: result.timings?.wasmTraceMs ?? 0,
179+
wasmProveMs: result.timings?.wasmProveMs ?? 0,
180+
wasmSerializeMs: result.timings?.wasmSerializeMs ?? 0,
181+
mainRenderMs: 0,
182+
});
175183

176184
log(`✅ Proof generated!`, 'success');
177185
log(`Proof size: ${proofData.length} bytes (${(proofData.length / 1024).toFixed(1)} KB)`);
178186
log(`⏱ ${(elapsed / 1000).toFixed(1)}s`, 'timing');
187+
log(
188+
`Timing breakdown: pack=${timings.inputPackMs}ms worker=${timings.workerWallMs}ms ` +
189+
`decode=${timings.wasmDecodeMs}ms trace=${timings.wasmTraceMs}ms ` +
190+
`prove=${timings.wasmProveMs}ms serialize=${timings.wasmSerializeMs}ms`,
191+
'timing',
192+
);
179193
setMetric(metricProveEl, `${(elapsed / 1000).toFixed(2)} s`);
180194
metricNoteEl.textContent =
181195
`FRI config: blowup 8, 34 queries, 0 PoW bits. Last proof size: ${(proofData.length / 1024).toFixed(1)} KB.`;
182196

183197
// Show proof data
184198
document.getElementById('proof-card').style.display = 'block';
185-
document.getElementById('proof-data').textContent = bytesToHex(proofData);
199+
document.getElementById('proof-data').textContent = formatProofPreview(proofData);
200+
timings.mainRenderMs = (performance.now() - renderStart).toFixed(2);
201+
log(`Render=${timings.mainRenderMs}ms`, 'timing');
186202

187203
document.getElementById('btn-prove').disabled = false;
188204
document.getElementById('btn-verify').disabled = false;

webapp/src/proof_ui.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export function bytesToHex(bytes) {
2+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
3+
}
4+
5+
export function formatProofPreview(bytes, prefixBytes = 32) {
6+
const shown = bytes.subarray(0, Math.min(bytes.length, prefixBytes));
7+
return `${bytesToHex(shown)}...`;
8+
}
9+
10+
export function summarizeTimings(timings) {
11+
return Object.fromEntries(
12+
Object.entries(timings).map(([key, value]) => [key, Number(value).toFixed(2)]),
13+
);
14+
}
15+
16+
export function buildWorkerResultMessage(id, proofData, timings) {
17+
return {
18+
message: {
19+
type: 'result',
20+
id,
21+
result: { proofData, timings },
22+
},
23+
transfer: [proofData.buffer],
24+
};
25+
}

webapp/src/worker.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
// sends an 'init' message before that finishes, we just re-send the
99
// ready/error status once we know it.
1010

11-
import init, { generate_keypair, prove_full, verify } from '../../pkg/davinci_stark.js';
11+
import init, { generate_keypair, prove_full_detailed, verify } from '../../pkg/davinci_stark.js';
1212
import wasmUrl from '../../pkg/davinci_stark_bg.wasm?url';
13+
import { buildWorkerResultMessage } from './proof_ui.js';
1314

1415
let ready = false;
1516
let initError = null;
@@ -55,8 +56,26 @@ self.onmessage = async (e) => {
5556

5657
case 'prove': {
5758
const { kBytes, fields, pkBytes, processId, address, weight, ballotMode } = payload;
58-
const proofData = prove_full(kBytes, fields, pkBytes, processId, address, weight, ballotMode);
59-
self.postMessage({ type: 'result', id, result: { proofData } });
59+
const workerStart = performance.now();
60+
const result = prove_full_detailed(
61+
kBytes,
62+
fields,
63+
pkBytes,
64+
processId,
65+
address,
66+
weight,
67+
ballotMode,
68+
);
69+
const proofData = result.proofData;
70+
const timings = {
71+
wasmDecodeMs: result.decodeMs,
72+
wasmTraceMs: result.traceMs,
73+
wasmProveMs: result.proveMs,
74+
wasmSerializeMs: result.serializeMs,
75+
workerWallMs: performance.now() - workerStart,
76+
};
77+
const message = buildWorkerResultMessage(id, proofData, timings);
78+
self.postMessage(message.message, message.transfer);
6079
break;
6180
}
6281

webapp/tests/proof_ui.test.mjs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import { buildWorkerResultMessage, formatProofPreview, summarizeTimings } from '../src/proof_ui.js';
4+
5+
test('formatProofPreview keeps only a short proof prefix', () => {
6+
const bytes = new Uint8Array(Array.from({ length: 32 }, (_, i) => i));
7+
const preview = formatProofPreview(bytes, 8);
8+
assert.equal(preview, '0001020304050607...');
9+
});
10+
11+
test('summarizeTimings rounds timing fields for display', () => {
12+
const summary = summarizeTimings({
13+
inputPackMs: 1.234,
14+
workerWallMs: 20.987,
15+
wasmDecodeMs: 0.456,
16+
wasmTraceMs: 5.432,
17+
wasmProveMs: 99.999,
18+
wasmSerializeMs: 3.333,
19+
mainRenderMs: 2.111,
20+
});
21+
22+
assert.deepEqual(summary, {
23+
inputPackMs: '1.23',
24+
workerWallMs: '20.99',
25+
wasmDecodeMs: '0.46',
26+
wasmTraceMs: '5.43',
27+
wasmProveMs: '100.00',
28+
wasmSerializeMs: '3.33',
29+
mainRenderMs: '2.11',
30+
});
31+
});
32+
33+
test('buildWorkerResultMessage transfers the proof buffer', () => {
34+
const proofData = new Uint8Array([1, 2, 3, 4]);
35+
const timings = { wasmProveMs: 12.5 };
36+
const { message, transfer } = buildWorkerResultMessage(7, proofData, timings);
37+
38+
assert.equal(message.type, 'result');
39+
assert.equal(message.id, 7);
40+
assert.equal(message.result.proofData, proofData);
41+
assert.deepEqual(message.result.timings, timings);
42+
assert.deepEqual(transfer, [proofData.buffer]);
43+
});

0 commit comments

Comments
 (0)