Skip to content

feat: shielded TX support#6

Merged
karim-en merged 118 commits intoomni-mainfrom
orchard_tx
Jan 12, 2026
Merged

feat: shielded TX support#6
karim-en merged 118 commits intoomni-mainfrom
orchard_tx

Conversation

@karim-en
Copy link
Collaborator

@karim-en karim-en commented Sep 15, 2025

What This Does

Adds security validation for Orchard (shielded) withdrawals in the Zcash bridge. Users can now withdraw to Unified Addresses with proper enforcement that bundles go to the right recipient with the right amount.

The Problem

The orchard_tx branch added basic Orchard transaction support, but had TODOs around validation:

  • No verification that Orchard outputs go to the intended recipient
  • No verification that amounts match what the user is withdrawing
  • No protection against bundle tampering after transparent signatures are collected

This meant a malicious user could potentially redirect shielded funds or manipulate amounts.

The Solution

1. OVK-Based Output Recovery

Implemented orchard_policy.rs module that uses a hardcoded Bridge OVK to recover and validate Orchard outputs:

pub const BRIDGE_OVK: [u8; 32] = [0u8; 32];

pub fn validate_orchard_bundle(
    bundle: &Bundle<Authorized, ZatBalance>,
    expected_recipient: &str,
    expected_amount: u64,
    chain: &Chain,
) {
    // Recover output using Bridge OVK
    let (recovered_amount, recovered_addr) = recover_orchard_output(bundle);

    // Validate matches expectations
    require!(recovered_amount == expected_amount, "Orchard amount mismatch");
    require!(recovered_addr == expected_addr_bytes, "Orchard recipient mismatch");
}

Trade-off: The OVK is public (contract code is public), so anyone can recover these outputs. This is acceptable for the bridge use case - we're enforcing policy, not providing privacy.

2. ZIP-244 Sighash Binding

The Orchard bundle is now included in the TransactionData used for transparent signature generation. This means:

  • Any modification to the Orchard bytes invalidates all transparent signatures
  • Prevents post-signing bundle swap attacks

3. Orchard-Only Withdrawals

Added support for withdrawals with zero transparent outputs (all funds → shielded pool):

if psbt.get_output_num() == 0 {
    // Orchard-only case: skip transparent validation
    let gas_fee = max_gas_fee.unwrap_or(10000);
    let orchard_amount = amount - withdraw_fee - gas_fee;
    (orchard_amount, gas_fee)
} else {
    // Normal transparent output validation
    self.check_withdraw_psbt_valid(...)
}

4. Unified Address Validation

Enforces that Unified Addresses must come with Orchard bundles:

if target_btc_address.starts_with("u1") || target_btc_address.starts_with("u") {
    require!(orchard_bundle.is_some(),
        "Unified Address provided without Orchard bundle. \
         Either provide both or use a transparent Zcash address (starts with t1/t3)");
}

Test Coverage

Added 5 comprehensive tests covering:

  • Happy path e2e withdrawal with OVK validation
  • Amount mismatch rejection
  • Recipient mismatch rejection (different spending keys → different recipients)
  • Unified Address without bundle rejection
  • Bundle serialization in final transaction

Note: Tests cache generated Orchard bundles because proof generation takes ~80-90s. First run is slow, subsequent runs are fast (~16s total).

# Build first
cargo near build non-reproducible-wasm --features zcash --out-dir res --no-abi

# Run tests
cargo test -p satoshi-bridge --features zcash \
  --test test_orchard_validation \
  --test test_orchard_withdrawal \
  -- --test-threads=1 --nocapture

Breaking Changes

Event Encoding

Changed SignedBtcTransaction event from hex to base64 to stay under NEAR's 16KB log limit:

- tx_bytes: &'a Vec<u8>
+ tx_bytes_base64: String

Overhead reduced from 2x (hex) to 1.33x (base64). Relayer will need to update the parser.

@karim-en karim-en requested a review from olga24912 January 7, 2026 14:05
@karim-en karim-en requested review from frolvanya and kiseln January 7, 2026 17:09
@karim-en karim-en changed the base branch from zcash_support to omni-main January 7, 2026 17:14
@karim-en karim-en merged commit 533e461 into omni-main Jan 12, 2026
1 check passed
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.

5 participants