Skip to content

CSHLD-2073: Support v6 transaction size calculation in wasm-utxo - #448

Merged
Ranjna-G merged 2 commits into
masterfrom
CSHLD-2073-v6-unsigned-tx-size-support
Oct 27, 2026
Merged

Ranjna-G merged 2 commits into
masterfrom
CSHLD-2073-v6-unsigned-tx-size-support

Conversation

@Ranjna-G

@Ranjna-G Ranjna-G commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Makes extract_unsigned_zcash_transaction v6 (Ironwood)-aware, so getUnsignedTransaction() returns the embedded unsigned v6 transaction bytes instead of trapping unreachable — giving wallet-platform the same size-calculation primitive utxo-lib provides for v4. Unblocks replacing the serialized-PSBT-length proxy in the wallet-platform pending-approval fee check (CSHLD-786 follow-up) and, later, restoring a wasm-based custodial integrity comparison for v6.

Linear: CSHLD-2073

Changes

  • src/fixed_script_wallet/bitgo_psbt/zcash/mod.rs — extract_unsigned_zcash_transaction v6 branch: serializes the transparent skeleton plus current action data via encode_v6_transaction, the same construction unsigned_v6_txid commits to (bundle-less for transparent-only PSBTs). Two unsigned-state compromises, both size-honest and txid-neutral: per-action spend-auth slots padded with zero signatures and the Halo2 proof slot padded with zero bytes of orchard::Proof::expected_proof_size(n_actions) — each a fixed wire slot the finalized transaction carries anyway, so the byte length equals the final form's (an empty proof slot would under-price a shielded spend by several KB in fee checks built on this size). The proof itself is unknowable before combine_ironwood_proof; ZIP-244 commits to neither slot, so the txid is unchanged
  • compute_txid keeps refusing a v6 PSBT via an explicit guard: it applies double-SHA256 (ZIP-243, v4 semantics) to the extracted bytes, which would be a meaningless value for v6 — v6 txids are ZIP-244 via unsigned_v6_txid/v6_txid
  • tests/v6.rs — v4_paths_reject_a_v6_psbt updated: extract_unsigned_zcash_transaction leaves the refusal set (v6-aware, like serialize); new extract_unsigned_v6_tx_transparent_only_round_trips (decode + ZIP-244 txid equals unsigned_v6_txid) and extract_unsigned_v6_tx_shielded_succeeds_pre_proof (succeeds pre-signing/pre-proof, deterministic; decodes the extracted bytes and asserts the proof slot is padded to expected_proof_size(n_actions) and the padded slots leave the ZIP-244 txid unchanged)

Test Plan

  • cargo test --lib → 697 passed, 0 failed (includes the two new v6 extract tests and the updated refusal test)
  • cargo check --target wasm32-unknown-unknown — clean (dev-dependencies do not ship in the wasm artifact)
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo fmt -- --check — clean

CI advisory fix (3197d03)

  • Unblocks cargo deny against two newly published RustSec advisories (master fails the same check — advisory-DB drift, not a code change): ctutils 0.4.3 → 0.4.6 fixes RUSTSEC-2026-0385 (lockfile bump only, patched within the ^0.4 line); RUSTSEC-2026-0387 (crypto-bigint constant-time issues) is ignored in deny.toml. k256's Scalar add/sub/neg do reach crypto-bigint add_mod/sub_mod/neg_mod during MuSig2 partial signing, but the constant-time concern is moot for this target: wasm under a JS JIT gives no constant-time guarantees regardless of the crypto-bigint version. The ignore's reason text was corrected accordingly in CSHLD-2073: correct RUSTSEC-2026-0387 ignore reason in deny.toml #451, which also tracks security-team sign-off; cargo-deny warns on unmatched ignores once k256 moves to elliptic-curve 0.14+ and the entry can be deleted.

@linear-code

linear-code Bot commented Oct 27, 2026

Copy link
Copy Markdown

CSHLD-2073

@Ranjna-G

Copy link
Copy Markdown
Contributor Author

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Besides the inline findings, I checked the other callers of extract_unsigned_zcash_transaction, and neither one picks up the new v6 bytes. serialize() returns through serialize_v6() before it reaches the extraction call, and compute_txid now has its own is_ironwood_v6() guard, so v6 is still refused on both of these v4 paths.

Extended reasoning...

