feat(wasm-utxo): add Ironwood PCZT constructor/combine bridge - #341
Conversation
OttoAllmendinger
left a comment
There was a problem hiding this comment.
Review
Overview
Adds zcash::ironwood_build, wiring the three local orchard PCZT roles (Constructor → IO-Finalizer/Signer → Extractor) to the v6 IronwoodBundle, with the Prover delegated externally, plus with_zkproof to splice the proof into the serialized PCZT. The role separation, the "why the proof can be filled in after signing" doc, and the type-level R: RngCore + CryptoRng bound are all clean. The golden_shield1zec_shielded_signatures_verify test is excellent — it verifies both the real dummy spend-auth signature and the real binding signature against compute_v6_sig_digest, an independent byte-correctness oracle for the shielded digest.
Issues
1. (Security) bsk transits the external proof service — this confirms the open question from #339.
The flow is finalize_shield_io (sets bsk + spend-auth sigs) → serialize_pczt → send to prover → with_zkproof → deserialize_pczt → combine (calls apply_binding_signature, which needs bsk). Because combine runs after the prover round-trip and consumes bsk, bsk must be serialized into the payload the proof service sees (with_zkproof's docstring: "wasm-utxo sends the signed PCZT", and that PCZT carries bsk per #339's BundleWire). So the binding-signature signing key is disclosed to a service whose only job is proving.
By this PR's own docstring the binding signature "signs the same build-time sighash and has no dependency on the [proof / transparent signatures]." That means it can be applied before delegating to the prover, so the disclosure is avoidable. Two options:
- apply the binding signature locally in
finalize/pre-serialize, so the outbound PCZT carriesbinding_sig(public) instead ofbsk(secret); or - strip
bskfrom the outbound payload and retain it locally, re-injecting after the prover returns (mirror ofwith_zkproof).
Not obviously exploitable for theft (transparent inputs still need their own signatures the service lacks), but it hands binding-signature authority to a semi-trusted service for no functional reason — a least-privilege regression worth closing. Can you confirm whether the production prover call strips bsk?
2. (Test coverage) The stack still never exercises the #338 unshield digest.
This golden test — like the transparent one in #338 — uses the shield1zec fixture, which has one transparent input. So compute_v6_sig_digest's inputs.is_empty() fallback (#338 finding: wrong for outputs-but-no-inputs) remains untested at every layer that consumes it. If unshield (shielded → transparent) is a supported v6 flow, it's currently unverified end-to-end.
Minor
combine:apply_binding_signaturereturningNoneis mapped toBindingSignatureFailed("spend-auth signatures did not verify against the sighash"), butNonecan also indicate a missing/invalidbsk. The message may misdirect debugging — consider more neutral wording.with_zkproofnot validating proof length (deferred to the Extractor) is reasonable and documented; fine as-is.
Praise
Dual-signature golden oracle, CSPRNG bound at the type level, nonempty correctly scoped to native dev-deps, and the with_zkproof idempotence / version-rejection tests are all nicely done.
Note: finding #1 is really a property of the #339 wire format and this PR's combine ordering together — a fix likely touches both BundleWire.bsk handling (#339) and the combine-after-prover ordering here.
Generated by Claude Code
e904ab9 to
5818b99
Compare
Add `zcash::ironwood_build`, the bridge between the `orchard` PCZT roles and the v6 `IronwoodBundle` wire type, for building transparent -> Ironwood shielding transactions without linking the halo2 prover: - `construct_shield_pczt` (Constructor): one output note + dummy spend as an `orchard::pczt::Bundle`, action data fixed, no proof/sigs. - `finalize_shield_io` (IO Finalizer/Signer): derive `bsk`, sign dummy spends over the shielded sighash. - `pczt_action_data`: effects-only view (commitments/ciphertexts/flags/value/ anchor) for computing the ZIP-244 sighash and txid. - `combine` (Transaction Extractor): given the signed+proven PCZT, verify spend-auth sigs, apply the binding signature, map to an encodable `IronwoodBundle`. The Prover role is delegated to an external service; its proof bytes are spliced into the serialized PCZT via `ironwood_pczt::with_zkproof`. Tests (all no-circuit): build->finalize->combine produces an encodable v6 tx with a stable txid; the combined tx cross-checks against zebra-chain; and a golden oracle reconstructs the orchard bundle from the real on-chain `shield1zec` action data and verifies both the real dummy spend-auth signature and the real binding signature against `compute_v6_sig_digest` — the shielded twin of PR1's transparent sighash oracle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5818b99 to
c173d92
Compare
Add
zcash::ironwood_build, the bridge between theorchardPCZT roles and the v6IronwoodBundlewire type, for building transparent -> Ironwood shielding transactions without linking the halo2 prover:construct_shield_pczt(Constructor): one output note + dummy spend as anorchard::pczt::Bundle, action data fixed, no proof/sigs.finalize_shield_io(IO Finalizer/Signer): derivebsk, sign dummy spends over the shielded sighash.pczt_action_data: effects-only view (commitments/ciphertexts/flags/value/ anchor) for computing the ZIP-244 sighash and txid.combine(Transaction Extractor): given the signed+proven PCZT, verify spend-auth sigs, apply the binding signature, map to an encodableIronwoodBundle.The Prover role is delegated to an external service; its proof bytes are spliced into the serialized PCZT via
ironwood_pczt::with_zkproof.Tests (all no-circuit): build->finalize->combine produces an encodable v6 tx with a stable txid; the combined tx cross-checks against zebra-chain; and a golden oracle reconstructs the orchard bundle from the real on chain
shield1zecaction data and verifies both the real dummy spend-auth signature and the real binding signature againstcompute_v6_sig_digest— the shielded twin of PR1's transparent sighash oracle.