1mod shield;
32mod shield_from_asset_lock;
33mod shielded_transfer;
34mod shielded_withdrawal;
35mod unshield;
36
37pub use self::shield::build_shield_transition;
38pub use shield_from_asset_lock::build_shield_from_asset_lock_transition;
39pub use shielded_transfer::build_shielded_transfer_transition;
40pub use shielded_withdrawal::build_shielded_withdrawal_transition;
41pub use unshield::build_unshield_transition;
42
43use grovedb_commitment_tree::{
44 Anchor, Authorized, Builder, Bundle, BundleType, DashMemo, Flags as OrchardFlags,
45 FullViewingKey, MerklePath, Note, NoteValue, PaymentAddress, ProvingKey, SpendAuthorizingKey,
46};
47use rand::rngs::OsRng;
48
49use crate::address_funds::OrchardAddress;
50use crate::shielded::{compute_platform_sighash, SerializedAction};
51use crate::ProtocolError;
52
53pub trait OrchardProver {
59 fn proving_key(&self) -> &ProvingKey;
61}
62
63pub struct SpendableNote {
66 pub note: Note,
68 pub merkle_path: MerklePath,
70}
71
72pub struct SerializedBundle {
75 pub actions: Vec<SerializedAction>,
77 pub flags: u8,
79 pub value_balance: i64,
81 pub anchor: [u8; 32],
85 pub proof: Vec<u8>,
87 pub binding_signature: [u8; 64],
89}
90
91impl From<&OrchardAddress> for PaymentAddress {
92 fn from(address: &OrchardAddress) -> Self {
93 *address.inner()
94 }
95}
96
97pub fn serialize_authorized_bundle(bundle: &Bundle<Authorized, i64, DashMemo>) -> SerializedBundle {
100 let actions: Vec<SerializedAction> = bundle
101 .actions()
102 .iter()
103 .map(|action| {
104 let enc = action.encrypted_note();
105 let mut encrypted_note = Vec::with_capacity(216);
106 encrypted_note.extend_from_slice(&enc.epk_bytes);
107 encrypted_note.extend_from_slice(enc.enc_ciphertext.as_ref());
108 encrypted_note.extend_from_slice(&enc.out_ciphertext);
109 SerializedAction {
110 nullifier: action.nullifier().to_bytes(),
111 rk: <[u8; 32]>::from(action.rk()),
112 cmx: action.cmx().to_bytes(),
113 encrypted_note,
114 cv_net: action.cv_net().to_bytes(),
115 spend_auth_sig: <[u8; 64]>::from(action.authorization()),
116 }
117 })
118 .collect();
119 let flags = bundle.flags().to_byte();
120 let value_balance = *bundle.value_balance();
121 let anchor = bundle.anchor().to_bytes();
122 let proof = bundle.authorization().proof().as_ref().to_vec();
123 let binding_signature = <[u8; 64]>::from(bundle.authorization().binding_signature());
124 SerializedBundle {
125 actions,
126 flags,
127 value_balance,
128 anchor,
129 proof,
130 binding_signature,
131 }
132}
133
134pub(crate) fn build_output_only_bundle<P: OrchardProver>(
143 recipient: &OrchardAddress,
144 amount: u64,
145 memo: [u8; 36],
146 prover: &P,
147) -> Result<Bundle<Authorized, i64, DashMemo>, ProtocolError> {
148 let payment_address = PaymentAddress::from(recipient);
149 let anchor = Anchor::empty_tree();
150 let mut builder = Builder::<DashMemo>::new(
151 BundleType::Transactional {
152 flags: OrchardFlags::SPENDS_DISABLED,
153 bundle_required: false,
154 },
155 anchor,
156 );
157
158 builder
159 .add_output(None, payment_address, NoteValue::from_raw(amount), memo)
160 .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add output: {:?}", e)))?;
161
162 prove_and_sign_bundle(builder, prover, &[], &[])
163}
164
165#[allow(clippy::too_many_arguments)]
170pub(crate) fn build_spend_bundle<P: OrchardProver>(
171 spends: Vec<SpendableNote>,
172 recipient: &OrchardAddress,
173 output_amount: u64,
174 memo: [u8; 36],
175 fvk: &FullViewingKey,
176 ask: &SpendAuthorizingKey,
177 anchor: Anchor,
178 prover: &P,
179 extra_sighash_data: &[u8],
180) -> Result<Bundle<Authorized, i64, DashMemo>, ProtocolError> {
181 let payment_address = PaymentAddress::from(recipient);
182
183 let mut builder = Builder::<DashMemo>::new(BundleType::DEFAULT, anchor);
184
185 for spend in spends {
186 builder
187 .add_spend(fvk.clone(), spend.note, spend.merkle_path)
188 .map_err(|e| {
189 ProtocolError::ShieldedBuildError(format!("failed to add spend: {:?}", e))
190 })?;
191 }
192
193 builder
194 .add_output(
195 None,
196 payment_address,
197 NoteValue::from_raw(output_amount),
198 memo,
199 )
200 .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to add output: {:?}", e)))?;
201
202 prove_and_sign_bundle(
203 builder,
204 prover,
205 std::slice::from_ref(ask),
206 extra_sighash_data,
207 )
208}
209
210pub(crate) fn prove_and_sign_bundle<P: OrchardProver>(
213 builder: Builder<DashMemo>,
214 prover: &P,
215 signing_keys: &[SpendAuthorizingKey],
216 extra_sighash_data: &[u8],
217) -> Result<Bundle<Authorized, i64, DashMemo>, ProtocolError> {
218 let mut rng = OsRng;
219
220 let (unauthorized, _) = builder
221 .build::<i64>(&mut rng)
222 .map_err(|e| ProtocolError::ShieldedBuildError(format!("failed to build bundle: {:?}", e)))?
223 .ok_or_else(|| {
224 ProtocolError::ShieldedBuildError("bundle was empty after build".to_string())
225 })?;
226
227 let bundle_commitment: [u8; 32] = unauthorized.commitment().into();
228 let sighash = compute_platform_sighash(&bundle_commitment, extra_sighash_data);
229
230 let proven = unauthorized
231 .create_proof(prover.proving_key(), &mut rng)
232 .map_err(|e| {
233 ProtocolError::ShieldedBuildError(format!("failed to create proof: {:?}", e))
234 })?;
235
236 proven
237 .apply_signatures(rng, sighash, signing_keys)
238 .map_err(|e| {
239 ProtocolError::ShieldedBuildError(format!("failed to apply signatures: {:?}", e))
240 })
241}
242
243#[cfg(test)]
245pub(crate) mod test_helpers {
246 use super::*;
247 use grovedb_commitment_tree::{
248 FullViewingKey, Hashable, MerkleHashOrchard, Note, NoteValue, ProvingKey, RandomSeed, Rho,
249 Scope, SpendingKey, NOTE_COMMITMENT_TREE_DEPTH,
250 };
251 use std::sync::OnceLock;
252
253 static PROVING_KEY: OnceLock<ProvingKey> = OnceLock::new();
254
255 pub fn proving_key() -> &'static ProvingKey {
257 PROVING_KEY.get_or_init(ProvingKey::build)
258 }
259
260 pub struct TestProver;
262
263 impl super::OrchardProver for TestProver {
264 fn proving_key(&self) -> &ProvingKey {
265 proving_key()
266 }
267 }
268
269 pub fn test_orchard_address() -> OrchardAddress {
271 let sk = SpendingKey::from_bytes([42u8; 32]).expect("valid spending key bytes");
272 let fvk = FullViewingKey::from(&sk);
273 let payment_address = fvk.address_at(0u32, Scope::External);
274 OrchardAddress::from_raw_bytes(&payment_address.to_raw_address_bytes())
275 .expect("valid orchard address bytes")
276 }
277
278 pub fn test_spendable_note(value: u64) -> SpendableNote {
285 let sk = SpendingKey::from_bytes([42u8; 32]).expect("valid spending key bytes");
286 let fvk = FullViewingKey::from(&sk);
287 let payment_address = fvk.address_at(0u32, Scope::External);
288
289 let rho: Rho =
291 Option::from(Rho::from_bytes(&[0u8; 32])).expect("zero is valid pallas::Base");
292 let rseed: RandomSeed =
293 Option::from(RandomSeed::from_bytes([1u8; 32], &rho)).expect("valid random seed");
294 let note: Note = Option::from(Note::from_parts(
295 payment_address,
296 NoteValue::from_raw(value),
297 rho,
298 rseed,
299 ))
300 .expect("note commitment should be valid");
301
302 let auth_path = [MerkleHashOrchard::empty_leaf(); NOTE_COMMITMENT_TREE_DEPTH];
304 let merkle_path = MerklePath::from_parts(0, auth_path);
305
306 SpendableNote { note, merkle_path }
307 }
308}