I checked the two other callers of the newly v6-aware extraction method in zcash/mod.rs (serialize and compute_txid) and confirmed that neither one encodes or double-SHA256-hashes v6 bytes on a v4 path.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟣 packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/mod.rs — pre-existing: JS callers still get a WASM unreachable trap from getUnsignedTransaction() on some v6 PSBTs, including any PSBT after combine_ironwood_proof. The new v6 branch of extract_unsigned_zcash_transaction can return Err, and get_unsigned_tx_bytes still calls .expect("Zcash unsigned tx encoding should not fail") on it. Fix: make get_unsigned_tx_bytes and the wasm get_unsigned_tx return Result<Vec<u8>, WasmUtxoError>, so every v6 error reaches JS as a catchable error rather than a panic.

    Why this was flagged

    The wasm get_unsigned_tx (src/wasm/fixed_script_wallet/mod.rs:1822) calls get_unsigned_tx_bytes, which .expects extract_unsigned_zcash_transaction() (bitgo_psbt/mod.rs:1734-1736). The v6 branch (zcash/mod.rs:222-241) returns Err in several cases. One is a PSBT after the wasm combine_ironwood_proof (zcash_psbt.rs:428), which calls mark_ironwood_extracted; require_no_shielded_output_ever_added then returns Err (ironwood.rs:623-627). Others are a missing consensus_branch_id in to_v6_transaction (ironwood.rs:880-881) and a PCZT decode or pczt_action_data failure. Each of these panics, and in wasm that is an unreachable trap that can leave the module instance unusable. The base branch trapped on every v6 PSBT. The PR is meant to stop that trap, but this path still has it, so the problem is partly pre-existing. Nothing catches the error because the return type is a bare Vec<u8>.

    Verification: pre-existing. The wasm get_unsigned_tx (src/wasm/fixed_script_wallet/mod.rs:1822-1823) returns self.psbt.get_unsigned_tx_bytes(), which (bitgo_psbt/mod.rs:1734-1736) still does .expect("Zcash unsigned tx encoding should not fail"). After combine_ironwood_proof, require_no_shielded_output_ever_added returns Err (ironwood.rs:623-628), so JS gets a WASM unreachable trap. On the base branch, every v6 PSBT already trapped by this route.

Comment thread packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/zcash/mod.rs
@Ranjna-G
Ranjna-G force-pushed the CSHLD-2073-v6-unsigned-tx-size-support branch 2 times, most recently from 8d79067 to 87adf1b Compare October 27, 2026 15:31
Make extract_unsigned_zcash_transaction v6-aware so
get_unsigned_tx_bytes (the JS getUnsignedTransaction()) can size and
diff unsigned Ironwood transactions the way utxo-lib does for v4 —
unblocking the wallet-platform pending-approval fee check, which
currently has to fall back to the serialized-PSBT length because the
wasm layer traps unreachable on Zcash PSBTs.

For a v6 PSBT the embedded unsigned v6 transaction is serialized via
encode_v6_transaction — the same construction unsigned_v6_txid commits
to: transparent skeleton plus current action data (bundle-less for
transparent-only PSBTs). Two unsigned-state compromises, both
size-honest and txid-neutral: per-action spend-auth slots are padded
with zero signatures and the Halo2 proof slot with zero bytes of
orchard::Proof::expected_proof_size(n_actions) — each a fixed wire slot
the finalized transaction carries anyway, so the byte length equals the
final form's (an empty proof slot would under-price a shielded spend by
several KB in fee checks built on this size). The proof itself is
unknowable before combine_ironwood_proof; ZIP-244 commits to neither
slot, so the txid is unchanged.

compute_txid keeps refusing a v6 PSBT, now via an explicit guard with
its own rationale: it applies double-SHA256 (ZIP-243 v4 semantics) to
the extracted bytes, and hashing v6 wire bytes that way would produce a
meaningless value — v6 txids are ZIP-244 via unsigned_v6_txid/v6_txid.

Ticket: CSHLD-2073
ctutils 0.4.3 -> 0.4.6 fixes RUSTSEC-2026-0385 (patched release in the
^0.4 line, lockfile only). RUSTSEC-2026-0387 (crypto-bigint constant-time
issues) is ignored in deny.toml: the patched version (>=0.7.6) is
unreachable behind k256 0.13.4 (musig2/secp), and wasm-utxo's k256 usage
does not expose the affected operations to attacker-controlled input.
Master fails the same check — both advisories were published after the
last green run, so this is advisory-DB drift, not a code change.

Ticket: CSHLD-2073
@Ranjna-G
Ranjna-G force-pushed the CSHLD-2073-v6-unsigned-tx-size-support branch from 87adf1b to 3859002 Compare October 27, 2026 15:32
@Ranjna-G
Ranjna-G marked this pull request as ready for review October 27, 2026 16:08
@Ranjna-G
Ranjna-G requested a review from a team as a code owner October 27, 2026 16:08

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@Ranjna-G
Ranjna-G enabled auto-merge October 27, 2026 16:08
Comment on lines +8 to +9
{ id = "RUSTSEC-2026-0387", reason = "crypto-bigint >=0.7.6 is unreachable behind k256 0.13.4 (musig2/secp); the affected constant-time ops are not exposed on attacker-controlled input in wasm-utxo's k256 usage" },
]

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.

k256's Scalar add, sub and neg go through crypto-bigint add_mod/sub_mod/neg_mod during MuSig2 partial signing. wasm running under a JS JIT gives no constant-time guarantees anyway. Please change the reason in a follow up PR and get an approval from security team

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Follow-up PR opened with the corrected reason: #451 — the ignore now records that k256's Scalar add/sub/neg do reach crypto-bigint add_mod/sub_mod/neg_mod during MuSig2 partial signing, and that the constant-time concern is moot because wasm under a JS JIT gives no constant-time guarantees regardless of the crypto-bigint version. Security-team approval is requested on that PR (tagged in the body with the exact claim to validate). The same rationale text in this PR's description has been corrected.

@Ranjna-G
Ranjna-G merged commit b08976d into master Oct 27, 2026
14 checks passed
@Ranjna-G
Ranjna-G deleted the CSHLD-2073-v6-unsigned-tx-size-support branch October 27, 2026 20:21
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