Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/wasm-utxo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions packages/wasm-utxo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ postcard = { version = "1.0", default-features = false, features = ["use-std"] }
# `ff::PrimeField::to_repr` for the one PCZT witness field (`alpha`, a Pallas scalar) that orchard
# exposes only as a curve scalar. Already in-tree via orchard/pasta_curves.
ff = "0.13"
# incrementalmerkletree::{Hashable, Level} — needed to fold a shard's leaves up to its local root
# (empty_leaf/empty_root/combine) in build_ironwood_witness_from_shard. orchard's public API does
# NOT re-export these (only MerklePath/MerkleHashOrchard), so this must be a direct dependency.
# Pinned to match orchard 0.15.0's own resolved pin (see Cargo.lock) so MerkleHashOrchard's
# `impl Hashable` resolves against the same crate version.
incrementalmerkletree = "0.8"

# Pinned to avoid RUSTSEC-2026-0204 (invalid pointer dereference in fmt::Pointer)
crossbeam-epoch = ">=0.9.20"
Expand Down
19 changes: 19 additions & 0 deletions packages/wasm-utxo/js/fixedScriptWallet/ZcashIronwoodWitness.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
IronwoodWitness as WasmIronwoodWitness,
ironwood_build_witness,
ironwood_build_witness_from_shard,
} from "../wasm/wasm_utxo.js";

/**
Expand Down Expand Up @@ -31,6 +32,24 @@ export class ZcashIronwoodWitness {
return new ZcashIronwoodWitness(ironwood_build_witness(cmx, position, authPath, anchor));
}

/**
* Build and validate a Merkle witness from one shard's leaves plus the sibling path above it,
* instead of a caller-precomputed 32-entry path.
* @throws Under the same conditions as {@link build}, plus if `shardHeight`/`upperPath`/
* `shardLeaves` are inconsistent with each other or don't cover `position`
*/
static buildFromShard(
position: number,
shardHeight: number,
shardLeaves: Uint8Array[],
upperPath: Uint8Array[],
anchor: Uint8Array,
): ZcashIronwoodWitness {
return new ZcashIronwoodWitness(
ironwood_build_witness_from_shard(position, shardHeight, shardLeaves, upperPath, anchor),
);
}

/** The leaf's position in the note commitment tree. */
get position(): number {
return this._wasm.position;
Expand Down
51 changes: 51 additions & 0 deletions packages/wasm-utxo/src/wasm/zcash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,57 @@ pub fn ironwood_build_witness(
})
}

/// Build and validate an Ironwood witness from one shard's leaves plus the sibling path above it,
/// instead of a caller-precomputed 32-entry path (see `ironwood_build_witness`).
///
/// `shardHeight` chooses how large the shard is (up to `2^shardHeight` leaves); `shardLeaves` must
/// be the dense, contiguous prefix of real leaves for that shard (index = shard-local position);
/// `upperPath` must have exactly `32 - shardHeight` entries. Throws under the same conditions as
/// `ironwood_build_witness`, plus if `shardHeight`/`upperPath`/`shardLeaves` are inconsistent with
/// each other or don't cover `position`.
#[wasm_bindgen]
pub fn ironwood_build_witness_from_shard(
position: u32,
shard_height: u8,
shard_leaves: Vec<js_sys::Uint8Array>,
upper_path: Vec<js_sys::Uint8Array>,
anchor: &[u8],
) -> Result<IronwoodWitness, WasmUtxoError> {
use crate::zcash::ironwood_build::{build_ironwood_witness_from_shard, ShardWitnessInput};

fn to_arrays(v: &[js_sys::Uint8Array], label: &str) -> Result<Vec<[u8; 32]>, WasmUtxoError> {
v.iter()
.enumerate()
.map(|(i, u)| {
u.to_vec().try_into().map_err(|_| {
WasmUtxoError::new(&format!(
"{label}[{i}] must be 32 bytes, got {}",
u.length()
))
})
})
.collect()
}

let anchor: [u8; 32] = anchor.try_into().map_err(|_| {
WasmUtxoError::new(&format!("anchor must be 32 bytes, got {}", anchor.len()))
})?;
let shard_leaves = to_arrays(&shard_leaves, "shardLeaves")?;

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.

shardLeaves are also not arrays but just a recursive tree

let upper_path = to_arrays(&upper_path, "upperPath")?;

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.

The cap (aka upper_path) is not an array


Ok(IronwoodWitness {
inner: build_ironwood_witness_from_shard(
&ShardWitnessInput {
position,
shard_height,
shard_leaves: &shard_leaves,
upper_path: &upper_path,
},
&anchor,
)?,
})
}

/// Resolve the Orchard/Ironwood receiver of a ZIP-316 unified address for `coin`'s network, as
/// its raw 43 bytes (diversifier + `pk_d`) — there is no scriptPubKey for a shielded output, so
/// this can't return script bytes uniformly and returns raw receiver bytes instead.
Expand Down
Loading
Loading