Skip to main content

zync_core/
lib.rs

1//! # zync-core
2//!
3//! Trust-minimized Zcash light client primitives. Remaining trust assumptions:
4//! the hardcoded activation block hash, the cryptographic proof systems
5//! (ligerito, NOMT, Halo 2), and your key material.
6//!
7//! ## Trust model
8//!
9//! A zync-powered light client verifies every claim the server makes:
10//!
11//! 1. **Header chain.** [ligerito](https://crates.io/crates/ligerito) polynomial
12//!    commitment proofs over block headers. The prover encodes headers into a trace
13//!    polynomial and commits to the evaluation; the verifier checks the commitment
14//!    in O(log n) without seeing any headers. Public outputs (block hashes,
15//!    state roots, commitments) are transcript-bound but NOT evaluation-proven —
16//!    the ligerito proximity test does not constrain which values the polynomial
17//!    contains. Soundness relies on the honest-prover assumption; cross-verification
18//!    (item 4) detects a malicious prover. Proven roots anchor subsequent steps.
19//!
20//! 2. **State proofs.** NOMT sparse merkle proofs for note commitments and
21//!    nullifiers. Each proof binds to the tree root proven by the header chain.
22//!    Commitment proofs verify that received notes exist in the global tree.
23//!    Nullifier proofs verify spent/unspent status without trusting the server.
24//!
25//! 3. **Actions integrity.** A running Blake2b commitment chain over per-block
26//!    orchard action merkle roots. Verified against the header-proven value to
27//!    detect block action tampering (inserted, removed, or reordered actions).
28//!
29//! 4. **Cross-verification.** BFT majority consensus against independent
30//!    lightwalletd nodes. Tip and activation block hashes are compared with
31//!    >2/3 agreement required. Prevents single-server eclipse attacks.
32//!
33//! 5. **Trial decryption.** Orchard note scanning with cmx recomputation.
34//!    After decryption, the note commitment is recomputed from the decrypted
35//!    fields and compared against the server-provided cmx. A malicious server
36//!    cannot craft ciphertexts that decrypt to fake notes with arbitrary values.
37//!
38//! ## Modules
39//!
40//! - [`verifier`]: ligerito header chain proof verification (epoch + tip, parallel)
41//! - [`nomt`]: NOMT sparse merkle proof verification (commitments + nullifiers)
42//! - [`actions`]: actions merkle root computation and running commitment chain
43//! - [`scanner`]: orchard note trial decryption (native + WASM parallel)
44//! - [`sync`]: sync verification primitives (proof validation, cross-verify, memo extraction)
45//! - [`prover`]: ligerito proof generation from header chain traces
46//! - [`trace`]: header chain trace encoding (headers to polynomial)
47//! - [`client`]: gRPC clients for zidecar and lightwalletd (feature-gated)
48//!
49//! ## Platform support
50//!
51//! Default features (`client`, `parallel`) build a native library with gRPC
52//! clients and rayon-based parallel scanning. For WASM, disable defaults and
53//! enable `wasm` or `wasm-parallel`:
54//!
55//! ```toml
56//! zync-core = { version = "0.4", default-features = false, features = ["wasm-parallel"] }
57//! ```
58//!
59//! WASM parallel scanning requires `SharedArrayBuffer` (COOP/COEP headers)
60//! and builds with:
61//! ```sh
62//! RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals' \
63//!   cargo build --target wasm32-unknown-unknown
64//! ```
65//!
66//! ## Minimal sync loop
67//!
68//! ```ignore
69//! use zync_core::{sync, verifier, Scanner, OrchardFvk};
70//!
71//! // 1. fetch header proof from zidecar
72//! let (proof_bytes, _, _) = client.get_header_proof().await?;
73//!
74//! // 2. verify and extract proven NOMT roots
75//! let proven = sync::verify_header_proof(&proof_bytes, tip, true)?;
76//!
77//! // 3. scan compact blocks for owned notes
78//! let scanner = Scanner::from_fvk(&fvk);
79//! let notes = scanner.scan(&actions);
80//!
81//! // 4. verify commitment proofs for received notes
82//! sync::verify_commitment_proofs(&proofs, &cmxs, &proven, &server_root)?;
83//!
84//! // 5. verify nullifier proofs for unspent notes
85//! let spent = sync::verify_nullifier_proofs(&nf_proofs, &nullifiers, &proven, &nf_root)?;
86//!
87//! // 6. verify actions commitment chain
88//! sync::verify_actions_commitment(&running, &proven.actions_commitment, true)?;
89//! ```
90
91#![allow(dead_code)]
92
93pub mod actions;
94pub mod endpoints;
95pub mod error;
96pub mod nomt;
97pub mod prover;
98pub mod scanner;
99pub mod sync;
100pub mod trace;
101pub mod verifier;
102
103#[cfg(feature = "wasm")]
104pub mod wasm_api;
105
106#[cfg(feature = "client")]
107pub mod client;
108
109pub use error::{Result, ZyncError};
110pub use scanner::{BatchScanner, DecryptedNote, ScanAction, Scanner};
111
112// re-export orchard key types for downstream consumers
113pub use orchard::keys::{FullViewingKey as OrchardFvk, IncomingViewingKey, Scope, SpendingKey};
114
115#[cfg(feature = "client")]
116pub use client::{LightwalletdClient, ZidecarClient};
117
118use ligerito::{ProverConfig, VerifierConfig};
119use ligerito_binary_fields::{BinaryElem128, BinaryElem32};
120use std::marker::PhantomData;
121
122/// blocks per epoch (~21 hours at 75s/block)
123pub const EPOCH_SIZE: u32 = 1024;
124
125/// max orchard actions per block
126pub const MAX_ACTIONS_PER_BLOCK: usize = 512;
127
128/// fields encoded per action in trace polynomial
129pub const FIELDS_PER_ACTION: usize = 8;
130
131/// fields encoded per block header in trace polynomial
132pub const FIELDS_PER_HEADER: usize = 32;
133
134/// sentinel row size appended after all headers in trace
135pub const TIP_SENTINEL_SIZE: usize = 24;
136
137/// polynomial size exponent for tip proofs (2^20 config, max ~32K headers)
138pub const TIP_TRACE_LOG_SIZE: usize = 20;
139
140/// polynomial size exponent for epoch proofs (2^26 config)
141pub const EPOCH_PROOF_TRACE_LOG_SIZE: usize = 26;
142
143/// security parameter (bits)
144pub const SECURITY_BITS: usize = 100;
145
146/// orchard activation height (mainnet)
147pub const ORCHARD_ACTIVATION_HEIGHT: u32 = 1_687_104;
148
149/// orchard activation height (testnet)
150pub const ORCHARD_ACTIVATION_HEIGHT_TESTNET: u32 = 1_842_420;
151
152/// orchard activation block hash (mainnet, LE internal order)
153pub const ACTIVATION_HASH_MAINNET: [u8; 32] = [
154    0x00, 0x00, 0x00, 0x00, 0x00, 0xd7, 0x23, 0x15, 0x6d, 0x9b, 0x65, 0xff, 0xcf, 0x49, 0x84,
155    0xda, 0x7a, 0x19, 0x67, 0x5e, 0xd7, 0xe2, 0xf0, 0x6d, 0x9e, 0x5d, 0x51, 0x88, 0xaf, 0x08,
156    0x7b, 0xf8,
157];
158
159/// domain separator for wallet state commitment
160pub const DOMAIN_WALLET_STATE: &[u8] = b"ZYNC_wallet_state_v1";
161
162/// domain separator for epoch proof hash
163pub const DOMAIN_EPOCH_PROOF: &[u8] = b"ZYNC_epoch_proof_v1";
164
165/// domain separator for ivk commitment
166pub const DOMAIN_IVK_COMMIT: &[u8] = b"ZYNC_ivk_commit_v1";
167
168/// genesis epoch hash (all zeros)
169pub const GENESIS_EPOCH_HASH: [u8; 32] = [0u8; 32];
170
171/// empty sparse merkle tree root
172pub const EMPTY_SMT_ROOT: [u8; 32] = [0u8; 32];
173
174/// ligerito prover config for tip proofs (2^20)
175pub fn tip_prover_config() -> ProverConfig<BinaryElem32, BinaryElem128> {
176    ligerito::hardcoded_config_20(PhantomData::<BinaryElem32>, PhantomData::<BinaryElem128>)
177}
178
179/// ligerito prover config for epoch proofs (2^26)
180pub fn epoch_proof_prover_config() -> ProverConfig<BinaryElem32, BinaryElem128> {
181    ligerito::hardcoded_config_26(PhantomData::<BinaryElem32>, PhantomData::<BinaryElem128>)
182}
183
184/// select the appropriate prover config for a given trace size
185pub fn prover_config_for_size(
186    trace_len: usize,
187) -> (ProverConfig<BinaryElem32, BinaryElem128>, usize) {
188    let log_size = if trace_len == 0 {
189        12
190    } else {
191        (trace_len as f64).log2().ceil() as u32
192    };
193
194    let (config_log, config) = if log_size <= 12 {
195        (
196            12,
197            ligerito::hardcoded_config_12(
198                PhantomData::<BinaryElem32>,
199                PhantomData::<BinaryElem128>,
200            ),
201        )
202    } else if log_size <= 16 {
203        (
204            16,
205            ligerito::hardcoded_config_16(
206                PhantomData::<BinaryElem32>,
207                PhantomData::<BinaryElem128>,
208            ),
209        )
210    } else if log_size <= 20 {
211        (
212            20,
213            ligerito::hardcoded_config_20(
214                PhantomData::<BinaryElem32>,
215                PhantomData::<BinaryElem128>,
216            ),
217        )
218    } else if log_size <= 24 {
219        (
220            24,
221            ligerito::hardcoded_config_24(
222                PhantomData::<BinaryElem32>,
223                PhantomData::<BinaryElem128>,
224            ),
225        )
226    } else if log_size <= 26 {
227        (
228            26,
229            ligerito::hardcoded_config_26(
230                PhantomData::<BinaryElem32>,
231                PhantomData::<BinaryElem128>,
232            ),
233        )
234    } else if log_size <= 28 {
235        (
236            28,
237            ligerito::hardcoded_config_28(
238                PhantomData::<BinaryElem32>,
239                PhantomData::<BinaryElem128>,
240            ),
241        )
242    } else {
243        (
244            30,
245            ligerito::hardcoded_config_30(
246                PhantomData::<BinaryElem32>,
247                PhantomData::<BinaryElem128>,
248            ),
249        )
250    };
251
252    (config, 1 << config_log)
253}
254
255/// select the appropriate verifier config for a given log size
256pub fn verifier_config_for_log_size(log_size: u32) -> VerifierConfig {
257    if log_size <= 12 {
258        ligerito::hardcoded_config_12_verifier()
259    } else if log_size <= 16 {
260        ligerito::hardcoded_config_16_verifier()
261    } else if log_size <= 20 {
262        ligerito::hardcoded_config_20_verifier()
263    } else if log_size <= 24 {
264        ligerito::hardcoded_config_24_verifier()
265    } else if log_size <= 26 {
266        ligerito::hardcoded_config_26_verifier()
267    } else if log_size <= 28 {
268        ligerito::hardcoded_config_28_verifier()
269    } else {
270        ligerito::hardcoded_config_30_verifier()
271    }
272}
273
274/// helper: calculate epoch number from block height
275pub fn epoch_for_height(height: u32) -> u32 {
276    height / EPOCH_SIZE
277}
278
279/// helper: get start height of epoch
280pub fn epoch_start(epoch: u32) -> u32 {
281    epoch * EPOCH_SIZE
282}
283
284/// helper: get end height of epoch (inclusive)
285pub fn epoch_end(epoch: u32) -> u32 {
286    epoch_start(epoch + 1) - 1
287}