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#[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}