paritytech / paritytech/verifiablejs
Revisit the `one_shot` prover API: is the single-call flow still the best option, and does the name still fit?
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 1
- Forks
- 1
- Avg merge
- 1h 59m
- Merged PRs (30d)
- 4
Description
Summary
one_shot bundles secret derivation + ring open + proof create into a single WASM call. This is a revisit issue: is that still the best (or only) shape, does the original rationale still hold, and does the name still communicate intent?
Short version after digging through the verifiable crate:
- The "we can't serialize the secret" memory is correct, but that is not what forces a single call.
- The more interesting question is whether a prover should be able to open a ring once and create many proofs against it — which the crate already supports and we simply don't expose.
one_shotnames the implementation, not the intent;prove/prove_membershipwould read better.
Not proposing to rush a change — mostly want to record the findings and decide keep vs. improve.
What one_shot does today
new_secret(entropy) -> member_from_secret -> decode(members) -> open(...) -> create(...)
Bvv::open is re-run on every call. open does the ring setup (prover_key(&pks) over all ring members); create produces the per-context VRF output + ring proof.
The secret-serialization story, checked
GenerateVerifiable::Secretis bound only byClone— unlikeMember/Members/Proof/Signature, which are allFullCodec. So the secret genuinely cannot beEncode/Decode'd across the WASM boundary. ✔ the memory holds.- But we never need to serialize it.
new_secret(entropy) = Secret::from_seed(entropy)is cheap and deterministic, and the 32-byte entropy (already derived from the mnemonic on the caller side) is the serializable stand-in. So the secret constraint does not force bundlingopenwithcreate—createcan always re-derive the secret from entropy internally.
The thing actually worth revisiting: reusing the ring commitment
open(config, member, members) -> Commitment, andCommitment(=ProverState) isFullCodec/ serializable — it holds the ring-derivedprover_key, the expensive part (cost scales with ring size).create(commitment, secret, ctx, msg)is comparatively cheap per proof.- Because
one_shotre-runsopenevery call, a client proving repeatedly against the same (large, People-chain-sized) ring redoes the expensive setup each time. - The crate already exposes
open/createas separate#[cfg(feature = "prover")]trait methods — the split just isn't surfaced in the WASM binding. Note the verifier side already has the analogous split (members_root+validate_with_commitment); the prover side doesn't.
Options
- A — status quo. Keep
one_shotas the only prover entry point. Fine if provers rarely make >1 proof per ring, or ifopenis cheap enough at real ring sizes. - B — expose a split alongside it:
prover_open(ring_exponent, entropy, members) -> commitmentBytesprove_with_commitment(ring_exponent, entropy, commitmentBytes, context, message) -> { proof, alias }- keep
one_shotas a convenience wrapper. Open once, prove many.
- C — multi-context.
create_multi_contextalready yields N aliases from one proof against oneopen, which covers "many proofs, one open" when the contexts are known up front (not when they arrive over time).
Naming
one_shot describes the all-in-one implementation, not what it does. Candidates: prove, prove_membership, create_proof, generate_proof. If we add the split (B), a clean pairing is open + prove with one_shot kept as the convenience form. Leaning prove / prove_membership.
Security notes (so a future split doesn't reintroduce a footgun)
- The real prover needs good randomness for the proof blinding — the deanonymization footgun (
insecure-deterministic-proverzeroes the blinding and is trivially deanonymizable). In WASM this comes fromgetrandom'sjsfeature; must stay on. A cached commitment (option B) does not change this — randomness lives increate, notopen. secret-split(side-channel mitigation) is orthogonal: it's about splitting the secret scalar during multiplication, not serialization.
Questions for the team
- At real People-chain ring sizes, how expensive is
openvscreate? That decides whether B is worth it. - Is there a product flow that makes many proofs against one ring within a session (so caching the opened commitment actually helps)?
- If we rename: break now, or add
proveas an alias and deprecateone_shot?
Cross-reference
verifiable-dart mirrors this exact API (one_shot, same shape). Whatever we decide should stay consistent across the JS and Dart clients.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the WASM binding entry points for one_shot and the verifiable crate's Bvv::open and create methods; measure or inspect their costs at realistic ring sizes. Compare the existing verifier split and verifiable-dart API. Done means a documented decision on status quo, split exposure, and naming, with consistent client APIs if changes are approved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, wasm
- Domain
- api, backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100