dpp/shielded/builder/
shield_from_asset_lock.rs

1use crate::address_funds::OrchardAddress;
2use crate::prelude::AssetLockProof;
3use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0;
4use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition;
5use crate::state_transition::StateTransition;
6use crate::ProtocolError;
7use platform_version::version::PlatformVersion;
8
9use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver};
10
11/// Builds a ShieldFromAssetLock state transition (core asset lock -> shielded pool).
12///
13/// Like Shield, constructs an output-only Orchard bundle. The funds come from
14/// a core asset lock proof rather than platform address inputs.
15///
16/// # Parameters
17/// - `recipient` - Orchard address to receive the shielded note
18/// - `shield_amount` - Amount of credits to shield (from the asset lock)
19/// - `asset_lock_proof` - Proof that funds are locked on core chain
20/// - `asset_lock_private_key` - Private key for the asset lock (signs the transition)
21/// - `prover` - Orchard prover (holds the Halo 2 proving key)
22/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload)
23/// - `platform_version` - Protocol version
24#[allow(clippy::too_many_arguments)]
25pub fn build_shield_from_asset_lock_transition<P: OrchardProver>(
26    recipient: &OrchardAddress,
27    shield_amount: u64,
28    asset_lock_proof: AssetLockProof,
29    asset_lock_private_key: &[u8],
30    prover: &P,
31    memo: [u8; 36],
32    platform_version: &PlatformVersion,
33) -> Result<StateTransition, ProtocolError> {
34    let bundle = build_output_only_bundle(recipient, shield_amount, memo, prover)?;
35    let sb = serialize_authorized_bundle(&bundle);
36
37    // For output-only bundles, Orchard value_balance is negative (value flowing in).
38    // Convert to u64 (absolute amount entering the pool).
39    let value_balance = sb
40        .value_balance
41        .checked_neg()
42        .and_then(|v| u64::try_from(v).ok())
43        .ok_or_else(|| {
44            ProtocolError::ShieldedBuildError(
45                "shield_from_asset_lock: bundle value_balance is not negative".to_string(),
46            )
47        })?;
48
49    ShieldFromAssetLockTransition::try_from_asset_lock_with_bundle(
50        asset_lock_proof,
51        asset_lock_private_key,
52        sb.actions,
53        value_balance,
54        sb.anchor,
55        sb.proof,
56        sb.binding_signature,
57        platform_version,
58    )
59}
60
61#[cfg(test)]
62mod tests {
63    use super::super::{build_output_only_bundle, serialize_authorized_bundle};
64    use crate::shielded::builder::test_helpers::{test_orchard_address, TestProver};
65
66    /// Verifies that an output-only bundle produces a negative value_balance
67    /// (value flowing into the pool), which is the precondition for
68    /// shield_from_asset_lock's value_balance conversion.
69    #[test]
70    fn test_output_only_bundle_value_balance_is_negative() {
71        let recipient = test_orchard_address();
72        let amount = 50_000u64;
73
74        let bundle = build_output_only_bundle(&recipient, amount, [0u8; 36], &TestProver)
75            .expect("bundle should build successfully");
76        let sb = serialize_authorized_bundle(&bundle);
77
78        // Output-only bundles have negative value_balance (value entering the pool)
79        assert!(
80            sb.value_balance < 0,
81            "expected negative value_balance, got {}",
82            sb.value_balance
83        );
84
85        // The absolute value should match the shield amount
86        let abs_balance = sb
87            .value_balance
88            .checked_neg()
89            .and_then(|v| u64::try_from(v).ok())
90            .expect("value_balance should be safely negatable");
91        assert_eq!(abs_balance, amount);
92    }
93}