Skip to main content

dpp/shielded/builder/
shield_from_identity.rs

1use crate::address_funds::OrchardAddress;
2use crate::identity::signer::Signer;
3use crate::identity::{Identity, IdentityPublicKey};
4use crate::prelude::{IdentityNonce, UserFeeIncrease};
5use crate::state_transition::shield_from_identity_transition::methods::ShieldFromIdentityTransitionMethodsV0;
6use crate::state_transition::shield_from_identity_transition::ShieldFromIdentityTransition;
7use crate::state_transition::StateTransition;
8use crate::ProtocolError;
9use platform_version::version::PlatformVersion;
10
11use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver};
12
13/// Build a `ShieldFromIdentity` transition: prove an outputs-only Orchard bundle
14/// paying `shield_amount` to `recipient`, then identity-sign it with a TRANSFER key.
15///
16/// The identity nonce must already be the next nonce for `identity` (the SDK
17/// fetches it); `amount + fee` is debited from the identity balance at execution.
18#[allow(clippy::too_many_arguments)]
19pub async fn build_shield_from_identity_transition<
20    S: Signer<IdentityPublicKey>,
21    P: OrchardProver,
22>(
23    identity: &Identity,
24    recipient: &OrchardAddress,
25    shield_amount: u64,
26    nonce: IdentityNonce,
27    signer: &S,
28    signing_transfer_key_to_use: Option<&IdentityPublicKey>,
29    user_fee_increase: UserFeeIncrease,
30    prover: &P,
31    memo: [u8; 36],
32    sender_ovk: Option<grovedb_commitment_tree::OutgoingViewingKey>,
33    platform_version: &PlatformVersion,
34) -> Result<StateTransition, ProtocolError> {
35    if shield_amount == 0 {
36        return Err(ProtocolError::ShieldedBuildError(
37            "shield amount must be greater than zero".to_string(),
38        ));
39    }
40
41    let bundle = build_output_only_bundle(recipient, shield_amount, memo, sender_ovk, 0, prover)?;
42    let sb = serialize_authorized_bundle(&bundle);
43
44    ShieldFromIdentityTransition::try_from_bundle_with_identity_signer(
45        identity,
46        sb.value_balance.unsigned_abs(),
47        sb.actions,
48        sb.anchor,
49        sb.proof,
50        sb.binding_signature,
51        user_fee_increase,
52        signer,
53        signing_transfer_key_to_use,
54        nonce,
55        platform_version,
56    )
57    .await
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use crate::address_funds::AddressWitness;
64    use crate::identity::accessors::IdentityGettersV0;
65    use crate::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
66    use crate::identity::{KeyType, Purpose, SecurityLevel};
67    use crate::shielded::builder::test_helpers::{test_orchard_address, TestProver};
68    use crate::state_transition::shield_from_identity_transition::accessors::ShieldFromIdentityTransitionAccessorsV0;
69    use crate::state_transition::StateTransitionIdentitySigned;
70    use platform_value::BinaryData;
71    use rand::rngs::StdRng;
72    use rand::SeedableRng;
73
74    #[derive(Debug)]
75    struct DummySigner;
76
77    #[async_trait::async_trait]
78    impl Signer<IdentityPublicKey> for DummySigner {
79        async fn sign(
80            &self,
81            _key: &IdentityPublicKey,
82            _data: &[u8],
83        ) -> Result<BinaryData, ProtocolError> {
84            Ok(BinaryData::new(vec![0u8; 65]))
85        }
86
87        async fn sign_create_witness(
88            &self,
89            _key: &IdentityPublicKey,
90            _data: &[u8],
91        ) -> Result<AddressWitness, ProtocolError> {
92            Err(ProtocolError::ShieldedBuildError(
93                "identity signer never creates address witnesses".to_string(),
94            ))
95        }
96
97        fn can_sign_with(&self, _key: &IdentityPublicKey) -> bool {
98            true
99        }
100    }
101
102    fn identity_with_transfer_key() -> Identity {
103        let platform_version = PlatformVersion::latest();
104        let mut identity =
105            Identity::random_identity(0, Some(1), platform_version).expect("identity");
106        let mut rng = StdRng::seed_from_u64(42);
107        let (key, _) = IdentityPublicKey::random_key_with_known_attributes(
108            0,
109            &mut rng,
110            Purpose::TRANSFER,
111            SecurityLevel::CRITICAL,
112            KeyType::ECDSA_SECP256K1,
113            None,
114            platform_version,
115        )
116        .expect("transfer key");
117        identity.add_public_key(key);
118        identity
119    }
120
121    #[tokio::test]
122    async fn test_build_shield_from_identity_rejects_zero_amount() {
123        let identity = identity_with_transfer_key();
124        let result = build_shield_from_identity_transition(
125            &identity,
126            &test_orchard_address(),
127            0,
128            1,
129            &DummySigner,
130            None,
131            0,
132            &TestProver,
133            [0u8; 36],
134            None,
135            PlatformVersion::latest(),
136        )
137        .await;
138        let err = result
139            .expect_err("zero amount must be rejected")
140            .to_string();
141        assert!(err.contains("greater than zero"), "unexpected error: {err}");
142    }
143
144    #[tokio::test]
145    async fn test_build_shield_from_identity_transition_valid() {
146        let identity = identity_with_transfer_key();
147        let result = build_shield_from_identity_transition(
148            &identity,
149            &test_orchard_address(),
150            50_000,
151            7,
152            &DummySigner,
153            None,
154            3,
155            &TestProver,
156            [0u8; 36],
157            None,
158            PlatformVersion::latest(),
159        )
160        .await;
161        let st = result.expect("expected a built transition");
162        let StateTransition::ShieldFromIdentity(t) = st else {
163            panic!("expected ShieldFromIdentity, got {st:?}");
164        };
165        assert_eq!(t.identity_id(), identity.id());
166        assert_eq!(t.amount(), 50_000);
167        assert_eq!(t.nonce(), 7);
168        assert!(!t.actions().is_empty());
169        let key = identity
170            .get_first_public_key_matching(
171                Purpose::TRANSFER,
172                SecurityLevel::full_range().into(),
173                KeyType::all_key_types().into(),
174                true,
175            )
176            .expect("transfer key");
177        assert_eq!(t.signature_public_key_id(), key.id());
178    }
179}