Skip to main content

dpp/shielded/builder/
shield_from_identity.rs

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