The bug: Ironwood migration-dummy breaks the helper send path
Post-NU6.3, a single/multi-note spend from the Orchard pool creates a wallet-controlled
zero-value "migration dummy" spend. create_pczt_from_proposal (in the pinned engine 42ffd0d)
does not stamp the ZIP-32 derivation on that dummy, so an external signer (our FROST flow, and
even a seed wallet's pczt sign) skips it as "not ours" → MissingSpendAuthSig at extract → the
broadcast fails.
This blocks the Architecture-B / convergence broadcast for any vault funded in the Orchard pool.
Where create is used (all hit the bug for Orchard-funded vaults)
orchestrator/src/send.rs::build_unproven → Payment: pczt::create (single output).
- same → Payroll:
konclave-signer build-payroll (multi output).
- Funding a vault (seed → vault) via the CLI also hits it (seed's own Orchard spend).
create is correct semantically (send X, change back) — the problem is only the Orchard dummy.
The two paths to fix it
(1) Workaround — create-max migration (interim, Phase-14-validated on mainnet)
zcash-devtool pczt create-max --address <self> <account> spends all notes to one output with
no dummy. Pattern:
- Migrate the vault's Orchard → Ironwood (a
create-max self-send, once). Funds land in the
Ironwood pool.
- Payments from the Ironwood pool then work with plain
create (the Ironwood pool signs its
own dummy correctly).
create-max does not fit a normal payment (spends everything to one address, no change), so it is
only for the migration step. Needs a small helper addition (a pczt::create_max wrapper + a "migrate
first" step in the send flow), gated so it can be removed once (2) lands.
(2) Proper fix — engine bump to librustzcash ≥ 51385a15 (definitive)
Commit 51385a15 ("Stamp ZIP 32 derivation on wallet-controlled zero-value spends in PCZTs", #2777,
merged 2026-07-27) makes plain create work — no create-max. But it is a major bump:
pczt 0.7 → 0.9, 406 commits past our pin, which breaks the byte-for-byte PCZT wire compat between
konclave-signer and zcash-devtool (ADR-0002). Requires rebuilding both at the new pin +
updating the signer's extract/inject to pczt 0.9. A dedicated engine-migration task.
Plan
- This issue / branch (
fix/ironwood-createmax-migration): add pczt::create_max + a migration
step to the helper, interim, so the convergence broadcast works now on the current engine.
- Separate issue (engine bump): move to librustzcash
51385a15 / pczt 0.9, re-align ADR-0002,
drop the workaround.
The branch is prepared with the create_max primitive so it is ready to complete/enable when we run
the money-gated broadcast.
The bug: Ironwood migration-dummy breaks the helper send path
Post-NU6.3, a single/multi-note spend from the Orchard pool creates a wallet-controlled
zero-value "migration dummy" spend.
create_pczt_from_proposal(in the pinned engine42ffd0d)does not stamp the ZIP-32 derivation on that dummy, so an external signer (our FROST flow, and
even a seed wallet's
pczt sign) skips it as "not ours" →MissingSpendAuthSigat extract → thebroadcast fails.
This blocks the Architecture-B / convergence broadcast for any vault funded in the Orchard pool.
Where
createis used (all hit the bug for Orchard-funded vaults)orchestrator/src/send.rs::build_unproven→ Payment:pczt::create(single output).konclave-signer build-payroll(multi output).createis correct semantically (send X, change back) — the problem is only the Orchard dummy.The two paths to fix it
(1) Workaround —
create-maxmigration (interim, Phase-14-validated on mainnet)zcash-devtool pczt create-max --address <self> <account>spends all notes to one output withno dummy. Pattern:
create-maxself-send, once). Funds land in theIronwood pool.
create(the Ironwood pool signs itsown dummy correctly).
create-maxdoes not fit a normal payment (spends everything to one address, no change), so it isonly for the migration step. Needs a small helper addition (a
pczt::create_maxwrapper + a "migratefirst" step in the send flow), gated so it can be removed once (2) lands.
(2) Proper fix — engine bump to librustzcash ≥
51385a15(definitive)Commit
51385a15("Stamp ZIP 32 derivation on wallet-controlled zero-value spends in PCZTs", #2777,merged 2026-07-27) makes plain
creatework — nocreate-max. But it is a major bump:pczt 0.7 → 0.9, 406 commits past our pin, which breaks the byte-for-byte PCZT wire compat betweenkonclave-signerandzcash-devtool(ADR-0002). Requires rebuilding both at the new pin +updating the signer's extract/inject to
pczt 0.9. A dedicated engine-migration task.Plan
fix/ironwood-createmax-migration): addpczt::create_max+ a migrationstep to the helper, interim, so the convergence broadcast works now on the current engine.
51385a15/pczt 0.9, re-align ADR-0002,drop the workaround.
The branch is prepared with the
create_maxprimitive so it is ready to complete/enable when we runthe money-gated broadcast.