zebra_rpc/
lightwalletd.rs1use zebra_chain::{block, transaction, transaction::Transaction};
10
11#[cfg(test)]
12mod tests;
13
14pub mod methods;
15pub mod server;
16pub mod tracing;
17
18#[allow(missing_docs)]
23mod wire {
24 tonic::include_proto!("cash.z.wallet.sdk.rpc");
25}
26pub use wire::*;
27
28pub(crate) const FILE_DESCRIPTOR_SET: &[u8] =
29 tonic::include_file_descriptor_set!("lightwalletd_descriptor");
30
31const COMPACT_CIPHERTEXT_SIZE: usize = 52;
34
35pub(crate) fn has_compact_data(tx: &Transaction) -> bool {
38 tx.sapling_nullifiers().next().is_some()
39 || tx.sapling_outputs().next().is_some()
40 || tx.orchard_actions().next().is_some()
41 || tx.ironwood_actions().next().is_some()
42}
43
44pub(crate) fn has_nullifiers(tx: &Transaction) -> bool {
50 tx.sapling_nullifiers().next().is_some()
51 || tx.orchard_actions().next().is_some()
52 || tx.ironwood_actions().next().is_some()
53}
54
55fn compact_action(
60 action: &::orchard::Action<
61 <::orchard::bundle::Authorized as ::orchard::bundle::Authorization>::SpendAuth,
62 >,
63 nullifiers_only: bool,
64) -> CompactOrchardAction {
65 let mut compact_action = CompactOrchardAction {
66 nullifier: action.nullifier().to_bytes().to_vec(),
67 ..Default::default()
68 };
69
70 if !nullifiers_only {
71 compact_action.cmx = action.cmx().to_bytes().to_vec();
72 compact_action.ephemeral_key = action.encrypted_note().epk_bytes.to_vec();
73 compact_action.ciphertext =
74 action.encrypted_note().enc_ciphertext.0[..COMPACT_CIPHERTEXT_SIZE].to_vec();
75 }
76
77 compact_action
78}
79
80impl CompactBlock {
81 pub fn from_block(
88 block: &block::Block,
89 hash: block::Hash,
90 height: block::Height,
91 sapling_tree_size: u64,
92 orchard_tree_size: u64,
93 ironwood_tree_size: u64,
94 nullifiers_only: bool,
95 ) -> Self {
96 let vtx = block
97 .transactions
98 .iter()
99 .enumerate()
100 .filter(|(_, tx)| {
101 if nullifiers_only {
102 has_nullifiers(tx)
103 } else {
104 has_compact_data(tx)
105 }
106 })
107 .map(|(index, tx)| {
108 CompactTx::from_transaction(index as u64, tx.hash(), tx, nullifiers_only)
109 })
110 .collect();
111
112 CompactBlock {
113 proto_version: 0,
114 height: height.0.into(),
115 hash: hash.0.to_vec(),
116 prev_hash: block.header.previous_block_hash.0.to_vec(),
117 time: block.header.time.timestamp() as u32,
119 header: Vec::new(),
120 vtx,
121 chain_metadata: Some(ChainMetadata {
122 sapling_commitment_tree_size: sapling_tree_size as u32,
125 orchard_commitment_tree_size: orchard_tree_size as u32,
126 ironwood_commitment_tree_size: ironwood_tree_size as u32,
127 }),
128 }
129 }
130}
131
132impl CompactTx {
133 pub fn from_transaction(
139 index: u64,
140 hash: transaction::Hash,
141 tx: &Transaction,
142 nullifiers_only: bool,
143 ) -> Self {
144 let spends = tx
145 .sapling_nullifiers()
146 .map(|nullifier| CompactSaplingSpend {
147 nf: <[u8; 32]>::from(nullifier).to_vec(),
148 })
149 .collect();
150
151 let outputs = if nullifiers_only {
152 Vec::new()
153 } else {
154 tx.sapling_outputs()
155 .map(|output| CompactSaplingOutput {
156 cmu: output.cmu().to_bytes().to_vec(),
157 ephemeral_key: output.ephemeral_key().0.to_vec(),
158 ciphertext: output.enc_ciphertext()[..COMPACT_CIPHERTEXT_SIZE].to_vec(),
159 })
160 .collect()
161 };
162
163 let actions = tx
164 .orchard_actions()
165 .map(|action| compact_action(action, nullifiers_only))
166 .collect();
167
168 let ironwood_actions = tx
169 .ironwood_actions()
170 .map(|action| compact_action(action, nullifiers_only))
171 .collect();
172
173 CompactTx {
174 index,
175 hash: hash.0.to_vec(),
176 fee: 0,
179 spends,
180 outputs,
181 actions,
182 ironwood_actions,
183 }
184 }
185}