From f6a0595392659641144a307685937a6d35f62d9c Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Fri, 6 Mar 2026 15:47:54 +0100 Subject: [PATCH 1/3] Expose PCZT getters needed for shielded voting Co-Authored-By: roman Co-Authored-By: Claude Code Remove duplicate shielded_sighash getter in pczt signer Bad merge left two identical methods. Keep the one with the fuller doc comment, fix stray braces in doc comments on nearby methods. --- pczt/CHANGELOG.md | 2 ++ pczt/src/orchard.rs | 1 + pczt/src/roles/signer/mod.rs | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pczt/CHANGELOG.md b/pczt/CHANGELOG.md index 9ebc18732b..55d2f55353 100644 --- a/pczt/CHANGELOG.md +++ b/pczt/CHANGELOG.md @@ -13,6 +13,8 @@ workspace. ### Added - `pczt::ExtractError` - `pczt::EffectsOnly` +- `pczt::orchard::Spend::spend_auth_sig` getter (via `getset`). +- `pczt::roles::signer::Signer::shielded_sighash` getter. - `pczt::roles::signer`: - `Signer::sighash` - `Signer::append_transparent_signature` diff --git a/pczt/src/orchard.rs b/pczt/src/orchard.rs index ebe4cd7c73..0a9cc8fa5b 100644 --- a/pczt/src/orchard.rs +++ b/pczt/src/orchard.rs @@ -111,6 +111,7 @@ pub struct Spend { /// /// This is set by the Signer. #[serde_as(as = "Option<[_; 64]>")] + #[getset(get = "pub")] pub(crate) spend_auth_sig: Option<[u8; 64]>, /// The [raw encoding] of the Orchard payment address that received the note being spent. diff --git a/pczt/src/roles/signer/mod.rs b/pczt/src/roles/signer/mod.rs index 4235a3b473..82820232f0 100644 --- a/pczt/src/roles/signer/mod.rs +++ b/pczt/src/roles/signer/mod.rs @@ -80,7 +80,7 @@ impl Signer { /// Calculates the signature digest that must be signed to authorize shielded spends. /// /// This can be used to produce a signature externally suitable for passing to e.g. - /// [`Self::apply_orchard_signature`].} + /// [`Self::apply_orchard_signature`]. pub fn shielded_sighash(&self) -> [u8; 32] { self.shielded_sighash } @@ -89,7 +89,7 @@ impl Signer { /// spend at the given index. /// /// This can be used to produce a signature externally suitable for passing to e.g. - /// [`Self::append_transparent_signature`].} + /// [`Self::append_transparent_signature`]. /// /// Returns an error if `index` is invalid for this PCZT. pub fn transparent_sighash(&self, index: usize) -> Result<[u8; 32], Error> { From 1bc3a279e3c5504458a104c4295e192ad38a1e2a Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Tue, 10 Mar 2026 23:51:48 +0100 Subject: [PATCH 2/3] Add governance wallet methods for historical note queries and witness generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WalletDb gains two new methods (not on traits — governance-specific): - get_unspent_orchard_notes_at_historical_height(account, height): returns all Orchard notes received at or before the given height and unspent as of that height. Backward-looking query for voting snapshots, unlike select_unspent_notes which is forward-looking. - generate_orchard_witnesses_at_historical_height(positions, frontier, height): copies wallet shard data to ephemeral in-memory DB, inserts the frontier as a checkpoint, and generates Merkle witnesses. Wallet DB is strictly read-only. Co-Authored-By: roman Co-Authored-By: Claude Code --- zcash_client_sqlite/CHANGELOG.md | 4 + zcash_client_sqlite/src/lib.rs | 59 ++++ .../src/wallet/commitment_tree.rs | 295 ++++++++++++++++++ zcash_client_sqlite/src/wallet/orchard.rs | 125 ++++++++ 4 files changed, 483 insertions(+) diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index 07b7731dd1..b66ad7ba90 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -27,6 +27,10 @@ workspace. - `zcash_client_sqlite::AccountRef` is now public. - Implement standalone P2SH address import support - `impl zcash_client_backend::data_api::WalletWrite::import_standalone_transparent_script()` +- `WalletDb::get_unspent_orchard_notes_at_historical_height` returns Orchard + notes received and unspent as of a given height. +- `WalletDb::generate_orchard_witnesses_at_historical_height` generates Merkle + witnesses at a historical height using an ephemeral in-memory tree. ### Changed - Migrated to `orchard 0.12`, `sapling-crypto 0.6`. diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 798c359bd1..abc6fa4149 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -2237,6 +2237,65 @@ impl<'a, C: Borrow>, P: consensus::Parameters, CL: Clo } } +// --- Governance-specific methods --- +// +// These methods support Zcash shielded voting and are not part of the +// general-purpose wallet traits. They provide note queries and witness +// generation at a historical height. + +#[cfg(feature = "orchard")] +impl, P: consensus::Parameters, CL, R> WalletDb { + /// Return all Orchard notes received at or before `height` + /// and unspent as of that height, for the given account. + /// + /// Unlike [`InputSource::select_unspent_notes`] (which applies confirmation, + /// dust, and expiry filters for transaction construction), this returns every + /// note that existed and was unspent at the given height. + pub fn get_unspent_orchard_notes_at_historical_height( + &self, + account: AccountUuid, + height: BlockHeight, + ) -> Result>, SqliteClientError> { + wallet::orchard::get_unspent_orchard_notes_at_historical_height( + self.conn.borrow(), + &self.params, + account, + height, + ) + } + + /// Generate Orchard Merkle witnesses at a historical height. + /// + /// Copies the wallet's Orchard shard data into an ephemeral in-memory + /// database, inserts the provided frontier as a checkpoint, and generates + /// a witness for each of the given note positions. + /// + /// The wallet DB is strictly read-only — shard data is copied, not modified. + pub fn generate_orchard_witnesses_at_historical_height( + &self, + note_positions: &[Position], + frontier_at_height: incrementalmerkletree::frontier::NonEmptyFrontier< + orchard::tree::MerkleHashOrchard, + >, + height: BlockHeight, + ) -> Result< + Vec< + incrementalmerkletree::MerklePath< + orchard::tree::MerkleHashOrchard, + { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, + >, + >, + SqliteClientError, + > { + wallet::commitment_tree::generate_orchard_witnesses_at_historical_height( + self.conn.borrow(), + note_positions, + frontier_at_height, + height, + ) + } +} + /// A handle for the SQLite block source. pub struct BlockDb(rusqlite::Connection); diff --git a/zcash_client_sqlite/src/wallet/commitment_tree.rs b/zcash_client_sqlite/src/wallet/commitment_tree.rs index 6f4104c964..e7dff44ae4 100644 --- a/zcash_client_sqlite/src/wallet/commitment_tree.rs +++ b/zcash_client_sqlite/src/wallet/commitment_tree.rs @@ -1140,6 +1140,203 @@ pub(crate) fn check_witnesses( Ok(scan_ranges) } +/// Create the shard-tree schema tables in an existing connection. +/// +/// The four tables (`{prefix}_tree_shards`, `{prefix}_tree_cap`, +/// `{prefix}_tree_checkpoints`, `{prefix}_tree_checkpoint_marks_removed`) +/// match the schema used by [`SqliteShardStore`]. +#[cfg(feature = "orchard")] +fn create_tree_tables( + conn: &rusqlite::Connection, + table_prefix: &str, +) -> Result<(), rusqlite::Error> { + conn.execute_batch(&format!( + "CREATE TABLE {table_prefix}_tree_shards ( + shard_index INTEGER PRIMARY KEY, + subtree_end_height INTEGER, + root_hash BLOB, + shard_data BLOB, + contains_marked INTEGER, + CONSTRAINT root_unique UNIQUE (root_hash) + ); + CREATE TABLE {table_prefix}_tree_cap ( + cap_id INTEGER PRIMARY KEY, + cap_data BLOB NOT NULL + ); + CREATE TABLE {table_prefix}_tree_checkpoints ( + checkpoint_id INTEGER PRIMARY KEY, + position INTEGER + ); + CREATE TABLE {table_prefix}_tree_checkpoint_marks_removed ( + checkpoint_id INTEGER NOT NULL, + mark_removed_position INTEGER NOT NULL, + FOREIGN KEY (checkpoint_id) REFERENCES {table_prefix}_tree_checkpoints(checkpoint_id) + ON DELETE CASCADE, + CONSTRAINT spend_position_unique UNIQUE (checkpoint_id, mark_removed_position) + );" + )) +} + +/// Copy shard and cap data for a commitment tree from one connection to another. +/// +/// Both connections must already have the `{prefix}_tree_shards` and +/// `{prefix}_tree_cap` tables (see [`create_tree_tables`]). +#[cfg(feature = "orchard")] +fn copy_tree_data( + src: &rusqlite::Connection, + dst: &rusqlite::Connection, + table_prefix: &str, +) -> Result<(), rusqlite::Error> { + use rusqlite::types::Value; + + let mut stmt = src.prepare(&format!( + "SELECT shard_index, subtree_end_height, root_hash, shard_data, contains_marked + FROM {table_prefix}_tree_shards" + ))?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + dst.execute( + &format!( + "INSERT INTO {table_prefix}_tree_shards + (shard_index, subtree_end_height, root_hash, shard_data, contains_marked) + VALUES (?1, ?2, ?3, ?4, ?5)" + ), + rusqlite::params![ + row.get::<_, Value>(0)?, + row.get::<_, Value>(1)?, + row.get::<_, Value>(2)?, + row.get::<_, Value>(3)?, + row.get::<_, Value>(4)?, + ], + )?; + } + + let mut stmt = src.prepare(&format!( + "SELECT cap_id, cap_data FROM {table_prefix}_tree_cap" + ))?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + dst.execute( + &format!("INSERT INTO {table_prefix}_tree_cap (cap_id, cap_data) VALUES (?1, ?2)"), + rusqlite::params![row.get::<_, Value>(0)?, row.get::<_, Value>(1)?], + )?; + } + + Ok(()) +} + +/// Generate Orchard Merkle witnesses at a historical height. +/// +/// Copies the wallet's Orchard shard data into an ephemeral in-memory database, +/// inserts the provided frontier at that height as a checkpoint, and +/// generates a witness for each of the given note positions. +/// +/// It is assumed that the caller provides the valid frontier at the given height. +/// +/// How it works: +/// To construct witnesses at a historical height, we need: +/// 1. Authentication path within each note's shard — the scanner marks the +/// wallet's notes as MARKED, preventing them and their siblings within a +/// shard from being pruned. +/// 2. Cap — the upper tree above the shard level. +/// 3. Frontier — the right edge at the historical height. It lets ShardTree +/// know exactly where the tree ended at that height. +/// +/// The wallet automatically prunes the tree after PRUNING_DEPTH checkpoints. +/// These three components are sufficient to reconstruct the tree structure +/// needed for witness generation even after pruning has occurred. +/// +/// The wallet DB is strictly read-only. Shard data is copied, not modified. +/// An ephemeral in-memory database is created to avoid tampering with the primary wallet DB. +/// +/// Example application: token holder voting. The wallet tree may have advanced past +/// the historical height, but we need witnesses anchored at that frontier. +#[cfg(feature = "orchard")] +pub fn generate_orchard_witnesses_at_historical_height( + conn: &rusqlite::Connection, + note_positions: &[Position], + frontier_at_height: incrementalmerkletree::frontier::NonEmptyFrontier< + orchard::tree::MerkleHashOrchard, + >, + height: BlockHeight, +) -> Result< + Vec< + incrementalmerkletree::MerklePath< + orchard::tree::MerkleHashOrchard, + { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, + >, + >, + SqliteClientError, +> { + use incrementalmerkletree::Marking; + use shardtree::ShardTree; + use shardtree::store::Checkpoint; + use zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT; + + let table_prefix = "orchard"; + let frontier_position = frontier_at_height.position(); + + let mem_conn = rusqlite::Connection::open_in_memory().map_err(SqliteClientError::DbError)?; + create_tree_tables(&mem_conn, table_prefix).map_err(SqliteClientError::DbError)?; + copy_tree_data(conn, &mem_conn, table_prefix).map_err(SqliteClientError::DbError)?; + + let store = SqliteShardStore::< + _, + orchard::tree::MerkleHashOrchard, + ORCHARD_SHARD_HEIGHT, + >::from_connection(mem_conn, table_prefix) + .map_err(SqliteClientError::DbError)?; + + let mut tree = + ShardTree::<_, { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, ORCHARD_SHARD_HEIGHT>::new( + store, 100, + ); + + // Insert frontier + checkpoint + tree.insert_frontier_nodes( + frontier_at_height, + Retention::Checkpoint { + id: height, + marking: Marking::None, + }, + ) + .map_err(|e| { + SqliteClientError::CorruptedData(format!("failed to insert frontier nodes: {}", e)) + })?; + + tree.store_mut() + .add_checkpoint(height, Checkpoint::at_position(frontier_position)) + .map_err(|e| { + SqliteClientError::CorruptedData(format!("failed to add checkpoint: {}", e)) + })?; + + // Generate witness per note position + let mut witnesses = Vec::with_capacity(note_positions.len()); + for &pos in note_positions { + let merkle_path = tree + .witness_at_checkpoint_id(pos, &height) + .map_err(|e| { + SqliteClientError::CorruptedData(format!( + "failed to generate witness for position {}: {} \ + (wallet may need to sync through the target height)", + u64::from(pos), + e + )) + })? + .ok_or_else(|| { + SqliteClientError::CorruptedData(format!( + "no witness available for position {} \ + (wallet missing shard data — sync through the target height)", + u64::from(pos) + )) + })?; + + witnesses.push(merkle_path); + } + + Ok(witnesses) +} + #[cfg(test)] mod tests { use tempfile::NamedTempFile; @@ -1226,6 +1423,11 @@ mod tests { super::check_rewind_remove_mark(new_tree::); } + #[test] + fn witnesses_at_historical_height() { + super::witnesses_at_historical_height() + } + #[test] fn put_shard_roots() { super::put_shard_roots::() @@ -1342,4 +1544,97 @@ mod tests { ] ); } + + /// Test that `generate_orchard_witnesses_at_historical_height` produces valid + /// witnesses when given a frontier extracted from an earlier tree state. + #[cfg(feature = "orchard")] + fn witnesses_at_historical_height() { + use ::orchard::tree::MerkleHashOrchard; + use incrementalmerkletree::frontier::Frontier; + use rand::SeedableRng; + use rand_chacha::ChaChaRng; + use zcash_client_backend::data_api::ORCHARD_SHARD_HEIGHT; + + let data_file = NamedTempFile::new().unwrap(); + let mut db_data = WalletDb::for_path( + data_file.path(), + Network::TestNetwork, + test_clock(), + test_rng(), + ) + .unwrap(); + data_file.keep().unwrap(); + + WalletMigrator::new().init_or_migrate(&mut db_data).unwrap(); + + let mut rng = ChaChaRng::seed_from_u64(0); + + // Build a tree with some leaves, marking one as our note. + // We track the frontier separately to capture the tree state at a + // snapshot height, while the wallet DB holds the persistent shard data. + let mut frontier_tree: Frontier = Frontier::empty(); + let snapshot_height = BlockHeight::from(100); + let note_position; + let note_leaf; + + { + let tx = db_data.conn.transaction().unwrap(); + let store = + SqliteShardStore::<_, MerkleHashOrchard, ORCHARD_SHARD_HEIGHT>::from_connection( + &tx, "orchard", + ) + .unwrap(); + let mut tree = ShardTree::< + _, + { ::orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 }, + ORCHARD_SHARD_HEIGHT, + >::new(store, 100); + + // Insert 5 leaves. Mark leaf 2 as our note. + let mut saved_leaf = None; + for i in 0u64..5 { + let leaf = MerkleHashOrchard::random(&mut rng); + let retention = if i == 2 { + saved_leaf = Some(leaf); + Retention::Marked + } else { + Retention::Ephemeral + }; + tree.append(leaf, retention).unwrap(); + frontier_tree.append(leaf); + } + note_position = Position::from(2); + note_leaf = saved_leaf.unwrap(); + + // Advance the tree past the snapshot height, simulating the + // wallet continuing to sync after the voting snapshot. + tree.checkpoint(snapshot_height).unwrap(); + for _ in 0..5 { + let leaf = MerkleHashOrchard::random(&mut rng); + tree.append(leaf, Retention::Ephemeral).unwrap(); + } + tree.checkpoint(BlockHeight::from(200)).unwrap(); + + tx.commit().unwrap(); + } + + // The frontier_tree captured the state after 5 leaves. + let expected_root = frontier_tree.root(); + let frontier = frontier_tree.take().expect("frontier is non-empty"); + + // Generate witness using the function under test + let witnesses = super::generate_orchard_witnesses_at_historical_height( + &db_data.conn, + &[note_position], + frontier, + snapshot_height, + ) + .expect("witness generation should succeed"); + + assert_eq!(witnesses.len(), 1); + + // The witness, combined with the note's leaf hash, must recompute + // to the tree root at the snapshot height. + assert_eq!(witnesses[0].root(note_leaf), expected_root); + } } diff --git a/zcash_client_sqlite/src/wallet/orchard.rs b/zcash_client_sqlite/src/wallet/orchard.rs index 512e9e4f16..3f5bec88f8 100644 --- a/zcash_client_sqlite/src/wallet/orchard.rs +++ b/zcash_client_sqlite/src/wallet/orchard.rs @@ -167,6 +167,56 @@ pub(crate) fn select_spendable_orchard_notes( ) } +/// Return all Orchard notes that were received at or before `height` +/// and unspent as of `height`, for the given account. +/// +/// Unlike `select_spendable_notes` (which applies confirmation, dust, and +/// expiry filters for transaction construction), this returns every note +/// that existed and was unspent at the given height. +pub fn get_unspent_orchard_notes_at_historical_height( + conn: &Connection, + params: &P, + account: AccountUuid, + height: BlockHeight, +) -> Result>, SqliteClientError> { + let mut stmt = conn.prepare_cached( + "SELECT + rn.id AS id, t.txid, rn.action_index, + rn.diversifier, rn.value, rn.rho, rn.rseed, rn.commitment_tree_position, + accounts.ufvk AS ufvk, rn.recipient_key_scope, + t.block AS mined_height, + NULL AS max_shielding_input_height + FROM orchard_received_notes rn + INNER JOIN accounts ON accounts.id = rn.account_id + INNER JOIN transactions t ON t.id_tx = rn.transaction_id + WHERE accounts.uuid = :account_uuid + AND t.block IS NOT NULL + AND t.block <= :height + AND rn.nf IS NOT NULL + AND rn.commitment_tree_position IS NOT NULL + AND rn.recipient_key_scope IN (0, 1) + AND accounts.ufvk IS NOT NULL + AND rn.id NOT IN ( + SELECT rns.orchard_received_note_id + FROM orchard_received_note_spends rns + JOIN transactions t_spend ON t_spend.id_tx = rns.transaction_id + WHERE t_spend.block IS NOT NULL + AND t_spend.block <= :height + ) + ORDER BY rn.commitment_tree_position", + )?; + + let rows = stmt.query_and_then( + named_params![ + ":account_uuid": account.0, + ":height": u32::from(height), + ], + |row| to_received_note(params, row), + )?; + + rows.filter_map(|r| r.transpose()).collect() +} + pub(crate) fn ensure_address< T: ReceivedOrchardOutput, P: consensus::Parameters, @@ -633,4 +683,79 @@ pub(crate) mod tests { OrchardPoolTester, >(); } + + #[test] + fn get_unspent_orchard_notes_at_historical_height_boundary_heights() { + use zcash_client_backend::data_api::Account; + use zcash_client_backend::data_api::testing::{ + AddressType, TestBuilder, pool::ShieldedPoolTester, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + + use crate::testing::{BlockCache, db::TestDbFactory}; + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let account = st.test_account().cloned().unwrap(); + let dfvk = OrchardPoolTester::test_account_fvk(&st); + + // Receive a note at h1 + let value = Zatoshis::const_from_u64(50000); + let (h1, _, nf) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h1, 1); + + // Spend that note at h2 (produces change back to us) + let not_our_key = OrchardPoolTester::sk_to_fvk(&OrchardPoolTester::sk(&[0xf5; 32])); + let to = OrchardPoolTester::fvk_default_address(¬_our_key); + let spend_value = Zatoshis::const_from_u64(20000); + let (h2, _) = st.generate_next_block_spending(&dfvk, (nf, value), to, spend_value); + st.scan_cached_blocks(h2, 1); + + // Receive another note at h3 + let value3 = Zatoshis::const_from_u64(70000); + let (h3, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value3); + st.scan_cached_blocks(h3, 1); + + let db = st.wallet().db(); + + // Before any notes: nothing (h1 - 1 is before the note was mined) + let notes = db + .get_unspent_orchard_notes_at_historical_height(account.id(), h1 - 1) + .unwrap(); + assert_eq!(notes.len(), 0); + + // At h1: original note received and unspent + let notes = db + .get_unspent_orchard_notes_at_historical_height(account.id(), h1) + .unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].note_value().unwrap(), value); + + // At h2: original spent, only change note remains + let notes = db + .get_unspent_orchard_notes_at_historical_height(account.id(), h2) + .unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!( + notes[0].note_value().unwrap(), + (value - spend_value).unwrap() + ); + + // At h3: change note + new note + let notes = db + .get_unspent_orchard_notes_at_historical_height(account.id(), h3) + .unwrap(); + assert_eq!(notes.len(), 2); + let total: Zatoshis = notes + .iter() + .map(|n| n.note_value().unwrap()) + .sum::>() + .unwrap(); + assert_eq!(total, ((value - spend_value).unwrap() + value3).unwrap()); + } } From 67715ce417b4013ce837ef6a74c6cf7c853cbdfd Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 24 Apr 2026 13:42:39 -0300 Subject: [PATCH 3/3] Resolve `orchard` via valar-orchard on crates.io MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes the workspace-level `orchard` dep from the crates.io `orchard` package (patched to zcash/orchard git rev 6b12c77) to the `valar-orchard` package on crates.io, aliased locally to `orchard` via cargo's `package =` rename trick. Every member keeps its existing `orchard.workspace = true` line and its source-level `use orchard::…` imports — no other changes needed. Why --- `valar-orchard` is upstream 0.12.0 + the same post-release fixes up to zcash/orchard 6b12c77 that the previous `[patch.crates-io] orchard` entry pointed at, plus the `pub` visibility additions that valargroup/voting-circuits needs for the shielded-voting Halo 2 circuits (constants, spec, shared_primitives gadget). Before this change, consumers that also depend on `voting-circuits` (e.g. `zcash_voting`) ended up with two distinct Orchard crates in their dep-graph: `orchard` (from this fork, via our patch) and `valar-orchard` (pulled by `voting-circuits` from crates.io). Cargo treats them as different crates regardless of content, which forced a byte-round-trip `orchard_compat` bridge at the zcash_keys / pczt boundary. Routing our fork through `valar-orchard` too collapses that back to a single node and deletes the bridge. The `[patch.crates-io] orchard = { git = "zcash/orchard", rev = "6b12c77…" }` entry is obsolete after this change (no dep resolves crates.io `orchard` anymore) and is removed. Verified: `cargo check --workspace` clean. `valar-orchard v0.12.0` appears in the build graph; no `orchard v0.12.x` or git `orchard` entry. Made-with: Cursor --- Cargo.lock | 93 +++++++++++++++++++++++++++++------------------------- Cargo.toml | 13 ++++++-- 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b3947a538..2af2a6c8b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -824,6 +824,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "corez" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df6f98652d30167eaeea34d77b730e07c8caba6df17bd4551842b9b8da01deb" + [[package]] name = "cpp_demangle" version = "0.4.4" @@ -2967,42 +2973,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "orchard" -version = "0.12.0" -source = "git+https://github.com/zcash/orchard.git?rev=6b12c77260aa7fac0d804983fc31b71b584d48e0#6b12c77260aa7fac0d804983fc31b71b584d48e0" -dependencies = [ - "aes", - "bitvec", - "blake2b_simd", - "core2", - "ff", - "fpe", - "getset", - "group", - "halo2_gadgets", - "halo2_poseidon", - "halo2_proofs", - "hex", - "incrementalmerkletree", - "lazy_static", - "memuse", - "nonempty", - "pasta_curves", - "proptest", - "rand 0.8.5", - "rand_core 0.6.4", - "reddsa", - "serde", - "sinsemilla", - "subtle", - "tracing", - "visibility", - "zcash_note_encryption", - "zcash_spec", - "zip32", -] - [[package]] name = "ordered-float" version = "2.10.1" @@ -3157,7 +3127,6 @@ dependencies = [ "incrementalmerkletree", "jubjub", "nonempty", - "orchard", "pasta_curves", "postcard", "rand_core 0.6.4", @@ -3169,6 +3138,7 @@ dependencies = [ "serde_with", "sha2 0.10.8", "shardtree", + "valar-orchard", "zcash_note_encryption", "zcash_primitives", "zcash_proofs", @@ -6315,6 +6285,43 @@ dependencies = [ "serde", ] +[[package]] +name = "valar-orchard" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0415fc8aba029019c974bb0b0cc47e0bca53a27fdc2da263a2e6c99c9b3727f8" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "corez", + "ff", + "fpe", + "getset", + "group", + "halo2_gadgets", + "halo2_poseidon", + "halo2_proofs", + "hex", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "proptest", + "rand 0.8.5", + "rand_core 0.6.4", + "reddsa", + "serde", + "sinsemilla", + "subtle", + "tracing", + "visibility", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + [[package]] name = "valuable" version = "0.1.0" @@ -7068,7 +7075,6 @@ dependencies = [ "jubjub", "memuse", "nonempty", - "orchard", "pasta_curves", "pczt", "percent-encoding", @@ -7099,6 +7105,7 @@ dependencies = [ "tower", "tracing", "trait-variant", + "valar-orchard", "webpki-roots 1.0.3", "which", "zcash_address", @@ -7128,7 +7135,6 @@ dependencies = [ "incrementalmerkletree", "jubjub", "nonempty", - "orchard", "postcard", "proptest", "prost", @@ -7146,6 +7152,7 @@ dependencies = [ "time", "tokio", "tracing", + "valar-orchard", "wasm_sync", "which", "zcash_address", @@ -7178,7 +7185,6 @@ dependencies = [ "jubjub", "maybe-rayon", "nonempty", - "orchard", "pasta_curves", "proptest", "prost", @@ -7201,6 +7207,7 @@ dependencies = [ "time", "tracing", "uuid", + "valar-orchard", "zcash_address", "zcash_client_backend", "zcash_encoding", @@ -7232,9 +7239,9 @@ dependencies = [ "blake2b_simd", "ff", "jubjub", - "orchard", "rand_core 0.6.4", "sapling-crypto", + "valar-orchard", "zcash_address", "zcash_primitives", "zcash_proofs", @@ -7272,7 +7279,6 @@ dependencies = [ "jubjub", "memuse", "nonempty", - "orchard", "proptest", "rand 0.8.5", "rand_chacha 0.3.1", @@ -7283,6 +7289,7 @@ dependencies = [ "secrecy", "subtle", "tracing", + "valar-orchard", "zcash_address", "zcash_encoding", "zcash_protocol", @@ -7324,7 +7331,6 @@ dependencies = [ "jubjub", "memuse", "nonempty", - "orchard", "pprof", "proptest", "rand_core 0.6.4", @@ -7333,6 +7339,7 @@ dependencies = [ "sapling-crypto", "secp256k1", "sha2 0.10.8", + "valar-orchard", "zcash_encoding", "zcash_note_encryption", "zcash_protocol", diff --git a/Cargo.toml b/Cargo.toml index f001f9cdc9..b952a2978e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,14 @@ redjubjub = { version = "0.8", default-features = false } sapling = { package = "sapling-crypto", version = "0.6", default-features = false } # - Orchard -orchard = { version = "0.12", default-features = false } +# +# Points at the Valar Group `valar-orchard` fork on crates.io, aliased locally to +# `orchard` so every member still writes `use orchard::…`. The fork is +# upstream-0.12.0 + post-release fixes (up to zcash/orchard 6b12c77) + governance- +# visibility additions needed by the shielded-voting circuits in +# valargroup/voting-circuits. Drop back to `orchard = "0.12"` once those +# visibility changes land in zcash/orchard upstream. +orchard = { version = "0.12.0", package = "valar-orchard", default-features = false } pasta_curves = "0.5" # - Transparent @@ -231,4 +238,6 @@ unexpected_cfgs = { level = "warn", check-cfg = [ [patch.crates-io] sapling = { package = "sapling-crypto", git = "https://github.com/zcash/sapling-crypto.git", rev = "4f95c2286dbe90f05e6f44d634bc2924b992fab4" } -orchard = { package = "orchard", git = "https://github.com/zcash/orchard.git", rev = "6b12c77260aa7fac0d804983fc31b71b584d48e0" } +# No orchard patch: the workspace resolves `orchard` via its `package = "valar-orchard"` +# alias on crates.io, which already carries the post-0.12.0 upstream fixes plus the +# governance-visibility additions.