zync_core/
lib.rs

1//! ZYNC Core - Zero-knowledge sYNChronization for Zcash
2//!
3//! core types and logic for ligerito-powered wallet sync
4
5#![allow(dead_code)] // wip
6
7pub mod state;
8pub mod error;
9
10// TODO: implement these modules
11// pub mod trace;
12// pub mod proof;
13// pub mod constraints;
14// pub mod transition;
15// pub mod crypto;
16
17pub use error::{ZyncError, Result};
18pub use state::{WalletState, WalletStateCommitment};
19// pub use trace::{SyncTrace, TraceField};
20// pub use proof::EpochProof;
21
22use ligerito::{ProverConfig, VerifierConfig};
23use ligerito_binary_fields::{BinaryElem32, BinaryElem128};
24use std::marker::PhantomData;
25
26/// blocks per epoch (~21 hours at 75s/block)
27pub const EPOCH_SIZE: u32 = 1024;
28
29/// max orchard actions per block
30pub const MAX_ACTIONS_PER_BLOCK: usize = 512;
31
32/// fields encoded per action in trace polynomial
33pub const FIELDS_PER_ACTION: usize = 8;
34
35/// polynomial size exponent for tip proofs (2^24 config)
36pub const TIP_TRACE_LOG_SIZE: usize = 24;
37
38/// polynomial size exponent for gigaproofs (2^28 config)
39pub const GIGAPROOF_TRACE_LOG_SIZE: usize = 28;
40
41/// security parameter (bits)
42pub const SECURITY_BITS: usize = 100;
43
44/// orchard activation height (mainnet)
45pub const ORCHARD_ACTIVATION_HEIGHT: u32 = 1_687_104;
46
47/// orchard activation height (testnet)
48pub const ORCHARD_ACTIVATION_HEIGHT_TESTNET: u32 = 1_842_420;
49
50/// domain separator for wallet state commitment
51pub const DOMAIN_WALLET_STATE: &[u8] = b"ZYNC_wallet_state_v1";
52
53/// domain separator for epoch proof hash
54pub const DOMAIN_EPOCH_PROOF: &[u8] = b"ZYNC_epoch_proof_v1";
55
56/// domain separator for ivk commitment
57pub const DOMAIN_IVK_COMMIT: &[u8] = b"ZYNC_ivk_commit_v1";
58
59/// genesis epoch hash (all zeros)
60pub const GENESIS_EPOCH_HASH: [u8; 32] = [0u8; 32];
61
62/// empty sparse merkle tree root
63pub const EMPTY_SMT_ROOT: [u8; 32] = [0u8; 32]; // todo: compute actual empty root
64
65/// ligerito prover config for tip proofs (2^24, ~1.3s, max 1024 blocks)
66pub fn tip_prover_config() -> ProverConfig<BinaryElem32, BinaryElem128> {
67    ligerito::hardcoded_config_24(
68        PhantomData::<BinaryElem32>,
69        PhantomData::<BinaryElem128>,
70    )
71}
72
73/// ligerito prover config for gigaproofs (2^28, ~25s, multi-epoch)
74pub fn gigaproof_prover_config() -> ProverConfig<BinaryElem32, BinaryElem128> {
75    ligerito::hardcoded_config_28(
76        PhantomData::<BinaryElem32>,
77        PhantomData::<BinaryElem128>,
78    )
79}
80
81/// select the appropriate prover config for a given trace size
82/// returns (config, required_trace_size) - trace must be padded to required_trace_size
83pub fn prover_config_for_size(trace_len: usize) -> (ProverConfig<BinaryElem32, BinaryElem128>, usize) {
84    let log_size = if trace_len == 0 { 12 } else { (trace_len as f64).log2().ceil() as u32 };
85
86    // available configs: 12, 16, 20, 24, 28, 30
87    let (config_log, config) = if log_size <= 12 {
88        (12, ligerito::hardcoded_config_12(PhantomData::<BinaryElem32>, PhantomData::<BinaryElem128>))
89    } else if log_size <= 16 {
90        (16, ligerito::hardcoded_config_16(PhantomData::<BinaryElem32>, PhantomData::<BinaryElem128>))
91    } else if log_size <= 20 {
92        (20, ligerito::hardcoded_config_20(PhantomData::<BinaryElem32>, PhantomData::<BinaryElem128>))
93    } else if log_size <= 24 {
94        (24, ligerito::hardcoded_config_24(PhantomData::<BinaryElem32>, PhantomData::<BinaryElem128>))
95    } else if log_size <= 28 {
96        (28, ligerito::hardcoded_config_28(PhantomData::<BinaryElem32>, PhantomData::<BinaryElem128>))
97    } else {
98        (30, ligerito::hardcoded_config_30(PhantomData::<BinaryElem32>, PhantomData::<BinaryElem128>))
99    };
100
101    (config, 1 << config_log)
102}
103
104/// select the appropriate verifier config for a given log size
105pub fn verifier_config_for_log_size(log_size: u32) -> VerifierConfig {
106    if log_size <= 12 {
107        ligerito::hardcoded_config_12_verifier()
108    } else if log_size <= 16 {
109        ligerito::hardcoded_config_16_verifier()
110    } else if log_size <= 20 {
111        ligerito::hardcoded_config_20_verifier()
112    } else if log_size <= 24 {
113        ligerito::hardcoded_config_24_verifier()
114    } else if log_size <= 28 {
115        ligerito::hardcoded_config_28_verifier()
116    } else {
117        ligerito::hardcoded_config_30_verifier()
118    }
119}
120
121/// ligerito verifier config for tip proofs (2^24)
122pub fn tip_verifier_config() -> VerifierConfig {
123    ligerito::hardcoded_config_24_verifier()
124}
125
126/// ligerito verifier config for gigaproofs (2^28)
127pub fn gigaproof_verifier_config() -> VerifierConfig {
128    ligerito::hardcoded_config_28_verifier()
129}
130
131/// helper: calculate epoch number from block height
132pub fn epoch_for_height(height: u32) -> u32 {
133    height / EPOCH_SIZE
134}
135
136/// helper: get start height of epoch
137pub fn epoch_start(epoch: u32) -> u32 {
138    epoch * EPOCH_SIZE
139}
140
141/// helper: get end height of epoch (inclusive)
142pub fn epoch_end(epoch: u32) -> u32 {
143    epoch_start(epoch + 1) - 1
144}
145
146/// wallet identifier (random 16 bytes)
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
148pub struct WalletId([u8; 16]);
149
150impl WalletId {
151    pub fn random() -> Self {
152        let mut bytes = [0u8; 16];
153        rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut bytes);
154        Self(bytes)
155    }
156
157    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
158        if bytes.len() != 16 {
159            return Err(ZyncError::InvalidData("wallet id must be 16 bytes".into()));
160        }
161        let mut arr = [0u8; 16];
162        arr.copy_from_slice(bytes);
163        Ok(Self(arr))
164    }
165
166    pub fn to_bytes(&self) -> &[u8; 16] {
167        &self.0
168    }
169}
170
171impl std::fmt::Display for WalletId {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        write!(f, "{}", hex::encode(&self.0[..8])) // short form
174    }
175}
176
177/// helper: hex encoding (inline to avoid dependency)
178mod hex {
179    pub fn encode(bytes: &[u8]) -> String {
180        bytes.iter().map(|b| format!("{:02x}", b)).collect()
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_wallet_id_roundtrip() {
190        let id = WalletId::random();
191        let bytes = id.to_bytes();
192        let id2 = WalletId::from_bytes(bytes).unwrap();
193        assert_eq!(id, id2);
194    }
195
196    #[test]
197    fn test_constants_consistency() {
198        // verify trace size calculation
199        let blocks = 1 << 10; // EPOCH_SIZE rounded up to power of 2
200        let actions = 1 << 9; // MAX_ACTIONS_PER_BLOCK
201        let fields = 1 << 3; // FIELDS_PER_ACTION = 8
202        assert_eq!(blocks * actions * fields, 1 << TRACE_DATA_LOG_SIZE);
203    }
204}