Skip to content

feat(wasm-utxo): add Ironwood PCZT constructor/combine bridge - #341

Draft
veetragjain wants to merge 1 commit into
masterfrom
veetragjain/cshld-1293-wasm-utxo-implement-ironwood-pczt-constructor-combine-bridge
Draft

feat(wasm-utxo): add Ironwood PCZT constructor/combine bridge#341
veetragjain wants to merge 1 commit into
masterfrom
veetragjain/cshld-1293-wasm-utxo-implement-ironwood-pczt-constructor-combine-bridge

Conversation

@veetragjain

@veetragjain veetragjain commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

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.

@linear-code

linear-code Bot commented Jul 28, 2026

Copy link
Copy Markdown

CSHLD-1293

@OttoAllmendinger OttoAllmendinger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_pcztsend to proverwith_zkproofdeserialize_pcztcombine (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 carries binding_sig (public) instead of bsk (secret); or
  • strip bsk from the outbound payload and retain it locally, re-injecting after the prover returns (mirror of with_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_signature returning None is mapped to BindingSignatureFailed ("spend-auth signatures did not verify against the sighash"), but None can also indicate a missing/invalid bsk. The message may misdirect debugging — consider more neutral wording.
  • with_zkproof not 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

@veetragjain
veetragjain force-pushed the veetragjain/cshld-1293-wasm-utxo-implement-ironwood-pczt-constructor-combine-bridge branch 2 times, most recently from e904ab9 to 5818b99 Compare July 29, 2026 11:16
Base automatically changed from veetragjain/wasm-utxo-orchard-pczt-serialization to master July 29, 2026 13:24
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>
@OttoAllmendinger
OttoAllmendinger force-pushed the veetragjain/cshld-1293-wasm-utxo-implement-ironwood-pczt-constructor-combine-bridge branch from 5818b99 to c173d92 Compare July 29, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants