Skip to main content

dpp/state_transition/
mod.rs

1use derive_more::From;
2#[cfg(feature = "serde-conversion")]
3use serde::{Deserialize, Serialize};
4use state_transitions::document::batch_transition::batched_transition::document_transition::DocumentTransition;
5use std::collections::BTreeMap;
6use std::ops::RangeInclusive;
7
8use platform_value::{BinaryData, Identifier};
9pub use state_transition_types::*;
10
11use bincode::{Decode, DecodeUntrusted, Encode};
12#[cfg(any(
13    feature = "state-transition-signing",
14    feature = "state-transition-validation"
15))]
16use dashcore::signer;
17#[cfg(feature = "state-transition-validation")]
18use dashcore::signer::double_sha;
19use platform_serialization_derive::{
20    PlatformDeserializeTrusted, PlatformDeserializeUntrusted, PlatformSerialize, PlatformSignable,
21};
22use platform_version::version::{PlatformVersion, ProtocolVersion, ALL_VERSIONS, LATEST_VERSION};
23
24#[cfg(any(
25    feature = "state-transition-signing",
26    feature = "state-transition-validation"
27))]
28use crate::BlsModule;
29use crate::ProtocolError;
30
31mod state_transition_types;
32
33pub mod state_transition_factory;
34
35pub mod errors;
36#[cfg(feature = "state-transition-signing")]
37use crate::util::hash::ripemd160_sha256;
38use crate::util::hash::{hash_double_to_vec, hash_single};
39
40pub mod proof_result;
41mod serialization;
42pub mod state_transitions;
43mod traits;
44
45// pub mod state_transition_fee;
46
47#[cfg(feature = "state-transition-validation")]
48use crate::consensus::basic::UnsupportedFeatureError;
49#[cfg(feature = "state-transition-signing")]
50use crate::consensus::signature::InvalidSignaturePublicKeySecurityLevelError;
51#[cfg(feature = "state-transition-signing")]
52use crate::consensus::signature::{
53    ContractBoundedKeyNonBatchError, ContractBoundedKeyOutOfBoundsError,
54};
55#[cfg(feature = "state-transition-validation")]
56use crate::consensus::signature::{
57    InvalidStateTransitionSignatureError, PublicKeyIsDisabledError, SignatureError,
58};
59#[cfg(feature = "state-transition-validation")]
60use crate::consensus::ConsensusError;
61pub use traits::*;
62
63use crate::address_funds::PlatformAddress;
64use crate::data_contract::serialized_version::DataContractInSerializationFormat;
65use crate::fee::Credits;
66#[cfg(any(
67    feature = "state-transition-signing",
68    feature = "state-transition-validation"
69))]
70use crate::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
71#[cfg(feature = "state-transition-signing")]
72use crate::identity::identity_public_key::contract_bounds::BatchedTransitionBoundsCheck;
73#[cfg(feature = "state-transition-signing")]
74use crate::identity::signer::Signer;
75use crate::identity::state_transition::OptionallyAssetLockProved;
76use crate::identity::Purpose;
77#[cfg(any(
78    feature = "state-transition-signing",
79    feature = "state-transition-validation"
80))]
81use crate::identity::{IdentityPublicKey, KeyType};
82use crate::identity::{KeyID, SecurityLevel};
83use crate::prelude::{AddressNonce, AssetLockProof, UserFeeIncrease};
84use crate::serialization::{PlatformDeserializableUntrusted, Signable};
85use crate::state_transition::address_credit_withdrawal_transition::{
86    AddressCreditWithdrawalTransition, AddressCreditWithdrawalTransitionSignable,
87};
88use crate::state_transition::address_funding_from_asset_lock_transition::{
89    AddressFundingFromAssetLockTransition, AddressFundingFromAssetLockTransitionSignable,
90};
91use crate::state_transition::address_funds_transfer_transition::{
92    AddressFundsTransferTransition, AddressFundsTransferTransitionSignable,
93};
94use crate::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0;
95use crate::state_transition::batch_transition::batched_transition::BatchedTransitionRef;
96#[cfg(feature = "state-transition-signing")]
97use crate::state_transition::batch_transition::resolvers::v0::BatchTransitionResolversV0;
98use crate::state_transition::batch_transition::{BatchTransition, BatchTransitionSignable};
99use crate::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0;
100use crate::state_transition::data_contract_create_transition::{
101    DataContractCreateTransition, DataContractCreateTransitionSignable,
102};
103use crate::state_transition::data_contract_update_transition::accessors::DataContractUpdateTransitionAccessorsV0;
104use crate::state_transition::data_contract_update_transition::{
105    DataContractUpdateTransition, DataContractUpdateTransitionSignable,
106};
107#[cfg(feature = "state-transition-signing")]
108use crate::state_transition::errors::InvalidSignaturePublicKeyError;
109#[cfg(all(feature = "state-transitions", feature = "validation"))]
110use crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError;
111#[cfg(feature = "state-transition-signing")]
112use crate::state_transition::errors::WrongPublicKeyPurposeError;
113#[cfg(feature = "state-transition-validation")]
114use crate::state_transition::errors::{
115    InvalidIdentityPublicKeyTypeError, PublicKeyMismatchError, StateTransitionIsNotSignedError,
116};
117use crate::state_transition::identity_create_from_addresses_transition::accessors::IdentityCreateFromAddressesTransitionAccessorsV0;
118use crate::state_transition::identity_create_from_addresses_transition::{
119    IdentityCreateFromAddressesTransition, IdentityCreateFromAddressesTransitionSignable,
120};
121use crate::state_transition::identity_create_from_shielded_pool_transition::accessors::IdentityCreateFromShieldedPoolTransitionAccessorsV0;
122use crate::state_transition::identity_create_from_shielded_pool_transition::{
123    IdentityCreateFromShieldedPoolTransition, IdentityCreateFromShieldedPoolTransitionSignable,
124};
125use crate::state_transition::identity_create_transition::accessors::IdentityCreateTransitionAccessorsV0;
126use crate::state_transition::identity_create_transition::{
127    IdentityCreateTransition, IdentityCreateTransitionSignable,
128};
129use crate::state_transition::identity_credit_transfer_to_addresses_transition::{
130    IdentityCreditTransferToAddressesTransition,
131    IdentityCreditTransferToAddressesTransitionSignable,
132};
133use crate::state_transition::identity_credit_transfer_transition::{
134    IdentityCreditTransferTransition, IdentityCreditTransferTransitionSignable,
135};
136use crate::state_transition::identity_credit_withdrawal_transition::{
137    IdentityCreditWithdrawalTransition, IdentityCreditWithdrawalTransitionSignable,
138};
139use crate::state_transition::identity_key_limits_update_transition::{
140    IdentityKeyLimitsUpdateTransition, IdentityKeyLimitsUpdateTransitionSignable,
141};
142use crate::state_transition::identity_top_up_from_shielded_pool_transition::{
143    IdentityTopUpFromShieldedPoolTransition, IdentityTopUpFromShieldedPoolTransitionSignable,
144};
145use crate::state_transition::identity_topup_from_addresses_transition::{
146    IdentityTopUpFromAddressesTransition, IdentityTopUpFromAddressesTransitionSignable,
147};
148use crate::state_transition::identity_topup_transition::{
149    IdentityTopUpTransition, IdentityTopUpTransitionSignable,
150};
151use crate::state_transition::identity_update_transition::accessors::IdentityUpdateTransitionAccessorsV0;
152use crate::state_transition::identity_update_transition::{
153    IdentityUpdateTransition, IdentityUpdateTransitionSignable,
154};
155use crate::state_transition::masternode_vote_transition::MasternodeVoteTransition;
156use crate::state_transition::masternode_vote_transition::MasternodeVoteTransitionSignable;
157use crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation;
158use crate::state_transition::shield_from_asset_lock_transition::{
159    ShieldFromAssetLockTransition, ShieldFromAssetLockTransitionSignable,
160};
161use crate::state_transition::shield_from_identity_transition::{
162    ShieldFromIdentityTransition, ShieldFromIdentityTransitionSignable,
163};
164use crate::state_transition::shield_transition::{ShieldTransition, ShieldTransitionSignable};
165use crate::state_transition::shielded_transfer_transition::{
166    ShieldedTransferTransition, ShieldedTransferTransitionSignable,
167};
168use crate::state_transition::shielded_withdrawal_transition::{
169    ShieldedWithdrawalTransition, ShieldedWithdrawalTransitionSignable,
170};
171#[cfg(feature = "state-transition-signing")]
172use crate::state_transition::state_transitions::document::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0;
173use crate::state_transition::unshield_transition::{
174    UnshieldTransition, UnshieldTransitionSignable,
175};
176use state_transitions::document::batch_transition::batched_transition::token_transition::TokenTransition;
177pub use state_transitions::*;
178
179pub type GetDataContractSecurityLevelRequirementFn =
180    fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>;
181
182macro_rules! call_method {
183    ($state_transition:expr, $method:ident, $args:tt ) => {
184        match $state_transition {
185            StateTransition::DataContractCreate(st) => st.$method($args),
186            StateTransition::DataContractUpdate(st) => st.$method($args),
187            StateTransition::Batch(st) => st.$method($args),
188            StateTransition::IdentityCreate(st) => st.$method($args),
189            StateTransition::IdentityTopUp(st) => st.$method($args),
190            StateTransition::IdentityCreditWithdrawal(st) => st.$method($args),
191            StateTransition::IdentityUpdate(st) => st.$method($args),
192            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method($args),
193            StateTransition::IdentityCreditTransfer(st) => st.$method($args),
194            StateTransition::MasternodeVote(st) => st.$method($args),
195            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($args),
196            StateTransition::IdentityCreateFromAddresses(st) => st.$method($args),
197            StateTransition::IdentityTopUpFromAddresses(st) => st.$method($args),
198            StateTransition::AddressFundsTransfer(st) => st.$method($args),
199            StateTransition::AddressFundingFromAssetLock(st) => st.$method($args),
200            StateTransition::AddressCreditWithdrawal(st) => st.$method($args),
201            StateTransition::Shield(st) => st.$method($args),
202            StateTransition::ShieldedTransfer(st) => st.$method($args),
203            StateTransition::Unshield(st) => st.$method($args),
204            StateTransition::IdentityTopUpFromShieldedPool(st) => st.$method($args),
205            StateTransition::ShieldFromAssetLock(st) => st.$method($args),
206            StateTransition::ShieldedWithdrawal(st) => st.$method($args),
207            StateTransition::IdentityCreateFromShieldedPool(st) => st.$method($args),
208            StateTransition::ShieldFromIdentity(st) => st.$method($args),
209        }
210    };
211    ($state_transition:expr, $method:ident ) => {
212        match $state_transition {
213            StateTransition::DataContractCreate(st) => st.$method(),
214            StateTransition::DataContractUpdate(st) => st.$method(),
215            StateTransition::Batch(st) => st.$method(),
216            StateTransition::IdentityCreate(st) => st.$method(),
217            StateTransition::IdentityTopUp(st) => st.$method(),
218            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
219            StateTransition::IdentityUpdate(st) => st.$method(),
220            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method(),
221            StateTransition::IdentityCreditTransfer(st) => st.$method(),
222            StateTransition::MasternodeVote(st) => st.$method(),
223            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
224            StateTransition::IdentityCreateFromAddresses(st) => st.$method(),
225            StateTransition::IdentityTopUpFromAddresses(st) => st.$method(),
226            StateTransition::AddressFundsTransfer(st) => st.$method(),
227            StateTransition::AddressFundingFromAssetLock(st) => st.$method(),
228            StateTransition::AddressCreditWithdrawal(st) => st.$method(),
229            StateTransition::Shield(st) => st.$method(),
230            StateTransition::ShieldedTransfer(st) => st.$method(),
231            StateTransition::Unshield(st) => st.$method(),
232            StateTransition::IdentityTopUpFromShieldedPool(st) => st.$method(),
233            StateTransition::ShieldFromAssetLock(st) => st.$method(),
234            StateTransition::ShieldedWithdrawal(st) => st.$method(),
235            StateTransition::IdentityCreateFromShieldedPool(st) => st.$method(),
236            StateTransition::ShieldFromIdentity(st) => st.$method(),
237        }
238    };
239}
240
241macro_rules! call_getter_method_identity_signed {
242    ($state_transition:expr, $method:ident, $args:tt ) => {
243        match $state_transition {
244            StateTransition::DataContractCreate(st) => Some(st.$method($args)),
245            StateTransition::DataContractUpdate(st) => Some(st.$method($args)),
246            StateTransition::Batch(st) => Some(st.$method($args)),
247            StateTransition::IdentityCreate(_) => None,
248            StateTransition::IdentityTopUp(_) => None,
249            StateTransition::IdentityCreditWithdrawal(st) => Some(st.$method($args)),
250            StateTransition::IdentityUpdate(st) => Some(st.$method($args)),
251            StateTransition::IdentityKeyLimitsUpdate(st) => Some(st.$method($args)),
252            StateTransition::IdentityCreditTransfer(st) => Some(st.$method($args)),
253            StateTransition::MasternodeVote(st) => Some(st.$method($args)),
254            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.$method($args)),
255            StateTransition::IdentityCreateFromAddresses(_) => None,
256            StateTransition::IdentityTopUpFromAddresses(_) => None,
257            StateTransition::AddressFundsTransfer(_) => None,
258            StateTransition::AddressFundingFromAssetLock(_) => None,
259            StateTransition::AddressCreditWithdrawal(_) => None,
260            StateTransition::Shield(_) => None,
261            StateTransition::ShieldedTransfer(_) => None,
262            StateTransition::Unshield(_) => None,
263            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
264            StateTransition::ShieldFromAssetLock(_) => None,
265            StateTransition::ShieldedWithdrawal(_) => None,
266            StateTransition::IdentityCreateFromShieldedPool(_) => None,
267            StateTransition::ShieldFromIdentity(st) => Some(st.$method($args)),
268        }
269    };
270    ($state_transition:expr, $method:ident ) => {
271        match $state_transition {
272            StateTransition::DataContractCreate(st) => Some(st.$method()),
273            StateTransition::DataContractUpdate(st) => Some(st.$method()),
274            StateTransition::Batch(st) => Some(st.$method()),
275            StateTransition::IdentityCreate(_) => None,
276            StateTransition::IdentityTopUp(_) => None,
277            StateTransition::IdentityCreditWithdrawal(st) => Some(st.$method()),
278            StateTransition::IdentityUpdate(st) => Some(st.$method()),
279            StateTransition::IdentityKeyLimitsUpdate(st) => Some(st.$method()),
280            StateTransition::IdentityCreditTransfer(st) => Some(st.$method()),
281            StateTransition::MasternodeVote(st) => Some(st.$method()),
282            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.$method()),
283            StateTransition::IdentityCreateFromAddresses(_) => None,
284            StateTransition::IdentityTopUpFromAddresses(_) => None,
285            StateTransition::AddressFundsTransfer(_) => None,
286            StateTransition::AddressFundingFromAssetLock(_) => None,
287            StateTransition::AddressCreditWithdrawal(_) => None,
288            StateTransition::Shield(_) => None,
289            StateTransition::ShieldedTransfer(_) => None,
290            StateTransition::Unshield(_) => None,
291            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
292            StateTransition::ShieldFromAssetLock(_) => None,
293            StateTransition::ShieldedWithdrawal(_) => None,
294            StateTransition::IdentityCreateFromShieldedPool(_) => None,
295            StateTransition::ShieldFromIdentity(st) => Some(st.$method()),
296        }
297    };
298}
299
300macro_rules! call_method_identity_signed {
301    ($state_transition:expr, $method:ident, $args:tt ) => {
302        match $state_transition {
303            StateTransition::DataContractCreate(st) => st.$method($args),
304            StateTransition::DataContractUpdate(st) => st.$method($args),
305            StateTransition::Batch(st) => st.$method($args),
306            StateTransition::IdentityCreate(_st) => {}
307            StateTransition::IdentityTopUp(_st) => {}
308            StateTransition::IdentityCreditWithdrawal(st) => st.$method($args),
309            StateTransition::IdentityUpdate(st) => st.$method($args),
310            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method($args),
311            StateTransition::IdentityCreditTransfer(st) => st.$method($args),
312            StateTransition::MasternodeVote(st) => st.$method($args),
313            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($args),
314            StateTransition::IdentityCreateFromAddresses(_) => {}
315            StateTransition::IdentityTopUpFromAddresses(_) => {}
316            StateTransition::AddressFundsTransfer(_) => {}
317            StateTransition::AddressFundingFromAssetLock(_) => {}
318            StateTransition::AddressCreditWithdrawal(_) => {}
319            StateTransition::Shield(_) => {}
320            StateTransition::ShieldedTransfer(_) => {}
321            StateTransition::Unshield(_) => {}
322            StateTransition::IdentityTopUpFromShieldedPool(_) => {}
323            StateTransition::ShieldFromAssetLock(_) => {}
324            StateTransition::ShieldedWithdrawal(_) => {}
325            StateTransition::IdentityCreateFromShieldedPool(_) => {}
326            StateTransition::ShieldFromIdentity(st) => st.$method($args),
327        }
328    };
329    ($state_transition:expr, $method:ident ) => {
330        match $state_transition {
331            StateTransition::DataContractCreate(st) => st.$method(),
332            StateTransition::DataContractUpdate(st) => st.$method(),
333            StateTransition::Batch(st) => st.$method(),
334            StateTransition::IdentityCreate(st) => {}
335            StateTransition::IdentityTopUp(st) => {}
336            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
337            StateTransition::IdentityUpdate(st) => st.$method(),
338            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method(),
339            StateTransition::IdentityCreditTransfer(st) => st.$method(),
340            StateTransition::MasternodeVote(st) => st.$method(),
341            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
342            StateTransition::IdentityCreateFromAddresses(_) => {}
343            StateTransition::IdentityTopUpFromAddresses(_) => {}
344            StateTransition::AddressFundsTransfer(_) => {}
345            StateTransition::AddressFundingFromAssetLock(_) => {}
346            StateTransition::AddressCreditWithdrawal(_) => {}
347            StateTransition::Shield(_) => {}
348            StateTransition::ShieldedTransfer(_) => {}
349            StateTransition::Unshield(_) => {}
350            StateTransition::IdentityTopUpFromShieldedPool(_) => {}
351            StateTransition::ShieldFromAssetLock(_) => {}
352            StateTransition::ShieldedWithdrawal(_) => {}
353            StateTransition::IdentityCreateFromShieldedPool(_) => {}
354            StateTransition::ShieldFromIdentity(st) => st.$method(),
355        }
356    };
357}
358
359#[cfg(feature = "state-transition-signing")]
360macro_rules! call_errorable_method_identity_signed {
361    ($state_transition:expr, $method:ident, $( $arg:expr ),* ) => {
362        match $state_transition {
363            StateTransition::DataContractCreate(st) => st.$method($( $arg ),*),
364            StateTransition::DataContractUpdate(st) => st.$method($( $arg ),*),
365            StateTransition::Batch(st) => st.$method($( $arg ),*),
366            StateTransition::IdentityCreate(_) => Err(ProtocolError::CorruptedCodeExecution(
367                "identity create can not be called for identity signing".to_string(),
368            )),
369            StateTransition::IdentityTopUp(_) => Err(ProtocolError::CorruptedCodeExecution(
370                "identity top up can not be called for identity signing".to_string(),
371            )),
372            StateTransition::IdentityCreditWithdrawal(st) => st.$method($( $arg ),*),
373            StateTransition::IdentityUpdate(st) => st.$method($( $arg ),*),
374            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method($( $arg ),*),
375            StateTransition::IdentityCreditTransfer(st) => st.$method($( $arg ),*),
376            StateTransition::MasternodeVote(st) => st.$method($( $arg ),*),
377            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method($( $arg ),*),
378            StateTransition::IdentityCreateFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
379                "identity create from addresses can not be called for identity signing".to_string(),
380            )),
381            StateTransition::IdentityTopUpFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
382                "identity top up from addresses can not be called for identity signing".to_string(),
383            )),
384            StateTransition::AddressFundsTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
385                "address funds transfer can not be called for identity signing".to_string(),
386            )),
387            StateTransition::AddressFundingFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
388                "address funding from asset lock can not be called for identity signing".to_string(),
389            )),
390            StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
391                "address credit withdrawal can not be called for identity signing".to_string(),
392            )),
393            StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution(
394                "shield transition can not be called for identity signing".to_string(),
395            )),
396            StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
397                "shielded transfer transition can not be called for identity signing".to_string(),
398            )),
399            StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution(
400                "unshield transition can not be called for identity signing".to_string(),
401            )),
402            StateTransition::IdentityTopUpFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
403                "identity top up from shielded pool transition can not be called for identity signing".to_string(),
404            )),
405            StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
406                "shield from asset lock transition can not be called for identity signing".to_string(),
407            )),
408            StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
409                "shielded withdrawal transition can not be called for identity signing".to_string(),
410            )),
411            StateTransition::IdentityCreateFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
412                "identity create from shielded pool transition can not be called for identity signing".to_string(),
413            )),
414            StateTransition::ShieldFromIdentity(st) => st.$method($( $arg ),*),
415        }
416    };
417    ($state_transition:expr, $method:ident) => {
418        match $state_transition {
419            StateTransition::DataContractCreate(st) => st.$method(),
420            StateTransition::DataContractUpdate(st) => st.$method(),
421            StateTransition::Batch(st) => st.$method(),
422            StateTransition::IdentityCreate(_) => Err(ProtocolError::CorruptedCodeExecution(
423                "identity create can not be called for identity signing".to_string(),
424            )),
425            StateTransition::IdentityTopUp(_) => Err(ProtocolError::CorruptedCodeExecution(
426                "identity top up can not be called for identity signing".to_string(),
427            )),
428            StateTransition::IdentityCreditWithdrawal(st) => st.$method(),
429            StateTransition::IdentityUpdate(st) => st.$method(),
430            StateTransition::IdentityKeyLimitsUpdate(st) => st.$method(),
431            StateTransition::IdentityCreditTransfer(st) => st.$method(),
432            StateTransition::MasternodeVote(st) => st.$method(),
433            StateTransition::IdentityCreditTransferToAddresses(st) => st.$method(),
434            StateTransition::IdentityCreateFromAddresses(st) => Err(ProtocolError::CorruptedCodeExecution(
435                "identity create from addresses can not be called for identity signing".to_string(),
436            )),
437            StateTransition::IdentityTopUpFromAddresses(_) => Err(ProtocolError::CorruptedCodeExecution(
438                "identity top up from addresses can not be called for identity signing".to_string(),
439            )),
440            StateTransition::AddressFundsTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
441                "address funds transfer can not be called for identity signing".to_string(),
442            )),
443            StateTransition::AddressFundingFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
444                "address funding from asset lock can not be called for identity signing".to_string(),
445            )),
446            StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
447                "address credit withdrawal can not be called for identity signing".to_string(),
448            )),
449            StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution(
450                "shield transition can not be called for identity signing".to_string(),
451            )),
452            StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution(
453                "shielded transfer transition can not be called for identity signing".to_string(),
454            )),
455            StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution(
456                "unshield transition can not be called for identity signing".to_string(),
457            )),
458            StateTransition::IdentityTopUpFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
459                "identity top up from shielded pool transition can not be called for identity signing".to_string(),
460            )),
461            StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution(
462                "shield from asset lock transition can not be called for identity signing".to_string(),
463            )),
464            StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution(
465                "shielded withdrawal transition can not be called for identity signing".to_string(),
466            )),
467            StateTransition::IdentityCreateFromShieldedPool(_) => Err(ProtocolError::CorruptedCodeExecution(
468                "identity create from shielded pool transition can not be called for identity signing".to_string(),
469            )),
470            StateTransition::ShieldFromIdentity(st) => st.$method(),
471        }
472    };
473}
474
475#[derive(
476    Debug,
477    Clone,
478    Encode,
479    Decode,
480    PlatformSerialize,
481    PlatformDeserializeTrusted,
482    PlatformDeserializeUntrusted,
483    PlatformSignable,
484    From,
485    PartialEq,
486    DecodeUntrusted,
487)]
488// `tag = "$type"` matches the system-field convention: every serde-injected
489// discriminator key in this crate carries a `$` prefix so it never collides
490// with user-data field names. Discriminates between **semantically
491// different variants** of the same kind (rather than **versions** of one
492// logical type, which use `tag = "$formatVersion"`).
493//
494// `$type` here is at the OUTERMOST level — there's no flatten path that
495// would put it next to a base's `document_type_name` (renamed to `$type`
496// in the wire). Inner umbrellas (`DocumentTransition`, `TokenTransition`)
497// use `$action` instead because they DO flatten the document base.
498//
499// Was previously `serde(untagged)`, which made deserialize ambiguous (each
500// variant tried in order until one matched structurally). The new
501// self-describing wire shape is `{"$type": "dataContractCreate", ...inner
502// fields...}`.
503//
504// The binary wire path (`PlatformSerialize`) is unchanged — only JSON/Value
505// consumers see the new shape, and there are no rs-drive / rs-drive-abci /
506// rs-sdk callers that route the umbrella through to_json/to_object today.
507#[cfg_attr(
508    feature = "serde-conversion",
509    derive(Serialize, Deserialize),
510    serde(tag = "$type", rename_all = "camelCase")
511)]
512#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version
513#[platform_serialize(limit = 100000)]
514pub enum StateTransition {
515    DataContractCreate(DataContractCreateTransition),
516    DataContractUpdate(DataContractUpdateTransition),
517    Batch(BatchTransition),
518    IdentityCreate(IdentityCreateTransition),
519    IdentityTopUp(IdentityTopUpTransition),
520    IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition),
521    IdentityUpdate(IdentityUpdateTransition),
522    IdentityCreditTransfer(IdentityCreditTransferTransition),
523    MasternodeVote(MasternodeVoteTransition),
524    IdentityCreditTransferToAddresses(IdentityCreditTransferToAddressesTransition),
525    IdentityCreateFromAddresses(IdentityCreateFromAddressesTransition),
526    IdentityTopUpFromAddresses(IdentityTopUpFromAddressesTransition),
527    AddressFundsTransfer(AddressFundsTransferTransition),
528    AddressFundingFromAssetLock(AddressFundingFromAssetLockTransition),
529    AddressCreditWithdrawal(AddressCreditWithdrawalTransition),
530    Shield(ShieldTransition),
531    ShieldedTransfer(ShieldedTransferTransition),
532    Unshield(UnshieldTransition),
533    ShieldFromAssetLock(ShieldFromAssetLockTransition),
534    ShieldedWithdrawal(ShieldedWithdrawalTransition),
535    IdentityCreateFromShieldedPool(IdentityCreateFromShieldedPoolTransition),
536    ShieldFromIdentity(ShieldFromIdentityTransition),
537    IdentityTopUpFromShieldedPool(IdentityTopUpFromShieldedPoolTransition),
538    IdentityKeyLimitsUpdate(IdentityKeyLimitsUpdateTransition),
539}
540
541#[cfg(all(feature = "json-conversion", feature = "serde-conversion"))]
542impl crate::serialization::JsonConvertible for StateTransition {}
543
544#[cfg(all(feature = "value-conversion", feature = "serde-conversion"))]
545impl crate::serialization::ValueConvertible for StateTransition {}
546
547#[cfg(all(
548    test,
549    feature = "json-conversion",
550    feature = "value-conversion",
551    feature = "serde-conversion"
552))]
553mod json_convertible_tests {
554    use super::*;
555
556    /// Round-trip a StateTransition through both JSON and Value, asserting:
557    /// 1. The wire emits `{"$type": "<expected_tag>", ...}` (umbrella's
558    ///    `tag = "$type", rename_all = "camelCase"` is correctly applied).
559    /// 2. Round-trip preserves the variant.
560    /// 3. Round-trip preserves structural equality (PartialEq on the inner).
561    ///
562    /// Inner field shapes are covered by each inner type's dedicated
563    /// `*_with_full_wire_shape` test — this helper only exercises the
564    /// umbrella's tag-dispatch boundary. The risk it catches: an inner
565    /// variant whose serde body conflicts with the umbrella's `"$type"` key,
566    /// or a serde rename that resolves to something other than the
567    /// expected camelCase form.
568    ///
569    /// `lossy_json_int_variants`: when true, the JSON-side equality assertion
570    /// runs after `normalize_integer_variants_for_json_round_trip` on both
571    /// sides. Required for variants that embed a `DataContract` —
572    /// `document_schemas` carry sized integer variants (`U32`/`I32`) that
573    /// JSON's single Number type cannot preserve. See commit 7397c73f31.
574    fn assert_umbrella_round_trip_inner(
575        original: StateTransition,
576        expected_type_tag: &str,
577        lossy_json_int_variants: bool,
578    ) {
579        use crate::serialization::{JsonConvertible, ValueConvertible};
580
581        // JSON
582        let json = original.to_json().expect("to_json");
583        assert_eq!(
584            json["$type"], expected_type_tag,
585            "json type tag for {expected_type_tag}",
586        );
587        let recovered = StateTransition::from_json(json).expect("from_json round-trip");
588        assert_eq!(
589            std::mem::discriminant(&original),
590            std::mem::discriminant(&recovered),
591            "json round-trip variant for {expected_type_tag}",
592        );
593        if lossy_json_int_variants {
594            use crate::tests::utils::normalize_integer_variants_for_json_round_trip;
595            let mut original_canon = original.to_object().expect("to_object");
596            let mut recovered_canon = recovered.to_object().expect("to_object");
597            normalize_integer_variants_for_json_round_trip(&mut original_canon);
598            normalize_integer_variants_for_json_round_trip(&mut recovered_canon);
599            assert_eq!(
600                original_canon, recovered_canon,
601                "json round-trip equality (modulo int-variant) for {expected_type_tag}",
602            );
603        } else {
604            assert_eq!(
605                original, recovered,
606                "json round-trip equality for {expected_type_tag}"
607            );
608        }
609
610        // Value
611        let value = original.to_object().expect("to_object");
612        let map = value.as_map().expect("Value::Map");
613        let tag = map
614            .iter()
615            .find(|(k, _)| k.as_text() == Some("$type"))
616            .map(|(_, v)| v)
617            .unwrap_or_else(|| panic!("type tag missing for {expected_type_tag}"));
618        assert_eq!(
619            *tag,
620            platform_value::Value::Text(expected_type_tag.to_string()),
621            "value type tag for {expected_type_tag}",
622        );
623        let recovered = StateTransition::from_object(value).expect("from_object round-trip");
624        assert_eq!(
625            std::mem::discriminant(&original),
626            std::mem::discriminant(&recovered),
627            "value round-trip variant for {expected_type_tag}",
628        );
629        assert_eq!(
630            original, recovered,
631            "value round-trip equality for {expected_type_tag}"
632        );
633    }
634
635    fn assert_umbrella_round_trip(original: StateTransition, expected_type_tag: &str) {
636        assert_umbrella_round_trip_inner(original, expected_type_tag, false);
637    }
638
639    /// Variant of `assert_umbrella_round_trip` for transitions that embed a
640    /// `DataContract` (`DataContractCreate`, `DataContractUpdate`). JSON's
641    /// single Number type collapses sized-int variants in the embedded
642    /// `document_schemas` tree, so the JSON-side equality assertion is
643    /// run modulo integer-variant normalization. The Value path keeps its
644    /// strict bit-exact assertion (platform_value preserves sized ints).
645    fn assert_umbrella_round_trip_lossy_json_int_variants(
646        original: StateTransition,
647        expected_type_tag: &str,
648    ) {
649        assert_umbrella_round_trip_inner(original, expected_type_tag, true);
650    }
651
652    // Per-variant umbrella round-trip tests. Inner fixtures are reused from
653    // each transition's own `json_convertible_tests::fixture()` (made
654    // `pub(crate)` for this purpose) — keeps the umbrella tests in sync
655    // with the inner-type tests automatically.
656
657    #[test]
658    fn umbrella_data_contract_create() {
659        let inner = crate::state_transition::data_contract_create_transition::json_convertible_tests::fixture();
660        assert_umbrella_round_trip_lossy_json_int_variants(
661            StateTransition::DataContractCreate(inner),
662            "dataContractCreate",
663        );
664    }
665
666    #[test]
667    fn umbrella_data_contract_update() {
668        let inner = crate::state_transition::data_contract_update_transition::json_convertible_tests::fixture();
669        assert_umbrella_round_trip_lossy_json_int_variants(
670            StateTransition::DataContractUpdate(inner),
671            "dataContractUpdate",
672        );
673    }
674
675    #[test]
676    fn umbrella_batch() {
677        let inner = crate::state_transition::batch_transition::json_convertible_tests::fixture();
678        assert_umbrella_round_trip(StateTransition::Batch(inner), "batch");
679    }
680
681    #[test]
682    fn umbrella_identity_create() {
683        let inner =
684            crate::state_transition::identity_create_transition::json_convertible_tests::fixture();
685        assert_umbrella_round_trip(StateTransition::IdentityCreate(inner), "identityCreate");
686    }
687
688    #[test]
689    fn umbrella_identity_top_up() {
690        let inner =
691            crate::state_transition::identity_topup_transition::json_convertible_tests::fixture();
692        assert_umbrella_round_trip(StateTransition::IdentityTopUp(inner), "identityTopUp");
693    }
694
695    #[test]
696    fn umbrella_identity_credit_withdrawal() {
697        let inner = crate::state_transition::identity_credit_withdrawal_transition::json_convertible_tests::fixture();
698        assert_umbrella_round_trip(
699            StateTransition::IdentityCreditWithdrawal(inner),
700            "identityCreditWithdrawal",
701        );
702    }
703
704    #[test]
705    fn umbrella_identity_update() {
706        let inner =
707            crate::state_transition::identity_update_transition::json_convertible_tests::fixture();
708        assert_umbrella_round_trip(StateTransition::IdentityUpdate(inner), "identityUpdate");
709    }
710
711    #[test]
712    fn umbrella_identity_key_limits_update() {
713        let inner = crate::state_transition::identity_key_limits_update_transition::json_convertible_tests::fixture();
714        assert_umbrella_round_trip(
715            StateTransition::IdentityKeyLimitsUpdate(inner),
716            "identityKeyLimitsUpdate",
717        );
718    }
719
720    #[test]
721    fn umbrella_identity_credit_transfer() {
722        let inner = crate::state_transition::identity_credit_transfer_transition::json_convertible_tests::fixture();
723        assert_umbrella_round_trip(
724            StateTransition::IdentityCreditTransfer(inner),
725            "identityCreditTransfer",
726        );
727    }
728
729    #[test]
730    fn umbrella_masternode_vote() {
731        let inner =
732            crate::state_transition::masternode_vote_transition::json_convertible_tests::fixture();
733        assert_umbrella_round_trip(StateTransition::MasternodeVote(inner), "masternodeVote");
734    }
735
736    #[test]
737    fn umbrella_identity_credit_transfer_to_addresses() {
738        let inner = crate::state_transition::identity_credit_transfer_to_addresses_transition::json_convertible_tests::fixture();
739        assert_umbrella_round_trip(
740            StateTransition::IdentityCreditTransferToAddresses(inner),
741            "identityCreditTransferToAddresses",
742        );
743    }
744
745    #[test]
746    fn umbrella_identity_create_from_addresses() {
747        let inner = crate::state_transition::identity_create_from_addresses_transition::json_convertible_tests::fixture();
748        assert_umbrella_round_trip(
749            StateTransition::IdentityCreateFromAddresses(inner),
750            "identityCreateFromAddresses",
751        );
752    }
753
754    #[test]
755    fn umbrella_identity_top_up_from_addresses() {
756        let inner = crate::state_transition::identity_topup_from_addresses_transition::json_convertible_tests::fixture();
757        assert_umbrella_round_trip(
758            StateTransition::IdentityTopUpFromAddresses(inner),
759            "identityTopUpFromAddresses",
760        );
761    }
762
763    #[test]
764    fn umbrella_address_funds_transfer() {
765        let inner = crate::state_transition::address_funds_transfer_transition::json_convertible_tests::fixture();
766        assert_umbrella_round_trip(
767            StateTransition::AddressFundsTransfer(inner),
768            "addressFundsTransfer",
769        );
770    }
771
772    #[test]
773    fn umbrella_address_funding_from_asset_lock() {
774        let inner = crate::state_transition::address_funding_from_asset_lock_transition::json_convertible_tests::fixture();
775        assert_umbrella_round_trip(
776            StateTransition::AddressFundingFromAssetLock(inner),
777            "addressFundingFromAssetLock",
778        );
779    }
780
781    #[test]
782    fn umbrella_address_credit_withdrawal() {
783        let inner = crate::state_transition::address_credit_withdrawal_transition::json_convertible_tests::fixture();
784        assert_umbrella_round_trip(
785            StateTransition::AddressCreditWithdrawal(inner),
786            "addressCreditWithdrawal",
787        );
788    }
789
790    #[test]
791    fn umbrella_shield() {
792        let inner = crate::state_transition::shield_transition::json_convertible_tests::fixture();
793        assert_umbrella_round_trip(StateTransition::Shield(inner), "shield");
794    }
795
796    #[test]
797    fn umbrella_shielded_transfer() {
798        let inner =
799            crate::state_transition::shielded_transfer_transition::json_convertible_tests::fixture(
800            );
801        assert_umbrella_round_trip(StateTransition::ShieldedTransfer(inner), "shieldedTransfer");
802    }
803
804    #[test]
805    fn umbrella_unshield() {
806        let inner = crate::state_transition::unshield_transition::json_convertible_tests::fixture();
807        assert_umbrella_round_trip(StateTransition::Unshield(inner), "unshield");
808    }
809
810    #[test]
811    fn umbrella_shield_from_asset_lock() {
812        let inner = crate::state_transition::shield_from_asset_lock_transition::json_convertible_tests::fixture();
813        assert_umbrella_round_trip(
814            StateTransition::ShieldFromAssetLock(inner),
815            "shieldFromAssetLock",
816        );
817    }
818
819    #[test]
820    fn umbrella_shielded_withdrawal() {
821        let inner = crate::state_transition::shielded_withdrawal_transition::json_convertible_tests::fixture();
822        assert_umbrella_round_trip(
823            StateTransition::ShieldedWithdrawal(inner),
824            "shieldedWithdrawal",
825        );
826    }
827
828    #[test]
829    fn umbrella_identity_create_from_shielded_pool() {
830        let inner = crate::state_transition::identity_create_from_shielded_pool_transition::json_convertible_tests::fixture();
831        assert_umbrella_round_trip(
832            StateTransition::IdentityCreateFromShieldedPool(inner),
833            "identityCreateFromShieldedPool",
834        );
835    }
836
837    #[test]
838    fn umbrella_identity_top_up_from_shielded_pool() {
839        let inner = crate::state_transition::identity_top_up_from_shielded_pool_transition::json_convertible_tests::fixture();
840        assert_umbrella_round_trip(
841            StateTransition::IdentityTopUpFromShieldedPool(inner),
842            "identityTopUpFromShieldedPool",
843        );
844    }
845
846    #[test]
847    fn umbrella_shield_from_identity() {
848        let inner = crate::state_transition::shield_from_identity_transition::json_convertible_tests::fixture();
849        assert_umbrella_round_trip(
850            StateTransition::ShieldFromIdentity(inner),
851            "shieldFromIdentity",
852        );
853    }
854}
855
856impl OptionallyAssetLockProved for StateTransition {
857    fn optional_asset_lock_proof(&self) -> Option<&AssetLockProof> {
858        match self {
859            StateTransition::IdentityCreate(st) => st.optional_asset_lock_proof(),
860            StateTransition::IdentityTopUp(st) => st.optional_asset_lock_proof(),
861            StateTransition::ShieldFromAssetLock(st) => st.optional_asset_lock_proof(),
862            _ => None,
863        }
864    }
865}
866
867/// The state transition signing options
868#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
869pub struct StateTransitionSigningOptions {
870    /// This will allow signing with any security level for debugging purposes
871    pub allow_signing_with_any_security_level: bool,
872    /// This will allow signing with any purpose for debugging purposes
873    pub allow_signing_with_any_purpose: bool,
874}
875
876/// The active range of a transition carrying `keys`: from protocol version 14 when one of them
877/// is bound to a contract group or is a version 1 key (the format that can carry a budget or an
878/// expiry), `otherwise` when none is.
879fn active_version_range_for_keys_in_creation(
880    keys: &[IdentityPublicKeyInCreation],
881    otherwise: RangeInclusive<ProtocolVersion>,
882) -> RangeInclusive<ProtocolVersion> {
883    if IdentityPublicKeyInCreation::first_bound_to_a_contract_group(keys).is_some()
884        || IdentityPublicKeyInCreation::first_in_version_1_format(keys).is_some()
885    {
886        14..=LATEST_VERSION
887    } else {
888        otherwise
889    }
890}
891
892impl StateTransition {
893    #[allow(unused_variables)]
894    pub fn deserialize_from_bytes_untrusted_in_version(
895        bytes: &[u8],
896        platform_version: &PlatformVersion,
897    ) -> Result<Self, ProtocolError> {
898        let max_value_depth = platform_version
899            .system_limits
900            .max_document_value_depth
901            .map(usize::from);
902        let state_transition =
903            platform_value::with_value_decode_depth_limit(max_value_depth, || {
904                StateTransition::deserialize_from_bytes_untrusted(bytes)
905            })?;
906        #[cfg(all(feature = "state-transitions", feature = "validation"))]
907        {
908            let active_version_range = state_transition.active_version_range();
909
910            // Tests are done with very high protocol ranges, while we could put this behind a feature,
911            // that would probably be overkill.
912            if active_version_range.contains(&platform_version.protocol_version)
913                || platform_version.protocol_version > 268435456
914            {
915                Ok(state_transition)
916            } else {
917                Err(ProtocolError::StateTransitionError(
918                    StateTransitionIsNotActiveError {
919                        state_transition_type: state_transition.name(),
920                        active_version_range,
921                        current_protocol_version: platform_version.protocol_version,
922                    },
923                ))
924            }
925        }
926        #[cfg(not(all(feature = "state-transitions", feature = "validation")))]
927        Ok(state_transition)
928    }
929
930    pub fn active_version_range(&self) -> RangeInclusive<ProtocolVersion> {
931        match self {
932            StateTransition::DataContractCreate(data_contract_create_transition) => {
933                match data_contract_create_transition {
934                    // Version 1 carries contract groups, which exist from protocol version 14.
935                    // The embedded contract format alone would admit it at 9 and above, where
936                    // the group data would be silently dropped.
937                    DataContractCreateTransition::V1(_) => 14..=LATEST_VERSION,
938                    DataContractCreateTransition::V0(_) => {
939                        match data_contract_create_transition.data_contract() {
940                            DataContractInSerializationFormat::V0(_) => ALL_VERSIONS,
941                            DataContractInSerializationFormat::V1(_) => 9..=LATEST_VERSION,
942                        }
943                    }
944                }
945            }
946            StateTransition::DataContractUpdate(data_contract_update_transition) => {
947                match data_contract_update_transition.data_contract() {
948                    DataContractInSerializationFormat::V0(_) => ALL_VERSIONS,
949                    DataContractInSerializationFormat::V1(_) => 9..=LATEST_VERSION,
950                }
951            }
952            StateTransition::Batch(batch_transition) => match batch_transition {
953                BatchTransition::V0(_) => ALL_VERSIONS,
954                BatchTransition::V1(_) => 9..=LATEST_VERSION,
955            },
956            // A key bound to a contract group, and a version 1 key, exist from protocol version
957            // 14, so a transition carrying one is inactive before that: an earlier version
958            // rejects it without charging, exactly as a binary that cannot decode it does.
959            StateTransition::IdentityCreate(st) => {
960                active_version_range_for_keys_in_creation(st.public_keys(), ALL_VERSIONS)
961            }
962            StateTransition::IdentityUpdate(st) => {
963                active_version_range_for_keys_in_creation(st.public_keys_to_add(), ALL_VERSIONS)
964            }
965            StateTransition::IdentityCreateFromAddresses(st) => {
966                active_version_range_for_keys_in_creation(st.public_keys(), 11..=LATEST_VERSION)
967            }
968            StateTransition::IdentityCreateFromShieldedPool(st) => {
969                active_version_range_for_keys_in_creation(st.public_keys(), 12..=LATEST_VERSION)
970            }
971            StateTransition::IdentityTopUp(_)
972            | StateTransition::IdentityCreditWithdrawal(_)
973            | StateTransition::IdentityCreditTransfer(_)
974            | StateTransition::MasternodeVote(_) => ALL_VERSIONS,
975            StateTransition::IdentityCreditTransferToAddresses(_)
976            | StateTransition::IdentityTopUpFromAddresses(_)
977            | StateTransition::AddressFundsTransfer(_)
978            | StateTransition::AddressFundingFromAssetLock(_)
979            | StateTransition::AddressCreditWithdrawal(_) => 11..=LATEST_VERSION,
980            StateTransition::Shield(_)
981            | StateTransition::ShieldedTransfer(_)
982            | StateTransition::Unshield(_)
983            | StateTransition::ShieldFromAssetLock(_)
984            | StateTransition::ShieldedWithdrawal(_) => 12..=LATEST_VERSION,
985            StateTransition::ShieldFromIdentity(_)
986            | StateTransition::IdentityTopUpFromShieldedPool(_)
987            | StateTransition::IdentityKeyLimitsUpdate(_) => 14..=LATEST_VERSION,
988        }
989    }
990
991    pub fn is_identity_signed(&self) -> bool {
992        !matches!(
993            self,
994            StateTransition::IdentityCreate(_)
995                | StateTransition::IdentityTopUp(_)
996                | StateTransition::Shield(_)
997                | StateTransition::ShieldedTransfer(_)
998                | StateTransition::Unshield(_)
999                | StateTransition::ShieldFromAssetLock(_)
1000                | StateTransition::ShieldedWithdrawal(_)
1001                | StateTransition::IdentityCreateFromShieldedPool(_)
1002                | StateTransition::IdentityTopUpFromShieldedPool(_)
1003        )
1004    }
1005
1006    pub fn required_asset_lock_balance_for_processing_start(
1007        &self,
1008        platform_version: &PlatformVersion,
1009    ) -> Result<Credits, ProtocolError> {
1010        match self {
1011            StateTransition::IdentityCreate(st) => {
1012                st.calculate_min_required_fee(platform_version)
1013            }
1014            StateTransition::IdentityTopUp(st) => {
1015                st.calculate_min_required_fee(platform_version)
1016            }
1017            StateTransition::AddressFundingFromAssetLock(st) => {
1018                st.calculate_min_required_fee(platform_version)
1019            }
1020            StateTransition::ShieldFromAssetLock(st) => {
1021                st.calculate_min_required_fee(platform_version)
1022            }
1023            st => Err(ProtocolError::CorruptedCodeExecution(format!("{} is not an asset lock transaction, but we are calling required_asset_lock_balance_for_processing_start", st.name()))),
1024        }
1025    }
1026
1027    fn hash(&self, skip_signature: bool) -> Result<Vec<u8>, ProtocolError> {
1028        if skip_signature {
1029            Ok(hash_double_to_vec(self.signable_bytes()?))
1030        } else {
1031            Ok(hash_double_to_vec(
1032                crate::serialization::PlatformSerializable::serialize_to_bytes(self)?,
1033            ))
1034        }
1035    }
1036
1037    /// Returns state transition name
1038    pub fn name(&self) -> String {
1039        match self {
1040            Self::DataContractCreate(_) => "DataContractCreate".to_string(),
1041            Self::DataContractUpdate(_) => "DataContractUpdate".to_string(),
1042            Self::Batch(batch_transition) => {
1043                let mut document_transition_types = vec![];
1044                for transition in batch_transition.transitions_iter() {
1045                    let document_transition_name = match transition {
1046                        BatchedTransitionRef::Document(DocumentTransition::Create(_)) => "Create",
1047                        BatchedTransitionRef::Document(DocumentTransition::Replace(_)) => "Replace",
1048                        BatchedTransitionRef::Document(DocumentTransition::Delete(_)) => "Delete",
1049                        BatchedTransitionRef::Document(DocumentTransition::Transfer(_)) => {
1050                            "Transfer"
1051                        }
1052                        BatchedTransitionRef::Document(DocumentTransition::UpdatePrice(_)) => {
1053                            "UpdatePrice"
1054                        }
1055                        BatchedTransitionRef::Document(DocumentTransition::Purchase(_)) => {
1056                            "Purchase"
1057                        }
1058                        BatchedTransitionRef::Document(DocumentTransition::IndexOnlyDelete(_)) => {
1059                            "IndexOnlyDelete"
1060                        }
1061                        BatchedTransitionRef::Token(TokenTransition::Transfer(_)) => {
1062                            "TokenTransfer"
1063                        }
1064                        BatchedTransitionRef::Token(TokenTransition::Mint(_)) => "TokenMint",
1065                        BatchedTransitionRef::Token(TokenTransition::Burn(_)) => "TokenBurn",
1066                        BatchedTransitionRef::Token(TokenTransition::Freeze(_)) => "TokenFreeze",
1067                        BatchedTransitionRef::Token(TokenTransition::Unfreeze(_)) => {
1068                            "TokenUnfreeze"
1069                        }
1070                        BatchedTransitionRef::Token(TokenTransition::DestroyFrozenFunds(_)) => {
1071                            "TokenDestroyFrozenFunds"
1072                        }
1073                        BatchedTransitionRef::Token(TokenTransition::EmergencyAction(_)) => {
1074                            "TokenEmergencyAction"
1075                        }
1076                        BatchedTransitionRef::Token(TokenTransition::ConfigUpdate(_)) => {
1077                            "TokenConfigUpdate"
1078                        }
1079                        BatchedTransitionRef::Token(TokenTransition::Claim(_)) => "TokenClaim",
1080                        BatchedTransitionRef::Token(TokenTransition::DirectPurchase(_)) => {
1081                            "TokenDirectPurchase"
1082                        }
1083                        BatchedTransitionRef::Token(
1084                            TokenTransition::SetPriceForDirectPurchase(_),
1085                        ) => "SetPriceForDirectPurchase",
1086                    };
1087                    document_transition_types.push(document_transition_name);
1088                }
1089                format!("DocumentsBatch([{}])", document_transition_types.join(", "))
1090            }
1091            Self::IdentityCreate(_) => "IdentityCreate".to_string(),
1092            Self::IdentityTopUp(_) => "IdentityTopUp".to_string(),
1093            Self::IdentityCreditWithdrawal(_) => "IdentityCreditWithdrawal".to_string(),
1094            Self::IdentityUpdate(_) => "IdentityUpdate".to_string(),
1095            Self::IdentityKeyLimitsUpdate(_) => "IdentityKeyLimitsUpdate".to_string(),
1096            Self::IdentityCreditTransfer(_) => "IdentityCreditTransfer".to_string(),
1097            Self::MasternodeVote(_) => "MasternodeVote".to_string(),
1098            Self::IdentityCreditTransferToAddresses(_) => {
1099                "IdentityCreditTransferToAddresses".to_string()
1100            }
1101            Self::IdentityCreateFromAddresses(_) => "IdentityCreateFromAddresses".to_string(),
1102            Self::IdentityTopUpFromAddresses(_) => "IdentityTopUpFromAddresses".to_string(),
1103            Self::AddressFundsTransfer(_) => "AddressFundsTransfer".to_string(),
1104            Self::AddressFundingFromAssetLock(_) => "AddressFundingFromAssetLock".to_string(),
1105            Self::AddressCreditWithdrawal(_) => "AddressCreditWithdrawal".to_string(),
1106            Self::Shield(_) => "Shield".to_string(),
1107            Self::ShieldedTransfer(_) => "ShieldedTransfer".to_string(),
1108            Self::Unshield(_) => "Unshield".to_string(),
1109            Self::IdentityTopUpFromShieldedPool(_) => "IdentityTopUpFromShieldedPool".to_string(),
1110            Self::ShieldFromAssetLock(_) => "ShieldFromAssetLock".to_string(),
1111            Self::ShieldedWithdrawal(_) => "ShieldedWithdrawal".to_string(),
1112            Self::IdentityCreateFromShieldedPool(_) => "IdentityCreateFromShieldedPool".to_string(),
1113            Self::ShieldFromIdentity(_) => "ShieldFromIdentity".to_string(),
1114        }
1115    }
1116
1117    /// returns the signature as a byte-array
1118    pub fn signature(&self) -> Option<&BinaryData> {
1119        match self {
1120            StateTransition::DataContractCreate(st) => Some(st.signature()),
1121            StateTransition::DataContractUpdate(st) => Some(st.signature()),
1122            StateTransition::Batch(st) => Some(st.signature()),
1123            StateTransition::IdentityCreate(st) => Some(st.signature()),
1124            StateTransition::IdentityTopUp(st) => Some(st.signature()),
1125            StateTransition::IdentityCreditWithdrawal(st) => Some(st.signature()),
1126            StateTransition::IdentityUpdate(st) => Some(st.signature()),
1127            StateTransition::IdentityKeyLimitsUpdate(st) => Some(st.signature()),
1128            StateTransition::IdentityCreditTransfer(st) => Some(st.signature()),
1129            StateTransition::MasternodeVote(st) => Some(st.signature()),
1130            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.signature()),
1131            StateTransition::IdentityCreateFromAddresses(_) => None,
1132            StateTransition::IdentityTopUpFromAddresses(_) => None,
1133            StateTransition::AddressFundsTransfer(_) => None,
1134            StateTransition::AddressFundingFromAssetLock(st) => Some(st.signature()),
1135            StateTransition::AddressCreditWithdrawal(_) => None,
1136            StateTransition::Shield(_) => None,
1137            StateTransition::ShieldedTransfer(_) => None,
1138            StateTransition::Unshield(_) => None,
1139            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
1140            StateTransition::ShieldFromAssetLock(st) => Some(st.signature()),
1141            StateTransition::ShieldedWithdrawal(_) => None,
1142            StateTransition::IdentityCreateFromShieldedPool(_) => None,
1143            StateTransition::ShieldFromIdentity(st) => Some(st.signature()),
1144        }
1145    }
1146
1147    /// returns the number of private keys
1148    pub fn required_number_of_private_keys(&self) -> u16 {
1149        match self {
1150            StateTransition::IdentityCreateFromAddresses(st) => st.inputs().len() as u16,
1151            StateTransition::IdentityTopUpFromAddresses(st) => st.inputs().len() as u16,
1152            StateTransition::AddressFundsTransfer(st) => st.inputs().len() as u16,
1153            StateTransition::AddressCreditWithdrawal(st) => st.inputs().len() as u16,
1154            StateTransition::Shield(st) => st.inputs().len() as u16,
1155            StateTransition::ShieldedTransfer(_) => 0,
1156            StateTransition::Unshield(_) => 0,
1157            StateTransition::IdentityTopUpFromShieldedPool(_) => 0,
1158            StateTransition::ShieldFromAssetLock(_) => 0,
1159            StateTransition::ShieldedWithdrawal(_) => 0,
1160            StateTransition::IdentityCreateFromShieldedPool(_) => 0,
1161            _ => 1,
1162        }
1163    }
1164
1165    /// returns the fee_increase additional percentage multiplier, it affects only processing costs
1166    pub fn user_fee_increase(&self) -> UserFeeIncrease {
1167        match self {
1168            StateTransition::DataContractCreate(st) => st.user_fee_increase(),
1169            StateTransition::DataContractUpdate(st) => st.user_fee_increase(),
1170            StateTransition::Batch(st) => st.user_fee_increase(),
1171            StateTransition::IdentityCreate(st) => st.user_fee_increase(),
1172            StateTransition::IdentityTopUp(st) => st.user_fee_increase(),
1173            StateTransition::IdentityCreditWithdrawal(st) => st.user_fee_increase(),
1174            StateTransition::IdentityUpdate(st) => st.user_fee_increase(),
1175            StateTransition::IdentityKeyLimitsUpdate(st) => st.user_fee_increase(),
1176            StateTransition::IdentityCreditTransfer(st) => st.user_fee_increase(),
1177            StateTransition::IdentityCreditTransferToAddresses(st) => st.user_fee_increase(),
1178            StateTransition::IdentityCreateFromAddresses(st) => st.user_fee_increase(),
1179            StateTransition::IdentityTopUpFromAddresses(st) => st.user_fee_increase(),
1180            StateTransition::AddressFundsTransfer(st) => st.user_fee_increase(),
1181            StateTransition::AddressFundingFromAssetLock(st) => st.user_fee_increase(),
1182            StateTransition::AddressCreditWithdrawal(st) => st.user_fee_increase(),
1183            StateTransition::Shield(st) => st.user_fee_increase(),
1184            // These transitions don't support user fee adjustment
1185            StateTransition::ShieldFromAssetLock(_) => 0,
1186            StateTransition::MasternodeVote(_) => 0,
1187            StateTransition::ShieldedTransfer(_) => 0,
1188            StateTransition::Unshield(_) => 0,
1189            StateTransition::IdentityTopUpFromShieldedPool(_) => 0,
1190            StateTransition::ShieldedWithdrawal(_) => 0,
1191            StateTransition::IdentityCreateFromShieldedPool(_) => 0,
1192            StateTransition::ShieldFromIdentity(st) => st.user_fee_increase(),
1193        }
1194    }
1195
1196    /// Calculates the estimated minimum fee required for this state transition.
1197    ///
1198    /// The fee is calculated based on the number of inputs, outputs, and any
1199    /// transition-specific costs (e.g., key creation costs for identity creation).
1200    ///
1201    /// # Arguments
1202    ///
1203    /// * `platform_version` - The platform version containing fee configuration.
1204    ///
1205    /// # Returns
1206    ///
1207    /// The estimated fee in credits.
1208    fn calculate_estimated_fee(
1209        &self,
1210        platform_version: &PlatformVersion,
1211    ) -> Result<Credits, ProtocolError> {
1212        call_method!(self, calculate_min_required_fee, platform_version)
1213    }
1214
1215    /// The transaction id is a single hash of the data with the signature
1216    pub fn transaction_id(&self) -> Result<[u8; 32], ProtocolError> {
1217        Ok(hash_single(
1218            crate::serialization::PlatformSerializable::serialize_to_bytes(self)?,
1219        ))
1220    }
1221
1222    /// returns the signature as a byte-array
1223    pub fn signature_public_key_id(&self) -> Option<KeyID> {
1224        call_getter_method_identity_signed!(self, signature_public_key_id)
1225    }
1226
1227    /// returns the key security level requirement for the state transition
1228    pub fn security_level_requirement(&self, purpose: Purpose) -> Option<Vec<SecurityLevel>> {
1229        call_getter_method_identity_signed!(self, security_level_requirement, purpose)
1230    }
1231
1232    /// returns the key purpose requirement for the state transition
1233    pub fn purpose_requirement(&self) -> Option<Vec<Purpose>> {
1234        call_getter_method_identity_signed!(self, purpose_requirement)
1235    }
1236
1237    /// returns the signature as a byte-array
1238    pub fn owner_id(&self) -> Option<Identifier> {
1239        match self {
1240            StateTransition::DataContractCreate(st) => Some(st.owner_id()),
1241            StateTransition::DataContractUpdate(st) => Some(st.owner_id()),
1242            StateTransition::Batch(st) => Some(st.owner_id()),
1243            StateTransition::IdentityCreate(st) => Some(st.owner_id()),
1244            StateTransition::IdentityTopUp(st) => Some(st.owner_id()),
1245            StateTransition::IdentityCreditWithdrawal(st) => Some(st.owner_id()),
1246            StateTransition::IdentityUpdate(st) => Some(st.owner_id()),
1247            StateTransition::IdentityKeyLimitsUpdate(st) => Some(st.owner_id()),
1248            StateTransition::IdentityCreditTransfer(st) => Some(st.owner_id()),
1249            StateTransition::MasternodeVote(st) => Some(st.owner_id()),
1250            StateTransition::IdentityCreditTransferToAddresses(st) => Some(st.owner_id()),
1251            StateTransition::IdentityCreateFromAddresses(_) => None,
1252            StateTransition::IdentityTopUpFromAddresses(_) => None,
1253            StateTransition::AddressFundsTransfer(_) => None,
1254            StateTransition::AddressFundingFromAssetLock(_) => None,
1255            StateTransition::AddressCreditWithdrawal(_) => None,
1256            StateTransition::Shield(_) => None,
1257            StateTransition::ShieldedTransfer(_) => None,
1258            StateTransition::Unshield(_) => None,
1259            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
1260            StateTransition::ShieldFromAssetLock(_) => None,
1261            StateTransition::ShieldedWithdrawal(_) => None,
1262            StateTransition::IdentityCreateFromShieldedPool(_) => None,
1263            StateTransition::ShieldFromIdentity(st) => Some(st.owner_id()),
1264        }
1265    }
1266
1267    /// returns the signature as a byte-array
1268    pub fn inputs(&self) -> Option<&BTreeMap<PlatformAddress, (AddressNonce, Credits)>> {
1269        match self {
1270            StateTransition::DataContractCreate(_)
1271            | StateTransition::DataContractUpdate(_)
1272            | StateTransition::Batch(_)
1273            | StateTransition::IdentityCreate(_)
1274            | StateTransition::IdentityTopUp(_)
1275            | StateTransition::IdentityCreditWithdrawal(_)
1276            | StateTransition::IdentityUpdate(_)
1277            | StateTransition::IdentityKeyLimitsUpdate(_)
1278            | StateTransition::IdentityCreditTransfer(_)
1279            | StateTransition::MasternodeVote(_)
1280            | StateTransition::IdentityCreditTransferToAddresses(_) => None,
1281            StateTransition::IdentityCreateFromAddresses(st) => Some(st.inputs()),
1282            StateTransition::IdentityTopUpFromAddresses(st) => Some(st.inputs()),
1283            StateTransition::AddressFundsTransfer(st) => Some(st.inputs()),
1284            StateTransition::AddressFundingFromAssetLock(st) => Some(st.inputs()),
1285            StateTransition::AddressCreditWithdrawal(st) => Some(st.inputs()),
1286            StateTransition::Shield(st) => Some(st.inputs()),
1287            StateTransition::ShieldedTransfer(_) => None,
1288            StateTransition::Unshield(_) => None,
1289            StateTransition::IdentityTopUpFromShieldedPool(_) => None,
1290            StateTransition::ShieldFromAssetLock(_) => None,
1291            StateTransition::ShieldedWithdrawal(_) => None,
1292            StateTransition::IdentityCreateFromShieldedPool(_) => None,
1293            StateTransition::ShieldFromIdentity(_) => None,
1294        }
1295    }
1296
1297    /// returns the state transition type
1298    pub fn state_transition_type(&self) -> StateTransitionType {
1299        call_method!(self, state_transition_type)
1300    }
1301
1302    /// returns the unique identifiers for the state transition
1303    pub fn unique_identifiers(&self) -> Vec<String> {
1304        call_method!(self, unique_identifiers)
1305    }
1306
1307    /// set a new signature
1308    pub fn set_signature(&mut self, signature: BinaryData) -> bool {
1309        match self {
1310            StateTransition::DataContractCreate(st) => {
1311                st.set_signature(signature);
1312                true
1313            }
1314            StateTransition::DataContractUpdate(st) => {
1315                st.set_signature(signature);
1316                true
1317            }
1318            StateTransition::Batch(st) => {
1319                st.set_signature(signature);
1320                true
1321            }
1322            StateTransition::IdentityCreate(st) => {
1323                st.set_signature(signature);
1324                true
1325            }
1326            StateTransition::IdentityTopUp(st) => {
1327                st.set_signature(signature);
1328                true
1329            }
1330            StateTransition::IdentityCreditWithdrawal(st) => {
1331                st.set_signature(signature);
1332                true
1333            }
1334            StateTransition::IdentityUpdate(st) => {
1335                st.set_signature(signature);
1336                true
1337            }
1338            StateTransition::IdentityKeyLimitsUpdate(st) => {
1339                st.set_signature(signature);
1340                true
1341            }
1342            StateTransition::IdentityCreditTransfer(st) => {
1343                st.set_signature(signature);
1344                true
1345            }
1346            StateTransition::MasternodeVote(st) => {
1347                st.set_signature(signature);
1348                true
1349            }
1350            StateTransition::IdentityCreditTransferToAddresses(st) => {
1351                st.set_signature(signature);
1352                true
1353            }
1354            StateTransition::IdentityCreateFromAddresses(_)
1355            | StateTransition::IdentityTopUpFromAddresses(_)
1356            | StateTransition::AddressFundsTransfer(_)
1357            | StateTransition::Shield(_)
1358            | StateTransition::ShieldedTransfer(_)
1359            | StateTransition::Unshield(_)
1360            | StateTransition::ShieldedWithdrawal(_)
1361            | StateTransition::IdentityCreateFromShieldedPool(_)
1362            | StateTransition::IdentityTopUpFromShieldedPool(_) => false,
1363            StateTransition::ShieldFromIdentity(st) => {
1364                st.set_signature(signature);
1365                true
1366            }
1367            StateTransition::AddressFundingFromAssetLock(st) => {
1368                st.set_signature(signature);
1369                true
1370            }
1371            StateTransition::ShieldFromAssetLock(st) => {
1372                st.set_signature(signature);
1373                true
1374            }
1375            StateTransition::AddressCreditWithdrawal(_) => false,
1376        }
1377    }
1378
1379    /// set fee multiplier
1380    pub fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) {
1381        match self {
1382            StateTransition::DataContractCreate(st) => st.set_user_fee_increase(user_fee_increase),
1383            StateTransition::DataContractUpdate(st) => st.set_user_fee_increase(user_fee_increase),
1384            StateTransition::Batch(st) => st.set_user_fee_increase(user_fee_increase),
1385            StateTransition::IdentityCreate(st) => st.set_user_fee_increase(user_fee_increase),
1386            StateTransition::IdentityTopUp(st) => st.set_user_fee_increase(user_fee_increase),
1387            StateTransition::IdentityCreditWithdrawal(st) => {
1388                st.set_user_fee_increase(user_fee_increase)
1389            }
1390            StateTransition::IdentityUpdate(st) => st.set_user_fee_increase(user_fee_increase),
1391            StateTransition::IdentityKeyLimitsUpdate(st) => {
1392                st.set_user_fee_increase(user_fee_increase)
1393            }
1394            StateTransition::IdentityCreditTransfer(st) => {
1395                st.set_user_fee_increase(user_fee_increase)
1396            }
1397            StateTransition::IdentityCreditTransferToAddresses(st) => {
1398                st.set_user_fee_increase(user_fee_increase)
1399            }
1400            StateTransition::IdentityCreateFromAddresses(st) => {
1401                st.set_user_fee_increase(user_fee_increase)
1402            }
1403            StateTransition::IdentityTopUpFromAddresses(st) => {
1404                st.set_user_fee_increase(user_fee_increase)
1405            }
1406            StateTransition::AddressFundsTransfer(st) => {
1407                st.set_user_fee_increase(user_fee_increase)
1408            }
1409            StateTransition::AddressFundingFromAssetLock(st) => {
1410                st.set_user_fee_increase(user_fee_increase)
1411            }
1412            StateTransition::AddressCreditWithdrawal(st) => {
1413                st.set_user_fee_increase(user_fee_increase)
1414            }
1415            StateTransition::Shield(st) => st.set_user_fee_increase(user_fee_increase),
1416            // These transitions don't support user fee adjustment — no-op
1417            StateTransition::ShieldFromAssetLock(_) => {}
1418            StateTransition::MasternodeVote(_) => {}
1419            StateTransition::ShieldedTransfer(_) => {}
1420            StateTransition::Unshield(_) => {}
1421            StateTransition::IdentityTopUpFromShieldedPool(_) => {}
1422            StateTransition::ShieldedWithdrawal(_) => {}
1423            StateTransition::IdentityCreateFromShieldedPool(_) => {}
1424            StateTransition::ShieldFromIdentity(st) => st.set_user_fee_increase(user_fee_increase),
1425        }
1426    }
1427
1428    /// set a new signature
1429    pub fn set_signature_public_key_id(&mut self, public_key_id: KeyID) {
1430        call_method_identity_signed!(self, set_signature_public_key_id, public_key_id)
1431    }
1432
1433    #[cfg(feature = "state-transition-signing")]
1434    pub async fn sign_external<S: Signer<IdentityPublicKey>>(
1435        &mut self,
1436        identity_public_key: &IdentityPublicKey,
1437        signer: &S,
1438        get_data_contract_security_level_requirement: Option<
1439            impl Fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>,
1440        >,
1441    ) -> Result<(), ProtocolError> {
1442        self.sign_external_with_options(
1443            identity_public_key,
1444            signer,
1445            get_data_contract_security_level_requirement,
1446            StateTransitionSigningOptions::default(),
1447        )
1448        .await
1449    }
1450
1451    #[cfg(feature = "state-transition-signing")]
1452    pub async fn sign_external_with_options<S: Signer<IdentityPublicKey>>(
1453        &mut self,
1454        identity_public_key: &IdentityPublicKey,
1455        signer: &S,
1456        get_data_contract_security_level_requirement: Option<
1457            impl Fn(Identifier, String) -> Result<SecurityLevel, ProtocolError>,
1458        >,
1459        options: StateTransitionSigningOptions,
1460    ) -> Result<(), ProtocolError> {
1461        self.verify_identity_key_bounds(identity_public_key)?;
1462        match self {
1463            StateTransition::DataContractCreate(st) => {
1464                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1465                st.verify_public_key_is_enabled(identity_public_key)?;
1466            }
1467            StateTransition::DataContractUpdate(st) => {
1468                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1469                st.verify_public_key_is_enabled(identity_public_key)?;
1470            }
1471            StateTransition::Batch(st) => {
1472                let allow_token_transfer_keys = st.transitions_len() == 1
1473                    && (st
1474                        .first_transition()
1475                        .expect("expected first transition with len 1")
1476                        .as_transition_token_claim()
1477                        .is_some()
1478                        || st
1479                            .first_transition()
1480                            .expect("expected first transition with len 1")
1481                            .as_transition_token_transfer()
1482                            .is_some());
1483                let allowed_key_purposes = if allow_token_transfer_keys {
1484                    vec![Purpose::AUTHENTICATION, Purpose::TRANSFER]
1485                } else {
1486                    vec![Purpose::AUTHENTICATION]
1487                };
1488                if !options.allow_signing_with_any_purpose
1489                    && !allowed_key_purposes.contains(&identity_public_key.purpose())
1490                {
1491                    return Err(ProtocolError::WrongPublicKeyPurposeError(
1492                        WrongPublicKeyPurposeError::new(
1493                            identity_public_key.purpose(),
1494                            allowed_key_purposes,
1495                        ),
1496                    ));
1497                }
1498                if !options.allow_signing_with_any_security_level {
1499                    let security_level_requirement = st.combined_security_level_requirement(
1500                        get_data_contract_security_level_requirement,
1501                    )?;
1502                    if !security_level_requirement.contains(&identity_public_key.security_level()) {
1503                        return Err(ProtocolError::InvalidSignaturePublicKeySecurityLevelError(
1504                            InvalidSignaturePublicKeySecurityLevelError::new(
1505                                identity_public_key.security_level(),
1506                                security_level_requirement,
1507                            ),
1508                        ));
1509                    }
1510                }
1511                st.verify_public_key_is_enabled(identity_public_key)?;
1512            }
1513            StateTransition::IdentityCreditWithdrawal(st) => {
1514                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1515                st.verify_public_key_is_enabled(identity_public_key)?;
1516            }
1517            StateTransition::IdentityUpdate(st) => {
1518                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1519                st.verify_public_key_is_enabled(identity_public_key)?;
1520            }
1521            StateTransition::IdentityKeyLimitsUpdate(st) => {
1522                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1523                st.verify_public_key_is_enabled(identity_public_key)?;
1524            }
1525            StateTransition::IdentityCreditTransfer(st) => {
1526                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1527                st.verify_public_key_is_enabled(identity_public_key)?;
1528            }
1529            StateTransition::IdentityCreate(_) => {
1530                return Err(ProtocolError::CorruptedCodeExecution(
1531                    "identity create can not be called for identity signing".to_string(),
1532                ))
1533            }
1534            StateTransition::IdentityTopUp(_) => {
1535                return Err(ProtocolError::CorruptedCodeExecution(
1536                    "identity top up can not be called for identity signing".to_string(),
1537                ))
1538            }
1539            StateTransition::MasternodeVote(st) => {
1540                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1541                st.verify_public_key_is_enabled(identity_public_key)?;
1542            }
1543            StateTransition::IdentityCreditTransferToAddresses(st) => {
1544                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1545                st.verify_public_key_is_enabled(identity_public_key)?;
1546            }
1547            StateTransition::IdentityCreateFromAddresses(_) => {
1548                return Err(ProtocolError::CorruptedCodeExecution(
1549                    "identity create from addresses can not be called for identity signing"
1550                        .to_string(),
1551                ))
1552            }
1553            StateTransition::IdentityTopUpFromAddresses(_) => {
1554                return Err(ProtocolError::CorruptedCodeExecution(
1555                    "identity top up from addresses can not be called for identity signing"
1556                        .to_string(),
1557                ))
1558            }
1559            StateTransition::AddressFundsTransfer(_) => {
1560                return Err(ProtocolError::CorruptedCodeExecution(
1561                    "address funds transfer transition can not be called for identity signing"
1562                        .to_string(),
1563                ))
1564            }
1565            StateTransition::AddressFundingFromAssetLock(_) => {
1566                return Err(ProtocolError::CorruptedCodeExecution(
1567                    "address funding from asset lock transition can not be called for identity signing"
1568                        .to_string(),
1569                ))
1570            }
1571            StateTransition::AddressCreditWithdrawal(_) => {
1572                return Err(ProtocolError::CorruptedCodeExecution(
1573                    "address credit withdrawal transition can not be called for identity signing"
1574                        .to_string(),
1575                ))
1576            }
1577            StateTransition::Shield(_) => {
1578                return Err(ProtocolError::CorruptedCodeExecution(
1579                    "shield transition can not be called for identity signing".to_string(),
1580                ))
1581            }
1582            StateTransition::ShieldedTransfer(_) => {
1583                return Err(ProtocolError::CorruptedCodeExecution(
1584                    "shielded transfer transition can not be called for identity signing"
1585                        .to_string(),
1586                ))
1587            }
1588            StateTransition::Unshield(_) => {
1589                return Err(ProtocolError::CorruptedCodeExecution(
1590                    "unshield transition can not be called for identity signing".to_string(),
1591                ))
1592            }
1593            StateTransition::IdentityTopUpFromShieldedPool(_) => {
1594                return Err(ProtocolError::CorruptedCodeExecution(
1595                    "identity top up from shielded pool transition can not be called for identity signing".to_string(),
1596                ))
1597            }
1598            StateTransition::ShieldFromAssetLock(_) => {
1599                return Err(ProtocolError::CorruptedCodeExecution(
1600                    "shield from asset lock transition can not be called for identity signing"
1601                        .to_string(),
1602                ))
1603            }
1604            StateTransition::ShieldedWithdrawal(_) => {
1605                return Err(ProtocolError::CorruptedCodeExecution(
1606                    "shielded withdrawal transition can not be called for identity signing"
1607                        .to_string(),
1608                ))
1609            }
1610            StateTransition::IdentityCreateFromShieldedPool(_) => {
1611                return Err(ProtocolError::CorruptedCodeExecution(
1612                    "identity create from shielded pool transition can not be called for identity signing"
1613                        .to_string(),
1614                ))
1615            }
1616            StateTransition::ShieldFromIdentity(st) => {
1617                st.verify_public_key_level_and_purpose(identity_public_key, options)?;
1618                st.verify_public_key_is_enabled(identity_public_key)?;
1619            }
1620        }
1621        let data = self.signable_bytes()?;
1622        self.set_signature(signer.sign(identity_public_key, data.as_slice()).await?);
1623        self.set_signature_public_key_id(identity_public_key.id());
1624        Ok(())
1625    }
1626
1627    /// A contract-bound AUTHENTICATION key may only sign a Batch whose members are all inside
1628    /// its bounds. Consensus enforces the same rule from the stored key; checking here saves the
1629    /// round trip when the signing API is handed the key metadata. A contract group bound is
1630    /// left to consensus, which reads the group's memberships.
1631    #[cfg(feature = "state-transition-signing")]
1632    fn verify_identity_key_bounds(
1633        &self,
1634        identity_public_key: &IdentityPublicKey,
1635    ) -> Result<(), ProtocolError> {
1636        if identity_public_key.purpose() != Purpose::AUTHENTICATION {
1637            return Ok(());
1638        }
1639        let Some(bounds) = identity_public_key.contract_bounds() else {
1640            return Ok(());
1641        };
1642        match self {
1643            StateTransition::Batch(batch) => {
1644                use crate::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0;
1645                if batch.transitions_iter().all(|member| {
1646                    bounds.check_batched_transition(member) != BatchedTransitionBoundsCheck::Denied
1647                }) {
1648                    Ok(())
1649                } else {
1650                    Err(ProtocolError::ConsensusError(Box::new(
1651                        ContractBoundedKeyOutOfBoundsError::new(identity_public_key.id()).into(),
1652                    )))
1653                }
1654            }
1655            _ => Err(ProtocolError::ConsensusError(Box::new(
1656                ContractBoundedKeyNonBatchError::new(identity_public_key.id()).into(),
1657            ))),
1658        }
1659    }
1660
1661    #[cfg(feature = "state-transition-signing")]
1662    pub fn sign(
1663        &mut self,
1664        identity_public_key: &IdentityPublicKey,
1665        private_key: &[u8],
1666        bls: &impl BlsModule,
1667    ) -> Result<(), ProtocolError> {
1668        self.sign_with_options(
1669            identity_public_key,
1670            private_key,
1671            bls,
1672            StateTransitionSigningOptions::default(),
1673        )
1674    }
1675
1676    #[cfg(feature = "state-transition-signing")]
1677    pub fn sign_with_options(
1678        &mut self,
1679        identity_public_key: &IdentityPublicKey,
1680        private_key: &[u8],
1681        bls: &impl BlsModule,
1682        options: StateTransitionSigningOptions,
1683    ) -> Result<(), ProtocolError> {
1684        self.verify_identity_key_bounds(identity_public_key)?;
1685        call_errorable_method_identity_signed!(
1686            self,
1687            verify_public_key_level_and_purpose,
1688            identity_public_key,
1689            options
1690        )?;
1691        call_errorable_method_identity_signed!(
1692            self,
1693            verify_public_key_is_enabled,
1694            identity_public_key
1695        )?;
1696
1697        match identity_public_key.key_type() {
1698            KeyType::ECDSA_SECP256K1 => {
1699                let public_key_compressed = get_compressed_public_ec_key(private_key)?;
1700
1701                // we store compressed public key in the identity ,
1702                // and here we compare the private key used to sing the state transition with
1703                // the compressed key stored in the identity
1704
1705                if public_key_compressed.as_slice() != identity_public_key.data().as_slice() {
1706                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
1707                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
1708                    ));
1709                }
1710
1711                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
1712            }
1713            KeyType::ECDSA_HASH160 => {
1714                let public_key_compressed = get_compressed_public_ec_key(private_key)?;
1715                let pub_key_hash = ripemd160_sha256(&public_key_compressed);
1716
1717                if identity_public_key.data().as_slice() != pub_key_hash {
1718                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
1719                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
1720                    ));
1721                }
1722                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
1723            }
1724            KeyType::BLS12_381 => {
1725                let public_key = bls.private_key_to_public_key(private_key)?;
1726
1727                if public_key != identity_public_key.data().as_slice() {
1728                    return Err(ProtocolError::InvalidSignaturePublicKeyError(
1729                        InvalidSignaturePublicKeyError::new(identity_public_key.data().to_vec()),
1730                    ));
1731                }
1732                self.sign_by_private_key(private_key, identity_public_key.key_type(), bls)
1733            }
1734
1735            // the default behavior from
1736            // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransitionIdentitySigned.js#L108
1737            // is to return the error for the BIP13_SCRIPT_HASH
1738            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
1739                Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
1740                    InvalidIdentityPublicKeyTypeError::new(identity_public_key.key_type()),
1741                ))
1742            }
1743        }?;
1744
1745        self.set_signature_public_key_id(identity_public_key.id());
1746
1747        Ok(())
1748    }
1749
1750    #[cfg(feature = "state-transition-signing")]
1751    /// Signs data with the private key
1752    pub fn sign_by_private_key(
1753        &mut self,
1754        private_key: &[u8],
1755        key_type: KeyType,
1756        bls: &impl BlsModule,
1757    ) -> Result<(), ProtocolError> {
1758        let data = self.signable_bytes()?;
1759        match key_type {
1760            KeyType::BLS12_381 => {
1761                if !self.set_signature(bls.sign(&data, private_key)?.into()) {
1762                    return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1763                        needed: self.required_number_of_private_keys(),
1764                        using: 1,
1765                        msg: "failed to set BLS signature",
1766                    });
1767                }
1768            }
1769
1770            // https://github.com/dashevo/platform/blob/9c8e6a3b6afbc330a6ab551a689de8ccd63f9120/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L169
1771            KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => {
1772                let signature = signer::sign(&data, private_key)?;
1773                if !self.set_signature(signature.to_vec().into()) {
1774                    return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1775                        needed: self.required_number_of_private_keys(),
1776                        using: 1,
1777                        msg: "failed to set ECDSA signature",
1778                    });
1779                };
1780            }
1781
1782            // the default behavior from
1783            // https://github.com/dashevo/platform/blob/6b02b26e5cd3a7c877c5fdfe40c4a4385a8dda15/packages/js-dpp/lib/stateTransition/AbstractStateTransition.js#L187
1784            // is to return the error for the BIP13_SCRIPT_HASH
1785            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
1786                return Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
1787                    InvalidIdentityPublicKeyTypeError::new(key_type),
1788                ))
1789            }
1790        };
1791        Ok(())
1792    }
1793
1794    /// Sign `self.signable_bytes()` with an external Core-wallet signer and
1795    /// store the resulting Core-ECDSA signature in the transition's wrapper
1796    /// signature field.
1797    ///
1798    /// # Position in the signing-primitive family
1799    ///
1800    /// This is a **primitive** in the same family as
1801    /// [`Self::sign_by_private_key`] — it performs no validation of the
1802    /// transition variant, the key, or the relationship between them. It is
1803    /// the external-custody sibling of `sign_by_private_key`:
1804    ///
1805    /// | Primitive | Key source | Validation |
1806    /// |---|---|---|
1807    /// | [`Self::sign_by_private_key`] | raw `&[u8]` in host memory | none |
1808    /// | `sign_with_core_signer` | external signer (HSM / hardware wallet / secure enclave / remote signing service), key reached via BIP32 [`DerivationPath`] | none |
1809    ///
1810    /// Both produce **byte-identical** wrapper signatures over the same
1811    /// digest when given the same underlying private key (proven by
1812    /// `sign_with_signer_matches_sign_by_private_key_byte_for_byte` in this
1813    /// file's tests). The only difference is where the key bytes live: in
1814    /// host memory vs inside the signer's trust boundary. The signer
1815    /// performs the derive + sign + zeroise sequence atomically; this
1816    /// function never sees raw key material, only a 32-byte digest and the
1817    /// resulting signature.
1818    ///
1819    /// # Scope (what the BIP32 path means)
1820    ///
1821    /// The `path` parameter selects a key in the signer's Core wallet
1822    /// (BIP32-derived). For that path's signature to be **meaningful** the
1823    /// transition's wrapper signature field must itself carry a Core-key
1824    /// signature. Today that is exactly the four asset-lock-signed
1825    /// variants — `IdentityCreate`, `IdentityTopUp`,
1826    /// `AddressFundingFromAssetLock`, `ShieldFromAssetLock` — where the
1827    /// wrapper signature is the asset-lock proof signed by the credit
1828    /// output's Core key.
1829    ///
1830    /// For identity-signed variants (`DataContractCreate`, `Batch`,
1831    /// `IdentityCreditTransfer`, etc.) the wrapper signature is an
1832    /// identity-key signature paired with a `signature_public_key_id`,
1833    /// and the right external-signer entry point is [`Self::sign_external`]
1834    /// with a [`Signer<IdentityPublicKey>`](crate::identity::signer::Signer).
1835    /// Calling `sign_with_core_signer` on such a variant compiles and
1836    /// produces a structurally valid 65-byte signature, but the signature
1837    /// is **semantically meaningless** — Platform validation will reject
1838    /// the transition because the signature doesn't match the expected
1839    /// identity public key and `signature_public_key_id` isn't set. The
1840    /// same caveat applies to misusing `sign_by_private_key`, the sibling
1841    /// primitive — both rely on the caller passing a key the wrapper
1842    /// signature is *meant* to carry.
1843    ///
1844    /// # Wire-format parity with `sign_by_private_key`
1845    ///
1846    /// The byte layout of the stored signature mirrors
1847    /// `dashcore::signer::sign`:
1848    ///
1849    /// 1. `digest = double_sha256(self.signable_bytes()?)`
1850    /// 2. `signer.sign_ecdsa(path, digest).await` → non-recoverable
1851    ///    `(secp256k1::ecdsa::Signature, secp256k1::PublicKey)`.
1852    /// 3. Recover the recovery id by trying all four candidates against the
1853    ///    returned public key (libsecp256k1 normalises both signing paths to
1854    ///    low-s form so the 64-byte `r||s` payload is bit-identical).
1855    /// 4. Serialise as a 65-byte compact recoverable signature with the
1856    ///    `compressed` prefix convention used by `CompactSignature` — i.e.
1857    ///    `[recovery_id + 27 + 4, r (32) || s (32)]`.
1858    ///
1859    /// # Errors
1860    ///
1861    /// - Returns [`ProtocolError::ExternalSignerError`] wrapping the signer's
1862    ///   `Display` error when the underlying signer fails.
1863    /// - Returns [`ProtocolError::ExternalSignerError`] if no recovery id
1864    ///   matches the public key returned by the signer — this should be
1865    ///   unreachable for a conformant signer (invariant violation by a
1866    ///   non-conformant signer) but is surfaced rather than panicked on.
1867    /// - Returns [`ProtocolError::Generic`] if the SHA-256 transform did not
1868    ///   yield a 32-byte digest (defensive — should never happen).
1869    /// - Returns [`ProtocolError::InvalidVerificationWrongNumberOfElements`] if
1870    ///   `set_signature` rejects the result (matches `sign_by_private_key`).
1871    #[cfg(all(feature = "state-transition-signing", feature = "core_key_wallet"))]
1872    pub async fn sign_with_core_signer<S: ::key_wallet::signer::Signer>(
1873        &mut self,
1874        path: &::key_wallet::bip32::DerivationPath,
1875        signer: &S,
1876    ) -> Result<(), ProtocolError> {
1877        use dashcore::secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
1878        use dashcore::secp256k1::{Message, Secp256k1};
1879        use dashcore::signer::{double_sha, CompactSignature};
1880
1881        let data = self.signable_bytes()?;
1882        // Pre-image transform matches `dashcore::signer::sign`: double-SHA256
1883        // of the signable bytes is the actual ECDSA message digest.
1884        let data_hash = double_sha(&data);
1885        let digest: [u8; 32] = data_hash.as_slice().try_into().map_err(|_| {
1886            ProtocolError::Generic("double_sha did not return 32 bytes".to_string())
1887        })?;
1888
1889        let (signature, public_key) = signer
1890            .sign_ecdsa(path, digest)
1891            .await
1892            .map_err(|e| ProtocolError::ExternalSignerError(format!("signer failed: {}", e)))?;
1893
1894        // The signer returns a non-recoverable signature. The legacy path
1895        // stores a 65-byte recoverable compact signature, so we brute-force
1896        // the recovery id (0..3) by reconstructing a `RecoverableSignature`
1897        // and comparing the recovered public key with the one the signer
1898        // returned. secp256k1 normalises both `sign_ecdsa` and
1899        // `sign_ecdsa_recoverable` outputs to low-s form, so the 64-byte
1900        // `r||s` payload is bit-identical to what `dashcore::signer::sign`
1901        // produces.
1902        let compact_64 = signature.serialize_compact();
1903        let secp = Secp256k1::new();
1904        let msg = Message::from_digest(digest);
1905
1906        let mut found: Option<RecoverableSignature> = None;
1907        for id in 0..4i32 {
1908            let recid = match RecoveryId::try_from(id) {
1909                Ok(r) => r,
1910                Err(_) => continue,
1911            };
1912            let candidate = match RecoverableSignature::from_compact(&compact_64, recid) {
1913                Ok(s) => s,
1914                Err(_) => continue,
1915            };
1916            if let Ok(recovered) = secp.recover_ecdsa(&msg, &candidate) {
1917                if recovered == public_key {
1918                    found = Some(candidate);
1919                    break;
1920                }
1921            }
1922        }
1923        let recoverable = found.ok_or_else(|| {
1924            // Invariant violation by a non-conformant signer: the
1925            // signature returned does not correspond to the public
1926            // key the signer claims. Surface as ExternalSignerError
1927            // (NOT Generic) so callers can distinguish signer-side
1928            // failures from protocol-level invariants.
1929            ProtocolError::ExternalSignerError(
1930                "signer returned a signature whose recovery id does not match the returned public key".to_string(),
1931            )
1932        })?;
1933
1934        // Compressed-pubkey convention matches `dashcore::signer::sign`, which
1935        // always passes `true` regardless of the underlying key encoding. The
1936        // signer's `sign_ecdsa` returns the compressed `secp256k1::PublicKey`,
1937        // so this is consistent.
1938        let compact_65 = recoverable.to_compact_signature(true);
1939
1940        if !self.set_signature(compact_65.to_vec().into()) {
1941            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
1942                needed: self.required_number_of_private_keys(),
1943                using: 1,
1944                msg: "failed to set ECDSA signature",
1945            });
1946        }
1947        Ok(())
1948    }
1949
1950    #[cfg(feature = "state-transition-validation")]
1951    fn verify_by_raw_public_key<T: BlsModule>(
1952        &self,
1953        public_key: &[u8],
1954        public_key_type: KeyType,
1955        bls: &T,
1956    ) -> Result<(), ProtocolError> {
1957        match public_key_type {
1958            KeyType::ECDSA_SECP256K1 => self.verify_ecdsa_signature_by_public_key(public_key),
1959            KeyType::ECDSA_HASH160 => {
1960                self.verify_ecdsa_hash_160_signature_by_public_key_hash(public_key)
1961            }
1962            KeyType::BLS12_381 => self.verify_bls_signature_by_public_key(public_key, bls),
1963            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => {
1964                Err(ProtocolError::InvalidIdentityPublicKeyTypeError(
1965                    InvalidIdentityPublicKeyTypeError::new(public_key_type),
1966                ))
1967            }
1968        }
1969    }
1970
1971    #[cfg(feature = "state-transition-validation")]
1972    pub fn verify_identity_signed_signature(
1973        &self,
1974        public_key: &IdentityPublicKey,
1975        bls: &impl BlsModule,
1976    ) -> Result<(), ProtocolError> {
1977        // self.verify_public_key_level_and_purpose(public_key)?;
1978        if public_key.disabled_at().is_some() {
1979            return Err(ProtocolError::PublicKeyIsDisabledError(
1980                PublicKeyIsDisabledError::new(public_key.id()),
1981            ));
1982        }
1983
1984        let Some(signature) = self.signature() else {
1985            return Err(ProtocolError::CorruptedCodeExecution("verifying identity signature for a state transition that doesn't use identity signatures".to_string()));
1986        };
1987        if signature.is_empty() {
1988            return Err(ProtocolError::StateTransitionIsNotSignedError(
1989                StateTransitionIsNotSignedError::new(self.clone()),
1990            ));
1991        }
1992
1993        if self.signature_public_key_id() != Some(public_key.id()) {
1994            return Err(ProtocolError::PublicKeyMismatchError(
1995                PublicKeyMismatchError::new(public_key.clone()),
1996            ));
1997        }
1998
1999        let public_key_bytes = public_key.data().as_slice();
2000        match public_key.key_type() {
2001            KeyType::ECDSA_HASH160 => {
2002                self.verify_ecdsa_hash_160_signature_by_public_key_hash(public_key_bytes)
2003            }
2004
2005            KeyType::ECDSA_SECP256K1 => self.verify_ecdsa_signature_by_public_key(public_key_bytes),
2006
2007            KeyType::BLS12_381 => self.verify_bls_signature_by_public_key(public_key_bytes, bls),
2008
2009            // per https://github.com/dashevo/platform/pull/353, signing and verification is not supported
2010            KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => Ok(()),
2011        }
2012    }
2013
2014    #[cfg(feature = "state-transition-validation")]
2015    fn verify_ecdsa_hash_160_signature_by_public_key_hash(
2016        &self,
2017        public_key_hash: &[u8],
2018    ) -> Result<(), ProtocolError> {
2019        let Some(signature) = self.signature() else {
2020            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
2021                needed: self.required_number_of_private_keys(),
2022                using: 1,
2023                msg: "This state transition type should a single signature",
2024            });
2025        };
2026        if signature.is_empty() {
2027            return Err(ProtocolError::StateTransitionIsNotSignedError(
2028                StateTransitionIsNotSignedError::new(self.clone()),
2029            ));
2030        }
2031        let data = self.signable_bytes()?;
2032        let data_hash = double_sha(data);
2033        signer::verify_hash_signature(&data_hash, signature.as_slice(), public_key_hash).map_err(
2034            |e| {
2035                ProtocolError::from(ConsensusError::SignatureError(
2036                    SignatureError::InvalidStateTransitionSignatureError(
2037                        InvalidStateTransitionSignatureError::new(e.to_string()),
2038                    ),
2039                ))
2040            },
2041        )
2042    }
2043
2044    #[cfg(feature = "state-transition-validation")]
2045    /// Verifies an ECDSA signature with the public key
2046    fn verify_ecdsa_signature_by_public_key(&self, public_key: &[u8]) -> Result<(), ProtocolError> {
2047        let Some(signature) = self.signature() else {
2048            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
2049                needed: self.required_number_of_private_keys(),
2050                using: 1,
2051                msg: "This state transition type should a single signature",
2052            });
2053        };
2054        if signature.is_empty() {
2055            return Err(ProtocolError::StateTransitionIsNotSignedError(
2056                StateTransitionIsNotSignedError::new(self.clone()),
2057            ));
2058        }
2059        let data = self.signable_bytes()?;
2060        signer::verify_data_signature(&data, signature.as_slice(), public_key).map_err(|e| {
2061            // TODO: it shouldn't respond with consensus error
2062
2063            ProtocolError::from(ConsensusError::SignatureError(
2064                SignatureError::InvalidStateTransitionSignatureError(
2065                    InvalidStateTransitionSignatureError::new(e.to_string()),
2066                ),
2067            ))
2068        })
2069    }
2070
2071    #[cfg(feature = "state-transition-validation")]
2072    /// Verifies a BLS signature with the public key
2073    fn verify_bls_signature_by_public_key<T: BlsModule>(
2074        &self,
2075        public_key: &[u8],
2076        bls: &T,
2077    ) -> Result<(), ProtocolError> {
2078        let Some(signature) = self.signature() else {
2079            return Err(ProtocolError::InvalidVerificationWrongNumberOfElements {
2080                needed: self.required_number_of_private_keys(),
2081                using: 1,
2082                msg: "This state transition type should a single signature",
2083            });
2084        };
2085        if signature.is_empty() {
2086            return Err(ProtocolError::StateTransitionIsNotSignedError(
2087                StateTransitionIsNotSignedError::new(self.clone()),
2088            ));
2089        }
2090
2091        let data = self.signable_bytes()?;
2092
2093        bls.verify_signature(signature.as_slice(), &data, public_key)
2094            .map(|_| ())
2095            .map_err(|e| {
2096                // TODO: it shouldn't respond with consensus error
2097                ProtocolError::from(ConsensusError::SignatureError(
2098                    SignatureError::InvalidStateTransitionSignatureError(
2099                        InvalidStateTransitionSignatureError::new(e.to_string()),
2100                    ),
2101                ))
2102            })
2103    }
2104}
2105
2106#[cfg(feature = "state-transition-validation")]
2107impl StateTransitionStructureValidation for StateTransition {
2108    fn validate_structure(
2109        &self,
2110        platform_version: &PlatformVersion,
2111    ) -> crate::validation::SimpleConsensusValidationResult {
2112        match self {
2113            StateTransition::DataContractCreate(_)
2114            | StateTransition::DataContractUpdate(_)
2115            | StateTransition::Batch(_)
2116            | StateTransition::IdentityCreate(_)
2117            | StateTransition::IdentityTopUp(_)
2118            | StateTransition::IdentityCreditWithdrawal(_)
2119            | StateTransition::IdentityUpdate(_)
2120            | StateTransition::IdentityKeyLimitsUpdate(_)
2121            | StateTransition::IdentityCreditTransfer(_)
2122            | StateTransition::MasternodeVote(_) => {
2123                crate::validation::SimpleConsensusValidationResult::new_with_error(
2124                    UnsupportedFeatureError::new(
2125                        "structure validation for identity-based state transitions".to_string(),
2126                        platform_version.protocol_version,
2127                    )
2128                    .into(),
2129                )
2130            }
2131            StateTransition::IdentityCreditTransferToAddresses(transition) => {
2132                transition.validate_structure(platform_version)
2133            }
2134            StateTransition::IdentityCreateFromAddresses(transition) => {
2135                transition.validate_structure(platform_version)
2136            }
2137            StateTransition::IdentityTopUpFromAddresses(transition) => {
2138                transition.validate_structure(platform_version)
2139            }
2140            StateTransition::AddressFundsTransfer(transition) => {
2141                transition.validate_structure(platform_version)
2142            }
2143            StateTransition::AddressFundingFromAssetLock(transition) => {
2144                transition.validate_structure(platform_version)
2145            }
2146            StateTransition::AddressCreditWithdrawal(transition) => {
2147                transition.validate_structure(platform_version)
2148            }
2149            StateTransition::Shield(transition) => transition.validate_structure(platform_version),
2150            StateTransition::ShieldedTransfer(transition) => {
2151                transition.validate_structure(platform_version)
2152            }
2153            StateTransition::Unshield(transition) => {
2154                transition.validate_structure(platform_version)
2155            }
2156            StateTransition::IdentityTopUpFromShieldedPool(transition) => {
2157                transition.validate_structure(platform_version)
2158            }
2159            StateTransition::ShieldFromAssetLock(transition) => {
2160                transition.validate_structure(platform_version)
2161            }
2162            StateTransition::ShieldedWithdrawal(transition) => {
2163                transition.validate_structure(platform_version)
2164            }
2165            StateTransition::IdentityCreateFromShieldedPool(transition) => {
2166                transition.validate_structure(platform_version)
2167            }
2168            StateTransition::ShieldFromIdentity(transition) => {
2169                transition.validate_structure(platform_version)
2170            }
2171        }
2172    }
2173}
2174
2175#[cfg(test)]
2176mod tests {
2177    use super::*;
2178
2179    // -----------------------------------------------------------------------
2180    // StateTransitionSigningOptions tests
2181    // -----------------------------------------------------------------------
2182
2183    #[test]
2184    fn test_signing_options_default() {
2185        let opts = StateTransitionSigningOptions::default();
2186        assert!(!opts.allow_signing_with_any_security_level);
2187        assert!(!opts.allow_signing_with_any_purpose);
2188    }
2189
2190    #[test]
2191    fn test_signing_options_equality() {
2192        let a = StateTransitionSigningOptions {
2193            allow_signing_with_any_security_level: true,
2194            allow_signing_with_any_purpose: false,
2195        };
2196        let b = StateTransitionSigningOptions {
2197            allow_signing_with_any_security_level: true,
2198            allow_signing_with_any_purpose: false,
2199        };
2200        assert_eq!(a, b);
2201    }
2202
2203    #[test]
2204    fn test_signing_options_inequality() {
2205        let a = StateTransitionSigningOptions {
2206            allow_signing_with_any_security_level: true,
2207            allow_signing_with_any_purpose: false,
2208        };
2209        let b = StateTransitionSigningOptions {
2210            allow_signing_with_any_security_level: false,
2211            allow_signing_with_any_purpose: false,
2212        };
2213        assert_ne!(a, b);
2214    }
2215
2216    #[test]
2217    #[allow(clippy::clone_on_copy)]
2218    fn test_signing_options_clone() {
2219        let original = StateTransitionSigningOptions {
2220            allow_signing_with_any_security_level: true,
2221            allow_signing_with_any_purpose: true,
2222        };
2223        let cloned = original.clone();
2224        assert_eq!(original, cloned);
2225    }
2226
2227    #[test]
2228    fn test_signing_options_copy() {
2229        let original = StateTransitionSigningOptions {
2230            allow_signing_with_any_security_level: true,
2231            allow_signing_with_any_purpose: false,
2232        };
2233        let copied = original;
2234        assert_eq!(original, copied);
2235    }
2236
2237    #[test]
2238    fn test_signing_options_debug() {
2239        let opts = StateTransitionSigningOptions::default();
2240        let debug_str = format!("{:?}", opts);
2241        assert!(debug_str.contains("StateTransitionSigningOptions"));
2242        assert!(debug_str.contains("allow_signing_with_any_security_level"));
2243        assert!(debug_str.contains("allow_signing_with_any_purpose"));
2244    }
2245
2246    // -----------------------------------------------------------------------
2247    // StateTransition enum accessor / mutator / classification tests
2248    //
2249    // These exercise the non-trivial match arms across the large enum, using
2250    // the IdentityCreditTransfer, MasternodeVote, IdentityCreditWithdrawal and
2251    // DataContractCreate variants as representative signed / unsigned /
2252    // voting / contract cases. They intentionally do NOT use `sign`/`verify`
2253    // (those go through BLS/ECDSA and have their own coverage elsewhere).
2254    // -----------------------------------------------------------------------
2255    use crate::identity::core_script::CoreScript;
2256    use crate::identity::{Purpose, SecurityLevel};
2257    use crate::prelude::Identifier;
2258    use crate::state_transition::identity_credit_transfer_transition::v0::IdentityCreditTransferTransitionV0;
2259    use crate::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition;
2260    use crate::state_transition::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0;
2261    use crate::state_transition::identity_credit_withdrawal_transition::IdentityCreditWithdrawalTransition;
2262    use crate::state_transition::masternode_vote_transition::v0::MasternodeVoteTransitionV0;
2263    use crate::state_transition::masternode_vote_transition::MasternodeVoteTransition;
2264    use crate::withdrawal::Pooling;
2265
2266    fn sample_transfer_st() -> StateTransition {
2267        let v0 = IdentityCreditTransferTransitionV0 {
2268            identity_id: Identifier::from([1u8; 32]),
2269            recipient_id: Identifier::from([2u8; 32]),
2270            amount: 1_000,
2271            nonce: 7,
2272            user_fee_increase: 3,
2273            signature_public_key_id: 11,
2274            signature: BinaryData::new(vec![0u8; 65]),
2275        };
2276        StateTransition::IdentityCreditTransfer(IdentityCreditTransferTransition::V0(v0))
2277    }
2278
2279    fn sample_masternode_vote_st() -> StateTransition {
2280        let v0 = MasternodeVoteTransitionV0 {
2281            pro_tx_hash: Identifier::from([3u8; 32]),
2282            voter_identity_id: Identifier::from([4u8; 32]),
2283            vote: Default::default(),
2284            nonce: 2,
2285            signature_public_key_id: 5,
2286            signature: BinaryData::new(vec![9u8; 10]),
2287        };
2288        StateTransition::MasternodeVote(MasternodeVoteTransition::V0(v0))
2289    }
2290
2291    fn sample_withdrawal_st() -> StateTransition {
2292        let v0 = IdentityCreditWithdrawalTransitionV0 {
2293            identity_id: Identifier::from([5u8; 32]),
2294            amount: 42,
2295            core_fee_per_byte: 1,
2296            pooling: Pooling::Never,
2297            output_script: CoreScript::from_bytes(vec![0x76, 0xa9]),
2298            nonce: 4,
2299            user_fee_increase: 1,
2300            signature_public_key_id: 3,
2301            signature: BinaryData::new(vec![8u8; 65]),
2302        };
2303        StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V0(v0))
2304    }
2305
2306    #[test]
2307    fn test_name_returns_variant_names() {
2308        assert_eq!(sample_transfer_st().name(), "IdentityCreditTransfer");
2309        assert_eq!(sample_masternode_vote_st().name(), "MasternodeVote");
2310        assert_eq!(sample_withdrawal_st().name(), "IdentityCreditWithdrawal");
2311    }
2312
2313    #[test]
2314    fn test_state_transition_type_matches_variant() {
2315        assert_eq!(
2316            sample_transfer_st().state_transition_type(),
2317            StateTransitionType::IdentityCreditTransfer
2318        );
2319        assert_eq!(
2320            sample_masternode_vote_st().state_transition_type(),
2321            StateTransitionType::MasternodeVote
2322        );
2323        assert_eq!(
2324            sample_withdrawal_st().state_transition_type(),
2325            StateTransitionType::IdentityCreditWithdrawal
2326        );
2327    }
2328
2329    #[test]
2330    fn test_is_identity_signed_excludes_asset_lock_and_shielded() {
2331        assert!(sample_transfer_st().is_identity_signed());
2332        assert!(sample_masternode_vote_st().is_identity_signed());
2333        assert!(sample_withdrawal_st().is_identity_signed());
2334    }
2335
2336    #[test]
2337    fn test_signature_accessor() {
2338        let st = sample_transfer_st();
2339        let sig = st.signature().expect("transfer should expose signature");
2340        assert_eq!(sig.len(), 65);
2341
2342        let st = sample_masternode_vote_st();
2343        let sig = st.signature().expect("masternode vote has signature");
2344        assert_eq!(sig.as_slice(), &[9u8; 10]);
2345    }
2346
2347    #[test]
2348    fn test_owner_id_accessor() {
2349        let transfer = sample_transfer_st();
2350        assert_eq!(transfer.owner_id(), Some(Identifier::from([1u8; 32])));
2351
2352        let vote = sample_masternode_vote_st();
2353        assert_eq!(vote.owner_id(), Some(Identifier::from([4u8; 32])));
2354
2355        let withdraw = sample_withdrawal_st();
2356        assert_eq!(withdraw.owner_id(), Some(Identifier::from([5u8; 32])));
2357    }
2358
2359    #[test]
2360    fn test_signature_public_key_id_accessor() {
2361        assert_eq!(sample_transfer_st().signature_public_key_id(), Some(11));
2362        assert_eq!(
2363            sample_masternode_vote_st().signature_public_key_id(),
2364            Some(5)
2365        );
2366        assert_eq!(sample_withdrawal_st().signature_public_key_id(), Some(3));
2367    }
2368
2369    #[test]
2370    fn test_user_fee_increase_for_various_variants() {
2371        // Transfer exposes its internal value.
2372        assert_eq!(sample_transfer_st().user_fee_increase(), 3);
2373        // Masternode vote returns 0 unconditionally.
2374        assert_eq!(sample_masternode_vote_st().user_fee_increase(), 0);
2375        // Withdrawal exposes its internal value.
2376        assert_eq!(sample_withdrawal_st().user_fee_increase(), 1);
2377    }
2378
2379    #[test]
2380    fn test_set_signature_returns_true_for_supported() {
2381        let mut st = sample_transfer_st();
2382        let ok = st.set_signature(BinaryData::new(vec![0xaa; 65]));
2383        assert!(ok);
2384        assert_eq!(st.signature().unwrap().as_slice(), &[0xaa; 65]);
2385    }
2386
2387    #[test]
2388    fn test_set_user_fee_increase_updates_value() {
2389        let mut st = sample_transfer_st();
2390        st.set_user_fee_increase(42);
2391        assert_eq!(st.user_fee_increase(), 42);
2392
2393        // Masternode vote ignores the setter (documented no-op) — still reads 0.
2394        let mut vote = sample_masternode_vote_st();
2395        vote.set_user_fee_increase(99);
2396        assert_eq!(vote.user_fee_increase(), 0);
2397    }
2398
2399    #[test]
2400    fn test_set_signature_public_key_id() {
2401        let mut st = sample_transfer_st();
2402        st.set_signature_public_key_id(1234);
2403        assert_eq!(st.signature_public_key_id(), Some(1234));
2404    }
2405
2406    #[test]
2407    fn test_required_number_of_private_keys_default() {
2408        // Non asset-lock transitions always require 1 key.
2409        assert_eq!(sample_transfer_st().required_number_of_private_keys(), 1);
2410        assert_eq!(
2411            sample_masternode_vote_st().required_number_of_private_keys(),
2412            1
2413        );
2414        assert_eq!(sample_withdrawal_st().required_number_of_private_keys(), 1);
2415    }
2416
2417    #[test]
2418    fn test_inputs_none_for_legacy_variants() {
2419        // All these variants have no PlatformAddress inputs.
2420        assert!(sample_transfer_st().inputs().is_none());
2421        assert!(sample_masternode_vote_st().inputs().is_none());
2422        assert!(sample_withdrawal_st().inputs().is_none());
2423    }
2424
2425    #[test]
2426    fn test_active_version_range_legacy_transitions() {
2427        // These all report ALL_VERSIONS per the mod.rs table.
2428        assert_eq!(sample_transfer_st().active_version_range(), ALL_VERSIONS);
2429        assert_eq!(
2430            sample_masternode_vote_st().active_version_range(),
2431            ALL_VERSIONS
2432        );
2433        assert_eq!(sample_withdrawal_st().active_version_range(), ALL_VERSIONS);
2434    }
2435
2436    #[test]
2437    fn test_unique_identifiers_non_empty() {
2438        let ids = sample_transfer_st().unique_identifiers();
2439        assert_eq!(ids.len(), 1);
2440        assert!(!ids[0].is_empty());
2441    }
2442
2443    #[test]
2444    fn test_required_asset_lock_balance_rejects_non_asset_lock() {
2445        let platform_version = PlatformVersion::latest();
2446        let st = sample_transfer_st();
2447        let err = st
2448            .required_asset_lock_balance_for_processing_start(platform_version)
2449            .expect_err("credit transfer is not an asset lock state transition");
2450        match err {
2451            ProtocolError::CorruptedCodeExecution(msg) => {
2452                assert!(
2453                    msg.contains("is not an asset lock transaction"),
2454                    "unexpected error message: {msg}"
2455                );
2456            }
2457            other => panic!("expected CorruptedCodeExecution, got {other:?}"),
2458        }
2459    }
2460
2461    #[test]
2462    fn test_security_level_requirement_for_transfer() {
2463        // IdentityCreditTransfer requires CRITICAL at TRANSFER purpose.
2464        let st = sample_transfer_st();
2465        let levels = st
2466            .security_level_requirement(Purpose::TRANSFER)
2467            .expect("transfer state transition should return a requirement");
2468        assert_eq!(levels, vec![SecurityLevel::CRITICAL]);
2469    }
2470
2471    #[test]
2472    fn test_purpose_requirement_for_transfer() {
2473        let st = sample_transfer_st();
2474        let purposes = st
2475            .purpose_requirement()
2476            .expect("transfer state transition should have a purpose");
2477        assert_eq!(purposes, vec![Purpose::TRANSFER]);
2478    }
2479
2480    #[test]
2481    fn test_optional_asset_lock_proof_none_for_transfer() {
2482        let st = sample_transfer_st();
2483        assert!(st.optional_asset_lock_proof().is_none());
2484    }
2485
2486    // -----------------------------------------------------------------------
2487    // Enum construction: From<V0 / outer enum> → StateTransition
2488    // -----------------------------------------------------------------------
2489
2490    #[test]
2491    fn test_from_outer_enum_into_state_transition() {
2492        let outer: IdentityCreditTransferTransition =
2493            IdentityCreditTransferTransition::V0(IdentityCreditTransferTransitionV0::default());
2494        let st: StateTransition = outer.into();
2495        assert!(matches!(st, StateTransition::IdentityCreditTransfer(_)));
2496    }
2497
2498    #[test]
2499    fn test_from_masternode_vote_outer_into_state_transition() {
2500        let outer: MasternodeVoteTransition =
2501            MasternodeVoteTransition::V0(MasternodeVoteTransitionV0::default());
2502        let st: StateTransition = outer.into();
2503        assert!(matches!(st, StateTransition::MasternodeVote(_)));
2504    }
2505
2506    // -----------------------------------------------------------------------
2507    // Serialization round-trip: platform serialize / deserialize via enum.
2508    // Exercises the top-level `StateTransition` (de)serialize glue.
2509    // -----------------------------------------------------------------------
2510
2511    #[test]
2512    fn test_state_transition_platform_serialize_roundtrip() {
2513        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
2514        let original = sample_transfer_st();
2515        let bytes =
2516            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
2517        let restored = StateTransition::deserialize_from_bytes_untrusted(&bytes)
2518            .expect("deserialize should succeed");
2519        assert_eq!(original, restored);
2520    }
2521
2522    #[test]
2523    fn test_deserialize_from_bytes_in_version_succeeds_for_latest() {
2524        use crate::serialization::PlatformSerializable;
2525        let original = sample_transfer_st();
2526        let bytes =
2527            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
2528        let restored = StateTransition::deserialize_from_bytes_untrusted_in_version(
2529            &bytes,
2530            PlatformVersion::latest(),
2531        )
2532        .expect("deserialize_from_bytes_in_version should succeed");
2533        assert_eq!(original, restored);
2534    }
2535
2536    #[test]
2537    fn test_transaction_id_is_deterministic() {
2538        let st = sample_transfer_st();
2539        let a = st.transaction_id().expect("hash should succeed");
2540        let b = st.transaction_id().expect("hash should succeed");
2541        assert_eq!(a, b);
2542        assert_eq!(a.len(), 32);
2543    }
2544
2545    #[test]
2546    fn test_transaction_id_changes_on_signature_change() {
2547        let mut st = sample_transfer_st();
2548        let before = st.transaction_id().expect("hash should succeed");
2549        st.set_signature(BinaryData::new(vec![0xbb; 65]));
2550        let after = st.transaction_id().expect("hash should succeed");
2551        // Different signatures produce a different serialized form.
2552        assert_ne!(before, after);
2553    }
2554
2555    #[test]
2556    fn test_clone_preserves_inner_state() {
2557        let st = sample_transfer_st();
2558        let cloned = st.clone();
2559        assert_eq!(st, cloned);
2560    }
2561
2562    // -----------------------------------------------------------------------
2563    // Additional coverage: enum arms that weren't previously exercised.
2564    //
2565    // The tests below intentionally target variants the earlier tests did not
2566    // touch (DataContractCreate, DataContractUpdate, Batch, IdentityCreate,
2567    // IdentityTopUp, IdentityUpdate, shielded / address variants) to cover
2568    // the remaining match-arm branches in accessor / mutator / classification
2569    // methods.
2570    // -----------------------------------------------------------------------
2571
2572    use crate::data_contract::serialized_version::DataContractInSerializationFormat;
2573    use crate::state_transition::batch_transition::document_base_transition::v0::DocumentBaseTransitionV0;
2574    use crate::state_transition::batch_transition::document_base_transition::DocumentBaseTransition;
2575    use crate::state_transition::batch_transition::document_delete_transition::{
2576        DocumentDeleteTransition, DocumentDeleteTransitionV0,
2577    };
2578    use crate::state_transition::batch_transition::{BatchTransition, BatchTransitionV0};
2579    use crate::state_transition::data_contract_create_transition::{
2580        DataContractCreateTransition, DataContractCreateTransitionV0,
2581        DataContractCreateTransitionV1,
2582    };
2583    use crate::state_transition::data_contract_update_transition::{
2584        DataContractUpdateTransition, DataContractUpdateTransitionV0,
2585    };
2586    use crate::state_transition::identity_create_transition::v0::IdentityCreateTransitionV0;
2587    use crate::state_transition::identity_create_transition::IdentityCreateTransition;
2588    use crate::state_transition::identity_topup_transition::v0::IdentityTopUpTransitionV0;
2589    use crate::state_transition::identity_topup_transition::IdentityTopUpTransition;
2590    use crate::state_transition::identity_update_transition::v0::IdentityUpdateTransitionV0;
2591    use crate::state_transition::identity_update_transition::IdentityUpdateTransition;
2592    use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0;
2593    use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition;
2594    use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0;
2595    use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition;
2596    use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0;
2597    use crate::state_transition::unshield_transition::UnshieldTransition;
2598
2599    /// Build a DataContractInSerializationFormat from a crate-private v0
2600    /// constructor via the public TryFromPlatformVersioned impl and DataContract V1.
2601    fn sample_data_contract_in_serialization_format() -> DataContractInSerializationFormat {
2602        use crate::data_contract::config::v0::DataContractConfigV0;
2603        use crate::data_contract::config::DataContractConfig;
2604        use crate::data_contract::v1::DataContractV1;
2605        use crate::data_contract::DataContract;
2606        use platform_version::TryIntoPlatformVersioned;
2607        use std::collections::BTreeMap;
2608
2609        let contract = DataContract::V1(DataContractV1 {
2610            id: Identifier::from([9u8; 32]),
2611            version: 1,
2612            owner_id: Identifier::from([7u8; 32]),
2613            document_types: BTreeMap::new(),
2614            config: DataContractConfig::V0(DataContractConfigV0 {
2615                can_be_deleted: false,
2616                readonly: false,
2617                keeps_history: false,
2618                documents_keep_history_contract_default: false,
2619                documents_mutable_contract_default: false,
2620                documents_can_be_deleted_contract_default: false,
2621                requires_identity_encryption_bounded_key: None,
2622                requires_identity_decryption_bounded_key: None,
2623            }),
2624            schema_defs: None,
2625            created_at: None,
2626            updated_at: None,
2627            created_at_block_height: None,
2628            updated_at_block_height: None,
2629            created_at_epoch: None,
2630            updated_at_epoch: None,
2631            groups: BTreeMap::new(),
2632            tokens: BTreeMap::new(),
2633            keywords: Vec::new(),
2634            description: None,
2635        });
2636
2637        contract
2638            .try_into_platform_versioned(PlatformVersion::latest())
2639            .expect("expected to serialize a trivial contract")
2640    }
2641
2642    fn sample_data_contract_create_st() -> StateTransition {
2643        StateTransition::DataContractCreate(DataContractCreateTransition::V0(
2644            DataContractCreateTransitionV0 {
2645                data_contract: sample_data_contract_in_serialization_format(),
2646                identity_nonce: 1,
2647                user_fee_increase: 5,
2648                signature_public_key_id: 2,
2649                signature: BinaryData::new(vec![0xAB; 65]),
2650            },
2651        ))
2652    }
2653
2654    fn sample_data_contract_create_v1_st() -> StateTransition {
2655        StateTransition::DataContractCreate(DataContractCreateTransition::V1(
2656            DataContractCreateTransitionV1 {
2657                data_contract: sample_data_contract_in_serialization_format(),
2658                identity_nonce: 1,
2659                contract_group: None,
2660                contract_group_memberships: vec![],
2661                user_fee_increase: 5,
2662                signature_public_key_id: 2,
2663                signature: BinaryData::new(vec![0xAB; 65]),
2664            },
2665        ))
2666    }
2667
2668    fn sample_data_contract_update_st() -> StateTransition {
2669        StateTransition::DataContractUpdate(DataContractUpdateTransition::V0(
2670            DataContractUpdateTransitionV0 {
2671                identity_contract_nonce: 4,
2672                data_contract: sample_data_contract_in_serialization_format(),
2673                user_fee_increase: 9,
2674                signature_public_key_id: 6,
2675                signature: BinaryData::new(vec![0xCD; 65]),
2676            },
2677        ))
2678    }
2679
2680    #[cfg(all(feature = "state-transition-signing", feature = "bls-signatures"))]
2681    #[test]
2682    fn should_enforce_contract_bounds_before_private_key_signing() {
2683        use crate::consensus::signature::{
2684            ContractBoundedKeyNonBatchError, ContractBoundedKeyOutOfBoundsError,
2685        };
2686        use crate::identity::contract_bounds::ContractBounds;
2687        use crate::identity::identity_public_key::v0::IdentityPublicKeyV0;
2688
2689        let private_key = [1; 32];
2690        let bls = crate::bls::native_bls::NativeBlsModule;
2691        // `sample_batch_st_with_delete` deletes a "preorder" document of contract [2; 32].
2692        let mut key = IdentityPublicKeyV0 {
2693            id: 7,
2694            purpose: Purpose::AUTHENTICATION,
2695            security_level: SecurityLevel::HIGH,
2696            key_type: KeyType::ECDSA_SECP256K1,
2697            data: get_compressed_public_ec_key(&private_key)
2698                .unwrap()
2699                .to_vec()
2700                .into(),
2701            contract_bounds: Some(ContractBounds::SingleContract {
2702                id: Identifier::from([2; 32]),
2703            }),
2704            ..Default::default()
2705        };
2706        sample_batch_st_with_delete()
2707            .sign(&key.clone().into(), &private_key, &bls)
2708            .expect("a batch inside the bounds must sign");
2709        key.contract_bounds = Some(ContractBounds::SingleContractDocumentType {
2710            id: Identifier::from([2; 32]),
2711            document_type_name: "preorder".to_string(),
2712        });
2713        sample_batch_st_with_delete()
2714            .sign(&key.clone().into(), &private_key, &bls)
2715            .expect("a batch of the bound document type must sign");
2716
2717        let err = sample_transfer_st()
2718            .sign(&key.clone().into(), &private_key, &bls)
2719            .unwrap_err();
2720        assert!(matches!(err, ProtocolError::ConsensusError(error)
2721            if *error == ContractBoundedKeyNonBatchError::new(key.id).into()));
2722
2723        for denied in [
2724            ContractBounds::SingleContract {
2725                id: Identifier::from([3; 32]),
2726            },
2727            ContractBounds::SingleContractDocumentType {
2728                id: Identifier::from([2; 32]),
2729                document_type_name: "other".to_string(),
2730            },
2731        ] {
2732            key.contract_bounds = Some(denied.clone());
2733            let mut transition = sample_batch_st_with_delete();
2734            let original = transition.clone();
2735            let err = transition
2736                .sign(&key.clone().into(), &private_key, &bls)
2737                .unwrap_err();
2738            assert!(
2739                matches!(err, ProtocolError::ConsensusError(error)
2740                if *error == ContractBoundedKeyOutOfBoundsError::new(key.id).into()),
2741                "{denied:?}"
2742            );
2743            assert_eq!(
2744                transition, original,
2745                "rejection must preserve the transition"
2746            );
2747        }
2748
2749        key.contract_bounds = None;
2750        sample_batch_st_with_delete()
2751            .sign(&key.into(), &private_key, &bls)
2752            .expect("unbounded keys must still sign");
2753    }
2754
2755    fn sample_batch_st_with_delete() -> StateTransition {
2756        let base = DocumentBaseTransition::V0(DocumentBaseTransitionV0 {
2757            id: Identifier::from([1u8; 32]),
2758            identity_contract_nonce: 3,
2759            document_type_name: "preorder".to_string(),
2760            data_contract_id: Identifier::from([2u8; 32]),
2761        });
2762        let delete =
2763            DocumentTransition::Delete(DocumentDeleteTransition::V0(DocumentDeleteTransitionV0 {
2764                base,
2765            }));
2766        StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 {
2767            owner_id: Identifier::from([8u8; 32]),
2768            transitions: vec![delete],
2769            user_fee_increase: 2,
2770            signature_public_key_id: 7,
2771            signature: BinaryData::new(vec![0xEE; 65]),
2772        }))
2773    }
2774
2775    fn sample_batch_st_empty() -> StateTransition {
2776        StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 {
2777            owner_id: Identifier::from([1u8; 32]),
2778            transitions: vec![],
2779            user_fee_increase: 0,
2780            signature_public_key_id: 0,
2781            signature: BinaryData::new(vec![]),
2782        }))
2783    }
2784
2785    fn sample_identity_create_st() -> StateTransition {
2786        StateTransition::IdentityCreate(IdentityCreateTransition::V0(IdentityCreateTransitionV0 {
2787            identity_id: Identifier::from([3u8; 32]),
2788            ..Default::default()
2789        }))
2790    }
2791
2792    fn sample_identity_top_up_st() -> StateTransition {
2793        StateTransition::IdentityTopUp(IdentityTopUpTransition::V0(IdentityTopUpTransitionV0 {
2794            identity_id: Identifier::from([4u8; 32]),
2795            ..Default::default()
2796        }))
2797    }
2798
2799    fn sample_identity_update_st() -> StateTransition {
2800        StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(IdentityUpdateTransitionV0 {
2801            identity_id: Identifier::from([5u8; 32]),
2802            revision: 1,
2803            nonce: 2,
2804            add_public_keys: vec![],
2805            disable_public_keys: vec![],
2806            user_fee_increase: 11,
2807            signature_public_key_id: 33,
2808            signature: BinaryData::new(vec![0xFF; 65]),
2809        }))
2810    }
2811
2812    fn sample_unshield_st() -> StateTransition {
2813        StateTransition::Unshield(UnshieldTransition::V0(UnshieldTransitionV0 {
2814            output_address: Default::default(),
2815            actions: vec![],
2816            unshielding_amount: 0,
2817            anchor: [0u8; 32],
2818            proof: vec![],
2819            binding_signature: [0u8; 64],
2820        }))
2821    }
2822
2823    fn sample_shielded_transfer_st() -> StateTransition {
2824        StateTransition::ShieldedTransfer(ShieldedTransferTransition::V0(
2825            ShieldedTransferTransitionV0 {
2826                actions: vec![],
2827                value_balance: 0,
2828                anchor: [0u8; 32],
2829                proof: vec![],
2830                binding_signature: [0u8; 64],
2831            },
2832        ))
2833    }
2834
2835    fn sample_shielded_withdrawal_st() -> StateTransition {
2836        use crate::identity::core_script::CoreScript;
2837        use crate::withdrawal::Pooling;
2838        StateTransition::ShieldedWithdrawal(ShieldedWithdrawalTransition::V0(
2839            ShieldedWithdrawalTransitionV0 {
2840                actions: vec![],
2841                unshielding_amount: 0,
2842                anchor: [0u8; 32],
2843                proof: vec![],
2844                binding_signature: [0u8; 64],
2845                core_fee_per_byte: 1,
2846                pooling: Pooling::Never,
2847                output_script: CoreScript::from_bytes(vec![]),
2848            },
2849        ))
2850    }
2851
2852    // --- name() covers all previously-untested arms, including the nested
2853    // match for Batch variants. ---
2854    #[test]
2855    fn test_name_for_newly_covered_variants() {
2856        assert_eq!(
2857            sample_data_contract_create_st().name(),
2858            "DataContractCreate"
2859        );
2860        assert_eq!(
2861            sample_data_contract_update_st().name(),
2862            "DataContractUpdate"
2863        );
2864        assert_eq!(sample_identity_create_st().name(), "IdentityCreate");
2865        assert_eq!(sample_identity_top_up_st().name(), "IdentityTopUp");
2866        assert_eq!(sample_identity_update_st().name(), "IdentityUpdate");
2867        assert_eq!(sample_unshield_st().name(), "Unshield");
2868        assert_eq!(sample_shielded_transfer_st().name(), "ShieldedTransfer");
2869        assert_eq!(sample_shielded_withdrawal_st().name(), "ShieldedWithdrawal");
2870
2871        // Batch with a single Delete – exercises the nested DocumentTransition
2872        // match arm in `name()`.
2873        let batch_name = sample_batch_st_with_delete().name();
2874        assert_eq!(batch_name, "DocumentsBatch([Delete])");
2875
2876        // Empty batch – still renders, with an empty list.
2877        let empty_name = sample_batch_st_empty().name();
2878        assert_eq!(empty_name, "DocumentsBatch([])");
2879    }
2880
2881    // --- state_transition_type covers the call_method! dispatch. ---
2882    #[test]
2883    fn test_state_transition_type_for_newly_covered_variants() {
2884        assert_eq!(
2885            sample_data_contract_create_st().state_transition_type(),
2886            StateTransitionType::DataContractCreate
2887        );
2888        assert_eq!(
2889            sample_data_contract_update_st().state_transition_type(),
2890            StateTransitionType::DataContractUpdate
2891        );
2892        assert_eq!(
2893            sample_batch_st_with_delete().state_transition_type(),
2894            StateTransitionType::Batch
2895        );
2896        assert_eq!(
2897            sample_identity_create_st().state_transition_type(),
2898            StateTransitionType::IdentityCreate
2899        );
2900        assert_eq!(
2901            sample_identity_top_up_st().state_transition_type(),
2902            StateTransitionType::IdentityTopUp
2903        );
2904        assert_eq!(
2905            sample_identity_update_st().state_transition_type(),
2906            StateTransitionType::IdentityUpdate
2907        );
2908        assert_eq!(
2909            sample_unshield_st().state_transition_type(),
2910            StateTransitionType::Unshield
2911        );
2912        assert_eq!(
2913            sample_shielded_transfer_st().state_transition_type(),
2914            StateTransitionType::ShieldedTransfer
2915        );
2916        assert_eq!(
2917            sample_shielded_withdrawal_st().state_transition_type(),
2918            StateTransitionType::ShieldedWithdrawal
2919        );
2920    }
2921
2922    // --- active_version_range uses different branches per transition
2923    // "group". Exercises the contract-format V1 branch for
2924    // DataContractCreate/Update (9..=LATEST), the BatchTransitionV0 branch
2925    // (ALL_VERSIONS), and the shielded range (12..=LATEST).
2926    #[test]
2927    fn test_active_version_range_contract_and_shielded_branches() {
2928        // DataContractCreate/Update on PlatformVersion::latest use the V1
2929        // contract serialization format, which restricts active range.
2930        let contract_v1_range = 9..=LATEST_VERSION;
2931        assert_eq!(
2932            sample_data_contract_create_st().active_version_range(),
2933            contract_v1_range
2934        );
2935        let contract_v1_range = 9..=LATEST_VERSION;
2936        assert_eq!(
2937            sample_data_contract_update_st().active_version_range(),
2938            contract_v1_range
2939        );
2940        // BatchTransition::V0 → ALL_VERSIONS
2941        assert_eq!(
2942            sample_batch_st_with_delete().active_version_range(),
2943            ALL_VERSIONS
2944        );
2945        // IdentityCreate/TopUp/Update are ALL_VERSIONS.
2946        assert_eq!(
2947            sample_identity_create_st().active_version_range(),
2948            ALL_VERSIONS
2949        );
2950        assert_eq!(
2951            sample_identity_top_up_st().active_version_range(),
2952            ALL_VERSIONS
2953        );
2954        assert_eq!(
2955            sample_identity_update_st().active_version_range(),
2956            ALL_VERSIONS
2957        );
2958        // Shielded variants report a shielded range (12..=LATEST_VERSION).
2959        let shielded_range = 12..=LATEST_VERSION;
2960        assert_eq!(
2961            sample_shielded_transfer_st().active_version_range(),
2962            shielded_range.clone()
2963        );
2964        assert_eq!(
2965            sample_unshield_st().active_version_range(),
2966            shielded_range.clone()
2967        );
2968        assert_eq!(
2969            sample_shielded_withdrawal_st().active_version_range(),
2970            shielded_range
2971        );
2972    }
2973
2974    fn sample_identity_update_st_adding(
2975        public_key: crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation,
2976    ) -> StateTransition {
2977        StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(IdentityUpdateTransitionV0 {
2978            identity_id: Identifier::from([5u8; 32]),
2979            revision: 1,
2980            nonce: 2,
2981            add_public_keys: vec![public_key],
2982            disable_public_keys: vec![],
2983            user_fee_increase: 0,
2984            signature_public_key_id: 0,
2985            signature: BinaryData::new(vec![0xFF; 65]),
2986        }))
2987    }
2988
2989    #[test]
2990    fn should_only_admit_a_version_1_public_key_in_creation_from_protocol_version_14() {
2991        use crate::serialization::PlatformSerializable;
2992        use crate::state_transition::public_key_in_creation::v0::IdentityPublicKeyInCreationV0;
2993        use crate::state_transition::public_key_in_creation::v1::IdentityPublicKeyInCreationV1;
2994
2995        let version_0_key = IdentityPublicKeyInCreationV0 {
2996            id: 1,
2997            data: BinaryData::new(vec![2; 33]),
2998            ..Default::default()
2999        };
3000        let limited_key = IdentityPublicKeyInCreationV1::from_v0_with_limits(
3001            version_0_key.clone(),
3002            Some(1_000),
3003            None,
3004        );
3005
3006        let plain = sample_identity_update_st_adding(version_0_key.into());
3007        assert_eq!(plain.active_version_range(), ALL_VERSIONS);
3008
3009        let limited = sample_identity_update_st_adding(limited_key.into());
3010        assert_eq!(limited.active_version_range(), 14..=LATEST_VERSION);
3011
3012        // Protocol version 13 refuses it while decoding, the way a binary that does not know
3013        // the version 1 key does; protocol version 14 decodes it.
3014        let bytes = limited.serialize_to_bytes().expect("expected to serialize");
3015        let version_13 = PlatformVersion::get(13).expect("expected protocol version 13");
3016        assert!(matches!(
3017            StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_13),
3018            Err(ProtocolError::StateTransitionError(
3019                StateTransitionIsNotActiveError { .. }
3020            ))
3021        ));
3022        let version_14 = PlatformVersion::get(14).expect("expected protocol version 14");
3023        assert_eq!(
3024            StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_14)
3025                .expect("expected protocol version 14 to decode a limited key"),
3026            limited
3027        );
3028    }
3029
3030    // --- is_identity_signed exercises the inverted-match logic for the
3031    // shielded / identity-create / topup variants. ---
3032    #[test]
3033    fn test_is_identity_signed_false_for_identity_create_topup_and_shielded() {
3034        assert!(!sample_identity_create_st().is_identity_signed());
3035        assert!(!sample_identity_top_up_st().is_identity_signed());
3036        assert!(!sample_unshield_st().is_identity_signed());
3037        assert!(!sample_shielded_transfer_st().is_identity_signed());
3038        assert!(!sample_shielded_withdrawal_st().is_identity_signed());
3039    }
3040
3041    // --- signature accessor for each arm that returns Some/None; previously
3042    // only IdentityCreditTransfer / MasternodeVote / IdentityCreditWithdrawal
3043    // were covered.
3044    #[test]
3045    fn test_signature_accessor_for_other_variants() {
3046        // Some(_) arms
3047        assert_eq!(
3048            sample_data_contract_create_st().signature().unwrap().len(),
3049            65
3050        );
3051        assert_eq!(
3052            sample_data_contract_update_st().signature().unwrap().len(),
3053            65
3054        );
3055        assert_eq!(sample_batch_st_with_delete().signature().unwrap().len(), 65);
3056        assert_eq!(sample_identity_update_st().signature().unwrap().len(), 65);
3057
3058        // None arms for address / shielded variants.
3059        assert!(sample_unshield_st().signature().is_none());
3060        assert!(sample_shielded_transfer_st().signature().is_none());
3061        assert!(sample_shielded_withdrawal_st().signature().is_none());
3062    }
3063
3064    // --- owner_id accessor for each arm.
3065    #[test]
3066    fn test_owner_id_accessor_for_other_variants() {
3067        assert_eq!(
3068            sample_data_contract_create_st().owner_id(),
3069            Some(Identifier::from([7u8; 32]))
3070        );
3071        assert_eq!(
3072            sample_data_contract_update_st().owner_id(),
3073            Some(Identifier::from([7u8; 32]))
3074        );
3075        assert_eq!(
3076            sample_batch_st_with_delete().owner_id(),
3077            Some(Identifier::from([8u8; 32]))
3078        );
3079        assert_eq!(
3080            sample_identity_update_st().owner_id(),
3081            Some(Identifier::from([5u8; 32]))
3082        );
3083        // These variants unconditionally return None.
3084        assert!(sample_unshield_st().owner_id().is_none());
3085        assert!(sample_shielded_transfer_st().owner_id().is_none());
3086        assert!(sample_shielded_withdrawal_st().owner_id().is_none());
3087    }
3088
3089    // --- user_fee_increase accessor — includes arms that return 0
3090    // unconditionally (shielded/masternode) vs the variants' stored value.
3091    #[test]
3092    fn test_user_fee_increase_for_newly_covered_variants() {
3093        assert_eq!(sample_data_contract_create_st().user_fee_increase(), 5);
3094        assert_eq!(sample_data_contract_update_st().user_fee_increase(), 9);
3095        assert_eq!(sample_batch_st_with_delete().user_fee_increase(), 2);
3096        assert_eq!(sample_identity_update_st().user_fee_increase(), 11);
3097        // Unconditionally 0 for shielded.
3098        assert_eq!(sample_shielded_transfer_st().user_fee_increase(), 0);
3099        assert_eq!(sample_shielded_withdrawal_st().user_fee_increase(), 0);
3100        assert_eq!(sample_unshield_st().user_fee_increase(), 0);
3101    }
3102
3103    // --- set_user_fee_increase for the no-op shielded arms and for the
3104    // transitions that actually do store the value.
3105    #[test]
3106    fn test_set_user_fee_increase_for_newly_covered_variants() {
3107        let mut st = sample_data_contract_create_st();
3108        st.set_user_fee_increase(42);
3109        assert_eq!(st.user_fee_increase(), 42);
3110
3111        let mut st = sample_data_contract_update_st();
3112        st.set_user_fee_increase(13);
3113        assert_eq!(st.user_fee_increase(), 13);
3114
3115        let mut st = sample_batch_st_with_delete();
3116        st.set_user_fee_increase(101);
3117        assert_eq!(st.user_fee_increase(), 101);
3118
3119        let mut st = sample_identity_update_st();
3120        st.set_user_fee_increase(77);
3121        assert_eq!(st.user_fee_increase(), 77);
3122
3123        // Shielded no-ops: value stays 0.
3124        let mut shielded = sample_shielded_transfer_st();
3125        shielded.set_user_fee_increase(99);
3126        assert_eq!(shielded.user_fee_increase(), 0);
3127
3128        let mut withdrawal = sample_shielded_withdrawal_st();
3129        withdrawal.set_user_fee_increase(99);
3130        assert_eq!(withdrawal.user_fee_increase(), 0);
3131
3132        let mut unshield = sample_unshield_st();
3133        unshield.set_user_fee_increase(99);
3134        assert_eq!(unshield.user_fee_increase(), 0);
3135    }
3136
3137    // --- set_signature: exercises the `true` arms we didn't test before
3138    // (DataContractCreate/Update/Batch/IdentityUpdate) and the `false` arms
3139    // (shielded transitions).
3140    #[test]
3141    fn test_set_signature_false_for_shielded_and_identity_create_topup() {
3142        // `false` arms: shield*, shielded*, unshield, address* (no-op, returns false).
3143        let mut st = sample_unshield_st();
3144        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
3145        let mut st = sample_shielded_transfer_st();
3146        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
3147        let mut st = sample_shielded_withdrawal_st();
3148        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
3149    }
3150
3151    #[test]
3152    fn test_set_signature_true_for_newly_covered_variants() {
3153        let mut st = sample_data_contract_create_st();
3154        assert!(st.set_signature(BinaryData::new(vec![0x11; 65])));
3155        assert_eq!(st.signature().unwrap().as_slice(), &[0x11; 65]);
3156
3157        let mut st = sample_data_contract_update_st();
3158        assert!(st.set_signature(BinaryData::new(vec![0x22; 65])));
3159        assert_eq!(st.signature().unwrap().as_slice(), &[0x22; 65]);
3160
3161        let mut st = sample_batch_st_with_delete();
3162        assert!(st.set_signature(BinaryData::new(vec![0x33; 65])));
3163        assert_eq!(st.signature().unwrap().as_slice(), &[0x33; 65]);
3164
3165        let mut st = sample_identity_update_st();
3166        assert!(st.set_signature(BinaryData::new(vec![0x44; 65])));
3167        assert_eq!(st.signature().unwrap().as_slice(), &[0x44; 65]);
3168    }
3169
3170    // --- signature_public_key_id: identity-signed arms return Some, others
3171    // (shielded/identity-create/topup/address) return None.
3172    #[test]
3173    fn test_signature_public_key_id_returns_none_for_non_signed() {
3174        // IdentityCreate / IdentityTopUp / shielded / address variants are all
3175        // "not identity-signed" and return None.
3176        assert!(sample_identity_create_st()
3177            .signature_public_key_id()
3178            .is_none());
3179        assert!(sample_identity_top_up_st()
3180            .signature_public_key_id()
3181            .is_none());
3182        assert!(sample_unshield_st().signature_public_key_id().is_none());
3183        assert!(sample_shielded_transfer_st()
3184            .signature_public_key_id()
3185            .is_none());
3186        assert!(sample_shielded_withdrawal_st()
3187            .signature_public_key_id()
3188            .is_none());
3189    }
3190
3191    #[test]
3192    fn test_signature_public_key_id_for_signed_variants() {
3193        assert_eq!(
3194            sample_data_contract_create_st().signature_public_key_id(),
3195            Some(2)
3196        );
3197        assert_eq!(
3198            sample_data_contract_update_st().signature_public_key_id(),
3199            Some(6)
3200        );
3201        assert_eq!(
3202            sample_batch_st_with_delete().signature_public_key_id(),
3203            Some(7)
3204        );
3205        assert_eq!(
3206            sample_identity_update_st().signature_public_key_id(),
3207            Some(33)
3208        );
3209    }
3210
3211    // --- set_signature_public_key_id: no-op for IdentityCreate/TopUp and
3212    // shielded variants; updates for identity-signed variants. ---
3213    #[test]
3214    fn test_set_signature_public_key_id_noop_for_non_signed() {
3215        // These variants are not identity-signed; setter is a no-op in the
3216        // call_method_identity_signed! macro.
3217        let mut st = sample_identity_create_st();
3218        st.set_signature_public_key_id(100);
3219        assert_eq!(st.signature_public_key_id(), None);
3220
3221        let mut st = sample_identity_top_up_st();
3222        st.set_signature_public_key_id(100);
3223        assert_eq!(st.signature_public_key_id(), None);
3224
3225        let mut st = sample_unshield_st();
3226        st.set_signature_public_key_id(100);
3227        assert_eq!(st.signature_public_key_id(), None);
3228    }
3229
3230    #[test]
3231    fn test_set_signature_public_key_id_updates_for_signed_variants() {
3232        let mut st = sample_data_contract_create_st();
3233        st.set_signature_public_key_id(42);
3234        assert_eq!(st.signature_public_key_id(), Some(42));
3235
3236        let mut st = sample_batch_st_with_delete();
3237        st.set_signature_public_key_id(43);
3238        assert_eq!(st.signature_public_key_id(), Some(43));
3239
3240        let mut st = sample_identity_update_st();
3241        st.set_signature_public_key_id(44);
3242        assert_eq!(st.signature_public_key_id(), Some(44));
3243    }
3244
3245    // --- required_number_of_private_keys defaults to 1 for "signed" variants
3246    // and 0 for shielded ones.
3247    #[test]
3248    fn test_required_number_of_private_keys_various_variants() {
3249        assert_eq!(
3250            sample_data_contract_create_st().required_number_of_private_keys(),
3251            1
3252        );
3253        assert_eq!(
3254            sample_data_contract_update_st().required_number_of_private_keys(),
3255            1
3256        );
3257        assert_eq!(
3258            sample_batch_st_with_delete().required_number_of_private_keys(),
3259            1
3260        );
3261        assert_eq!(
3262            sample_identity_update_st().required_number_of_private_keys(),
3263            1
3264        );
3265        assert_eq!(
3266            sample_identity_create_st().required_number_of_private_keys(),
3267            1
3268        );
3269        // Shielded variants return 0 unconditionally.
3270        assert_eq!(
3271            sample_shielded_transfer_st().required_number_of_private_keys(),
3272            0
3273        );
3274        assert_eq!(
3275            sample_shielded_withdrawal_st().required_number_of_private_keys(),
3276            0
3277        );
3278        assert_eq!(sample_unshield_st().required_number_of_private_keys(), 0);
3279    }
3280
3281    // --- inputs(): None for all these variants (covers the big
3282    // wildcard/None arm in the match).
3283    #[test]
3284    fn test_inputs_none_for_many_variants() {
3285        assert!(sample_data_contract_create_st().inputs().is_none());
3286        assert!(sample_data_contract_update_st().inputs().is_none());
3287        assert!(sample_batch_st_with_delete().inputs().is_none());
3288        assert!(sample_identity_create_st().inputs().is_none());
3289        assert!(sample_identity_top_up_st().inputs().is_none());
3290        assert!(sample_identity_update_st().inputs().is_none());
3291        // Shielded variants also return None for inputs().
3292        assert!(sample_unshield_st().inputs().is_none());
3293        assert!(sample_shielded_transfer_st().inputs().is_none());
3294        assert!(sample_shielded_withdrawal_st().inputs().is_none());
3295    }
3296
3297    // --- optional_asset_lock_proof: None for everything that isn't
3298    // IdentityCreate / IdentityTopUp / ShieldFromAssetLock. The IdentityCreate
3299    // default contains the asset lock proof field, so this forwards to its
3300    // implementation.
3301    #[test]
3302    fn test_optional_asset_lock_proof_returns_none_for_wildcard_arms() {
3303        assert!(sample_data_contract_create_st()
3304            .optional_asset_lock_proof()
3305            .is_none());
3306        assert!(sample_data_contract_update_st()
3307            .optional_asset_lock_proof()
3308            .is_none());
3309        assert!(sample_batch_st_with_delete()
3310            .optional_asset_lock_proof()
3311            .is_none());
3312        assert!(sample_identity_update_st()
3313            .optional_asset_lock_proof()
3314            .is_none());
3315        assert!(sample_unshield_st().optional_asset_lock_proof().is_none());
3316        assert!(sample_shielded_transfer_st()
3317            .optional_asset_lock_proof()
3318            .is_none());
3319        assert!(sample_shielded_withdrawal_st()
3320            .optional_asset_lock_proof()
3321            .is_none());
3322    }
3323
3324    // --- required_asset_lock_balance_for_processing_start returns an
3325    // CorruptedCodeExecution error for non asset-lock variants. Exercise
3326    // additional arms beyond what the original transfer test covered.
3327    #[test]
3328    fn test_required_asset_lock_balance_errors_for_other_non_asset_lock_variants() {
3329        let platform_version = PlatformVersion::latest();
3330
3331        let cases: Vec<(&str, StateTransition)> = vec![
3332            ("DataContractCreate", sample_data_contract_create_st()),
3333            ("DataContractUpdate", sample_data_contract_update_st()),
3334            ("Batch", sample_batch_st_with_delete()),
3335            ("IdentityUpdate", sample_identity_update_st()),
3336            ("MasternodeVote", sample_masternode_vote_st()),
3337            ("Unshield", sample_unshield_st()),
3338            ("ShieldedTransfer", sample_shielded_transfer_st()),
3339            ("ShieldedWithdrawal", sample_shielded_withdrawal_st()),
3340        ];
3341
3342        for (label, st) in cases {
3343            let err = st
3344                .required_asset_lock_balance_for_processing_start(platform_version)
3345                .expect_err(&format!("expected error for {label}"));
3346            match err {
3347                ProtocolError::CorruptedCodeExecution(msg) => {
3348                    assert!(
3349                        msg.contains("is not an asset lock transaction"),
3350                        "unexpected error for {label}: {msg}"
3351                    );
3352                }
3353                other => panic!("expected CorruptedCodeExecution for {label}, got {other:?}"),
3354            }
3355        }
3356    }
3357
3358    // --- unique_identifiers: covers the call_method! dispatch for arms
3359    // beyond credit transfer. Each variant's `unique_identifiers`
3360    // implementation returns a non-empty vector; the individual identifier
3361    // strings may be empty for some variants whose IDs are encoded as empty
3362    // (this method simply shouldn't panic or short-circuit).
3363    #[test]
3364    fn test_unique_identifiers_non_empty_for_other_variants() {
3365        for st in [
3366            sample_data_contract_create_st(),
3367            sample_data_contract_update_st(),
3368            sample_batch_st_with_delete(),
3369            sample_identity_create_st(),
3370            sample_identity_top_up_st(),
3371            sample_identity_update_st(),
3372        ] {
3373            let ids = st.unique_identifiers();
3374            assert!(!ids.is_empty(), "unique_identifiers should not be empty");
3375        }
3376    }
3377
3378    // --- security_level_requirement returns None for identity-create/topup
3379    // and for every shielded/address variant. This hits the None arms in
3380    // call_getter_method_identity_signed!.
3381    #[test]
3382    fn test_security_level_requirement_returns_none_for_non_signed_variants() {
3383        let purpose = Purpose::AUTHENTICATION;
3384        assert!(sample_identity_create_st()
3385            .security_level_requirement(purpose)
3386            .is_none());
3387        assert!(sample_identity_top_up_st()
3388            .security_level_requirement(purpose)
3389            .is_none());
3390        assert!(sample_unshield_st()
3391            .security_level_requirement(purpose)
3392            .is_none());
3393        assert!(sample_shielded_transfer_st()
3394            .security_level_requirement(purpose)
3395            .is_none());
3396        assert!(sample_shielded_withdrawal_st()
3397            .security_level_requirement(purpose)
3398            .is_none());
3399    }
3400
3401    #[test]
3402    fn test_purpose_requirement_returns_none_for_non_signed_variants() {
3403        assert!(sample_identity_create_st().purpose_requirement().is_none());
3404        assert!(sample_identity_top_up_st().purpose_requirement().is_none());
3405        assert!(sample_unshield_st().purpose_requirement().is_none());
3406        assert!(sample_shielded_transfer_st()
3407            .purpose_requirement()
3408            .is_none());
3409        assert!(sample_shielded_withdrawal_st()
3410            .purpose_requirement()
3411            .is_none());
3412    }
3413
3414    // --- From impls: each From<Outer> → StateTransition uses `derive_more::From`.
3415    #[test]
3416    fn test_from_outer_data_contract_create_into_state_transition() {
3417        let outer: DataContractCreateTransition =
3418            DataContractCreateTransition::V0(DataContractCreateTransitionV0 {
3419                data_contract: sample_data_contract_in_serialization_format(),
3420                identity_nonce: 1,
3421                user_fee_increase: 0,
3422                signature_public_key_id: 0,
3423                signature: Default::default(),
3424            });
3425        let st: StateTransition = outer.into();
3426        assert!(matches!(st, StateTransition::DataContractCreate(_)));
3427    }
3428
3429    #[test]
3430    fn test_from_outer_data_contract_update_into_state_transition() {
3431        let outer: DataContractUpdateTransition =
3432            DataContractUpdateTransition::V0(DataContractUpdateTransitionV0 {
3433                identity_contract_nonce: 2,
3434                data_contract: sample_data_contract_in_serialization_format(),
3435                user_fee_increase: 0,
3436                signature_public_key_id: 0,
3437                signature: Default::default(),
3438            });
3439        let st: StateTransition = outer.into();
3440        assert!(matches!(st, StateTransition::DataContractUpdate(_)));
3441    }
3442
3443    #[test]
3444    fn test_from_outer_batch_into_state_transition() {
3445        let outer: BatchTransition = BatchTransition::V0(BatchTransitionV0::default());
3446        let st: StateTransition = outer.into();
3447        assert!(matches!(st, StateTransition::Batch(_)));
3448    }
3449
3450    #[test]
3451    fn test_from_outer_identity_create_into_state_transition() {
3452        let outer: IdentityCreateTransition =
3453            IdentityCreateTransition::V0(IdentityCreateTransitionV0::default());
3454        let st: StateTransition = outer.into();
3455        assert!(matches!(st, StateTransition::IdentityCreate(_)));
3456    }
3457
3458    #[test]
3459    fn test_from_outer_identity_update_into_state_transition() {
3460        let outer: IdentityUpdateTransition =
3461            IdentityUpdateTransition::V0(IdentityUpdateTransitionV0::default());
3462        let st: StateTransition = outer.into();
3463        assert!(matches!(st, StateTransition::IdentityUpdate(_)));
3464    }
3465
3466    // --- transaction_id + clone for additional variants — triggers the
3467    // serialize path for each arm.
3468    #[test]
3469    fn test_transaction_id_and_clone_for_identity_update() {
3470        let st = sample_identity_update_st();
3471        let id_a = st.transaction_id().expect("hash should succeed");
3472        let cloned = st.clone();
3473        let id_b = cloned.transaction_id().expect("hash should succeed");
3474        assert_eq!(id_a, id_b);
3475        assert_eq!(id_a.len(), 32);
3476    }
3477
3478    #[test]
3479    fn test_transaction_id_and_clone_for_data_contract_create() {
3480        let st = sample_data_contract_create_st();
3481        let id_a = st.transaction_id().expect("hash should succeed");
3482        let cloned = st.clone();
3483        let id_b = cloned.transaction_id().expect("hash should succeed");
3484        assert_eq!(id_a, id_b);
3485        assert_eq!(id_a.len(), 32);
3486    }
3487
3488    // --- serialize round-trip for variants beyond credit transfer. ---
3489    #[test]
3490    fn test_serialize_roundtrip_identity_update() {
3491        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
3492        let original = sample_identity_update_st();
3493        let bytes =
3494            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
3495        let restored = StateTransition::deserialize_from_bytes_untrusted(&bytes)
3496            .expect("deserialize should succeed");
3497        assert_eq!(original, restored);
3498    }
3499
3500    #[test]
3501    fn test_serialize_roundtrip_data_contract_update() {
3502        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
3503        let original = sample_data_contract_update_st();
3504        let bytes =
3505            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
3506        let restored = StateTransition::deserialize_from_bytes_untrusted(&bytes)
3507            .expect("deserialize should succeed");
3508        assert_eq!(original, restored);
3509    }
3510
3511    #[test]
3512    fn test_serialize_roundtrip_batch_empty() {
3513        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
3514        let original = sample_batch_st_empty();
3515        let bytes =
3516            PlatformSerializable::serialize_to_bytes(&original).expect("serialize should succeed");
3517        let restored = StateTransition::deserialize_from_bytes_untrusted(&bytes)
3518            .expect("deserialize should succeed");
3519        assert_eq!(original, restored);
3520    }
3521
3522    // --- deserialize_from_bytes_in_version error path: craft bytes for a
3523    // variant whose `active_version_range()` starts at 11 or 12 and then
3524    // attempt to deserialize them with a PlatformVersion whose protocol
3525    // version is below that range. Exercises the
3526    // `StateTransitionIsNotActiveError` arm.
3527    // ---
3528    #[cfg(all(feature = "state-transitions", feature = "validation"))]
3529    #[test]
3530    fn test_deserialize_from_bytes_in_version_returns_not_active_error() {
3531        use crate::serialization::PlatformSerializable;
3532
3533        // ShieldedTransfer has active_version_range = 12..=LATEST_VERSION.
3534        let original = sample_shielded_transfer_st();
3535        let bytes =
3536            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
3537
3538        // Find a real PlatformVersion whose protocol_version is < 12 so the
3539        // range check rejects it. PlatformVersion::get(1) corresponds to
3540        // protocol version 1 which is guaranteed below any shielded range.
3541        let low_version = PlatformVersion::get(1).expect("platform version 1 exists");
3542        assert!(
3543            low_version.protocol_version < 12,
3544            "expected sub-12 version for this test, got {}",
3545            low_version.protocol_version
3546        );
3547
3548        let err = StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, low_version)
3549            .expect_err("expected StateTransitionIsNotActiveError for sub-12 protocol");
3550        match err {
3551            ProtocolError::StateTransitionError(
3552                crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError {
3553                    state_transition_type,
3554                    active_version_range,
3555                    current_protocol_version,
3556                },
3557            ) => {
3558                assert_eq!(state_transition_type, "ShieldedTransfer");
3559                assert_eq!(current_protocol_version, low_version.protocol_version);
3560                assert!(active_version_range.start() >= &12);
3561            }
3562            other => panic!("expected StateTransitionIsNotActiveError, got {other:?}"),
3563        }
3564    }
3565
3566    // A version 1 data contract create carries contract groups, which only exist from
3567    // protocol version 14. Below that a node must reject it rather than create the
3568    // contract and drop the group data.
3569    #[test]
3570    fn test_data_contract_create_v1_is_not_active_before_protocol_version_14() {
3571        use crate::serialization::PlatformSerializable;
3572
3573        let original = sample_data_contract_create_v1_st();
3574        assert_eq!(original.active_version_range(), 14..=LATEST_VERSION);
3575
3576        let bytes =
3577            PlatformSerializable::serialize_to_bytes(&original).expect("serialize succeeds");
3578
3579        let version_13 = PlatformVersion::get(13).expect("platform version 13 exists");
3580        let err = StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_13)
3581            .expect_err("expected StateTransitionIsNotActiveError at protocol version 13");
3582        match err {
3583            ProtocolError::StateTransitionError(
3584                crate::state_transition::errors::StateTransitionError::StateTransitionIsNotActiveError {
3585                    active_version_range,
3586                    current_protocol_version,
3587                    ..
3588                },
3589            ) => {
3590                assert_eq!(current_protocol_version, 13);
3591                assert_eq!(*active_version_range.start(), 14);
3592            }
3593            other => panic!("expected StateTransitionIsNotActiveError, got {other:?}"),
3594        }
3595
3596        let version_14 = PlatformVersion::get(14).expect("platform version 14 exists");
3597        StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_14)
3598            .expect("a version 1 create is active at protocol version 14");
3599    }
3600
3601    #[test]
3602    fn should_gate_identity_transitions_carrying_a_contract_group_bound_key_to_protocol_version_14()
3603    {
3604        use crate::identity::contract_bounds::ContractBounds;
3605        use crate::identity::{KeyType, Purpose, SecurityLevel};
3606        use crate::serialization::PlatformSerializable;
3607        use crate::state_transition::errors::StateTransitionError;
3608        use crate::state_transition::public_key_in_creation::v0::IdentityPublicKeyInCreationV0;
3609        use crate::state_transition::public_key_in_creation::IdentityPublicKeyInCreation;
3610
3611        let key = |contract_bounds| {
3612            IdentityPublicKeyInCreation::V0(IdentityPublicKeyInCreationV0 {
3613                id: 1,
3614                key_type: KeyType::ECDSA_SECP256K1,
3615                purpose: Purpose::AUTHENTICATION,
3616                security_level: SecurityLevel::HIGH,
3617                contract_bounds,
3618                read_only: false,
3619                data: BinaryData::new(vec![2u8; 33]),
3620                signature: BinaryData::default(),
3621            })
3622        };
3623        let contract_bound = Some(ContractBounds::SingleContract {
3624            id: Identifier::from([9u8; 32]),
3625        });
3626        let group_bound = Some(ContractBounds::ContractGroup {
3627            id: Identifier::from([9u8; 32]),
3628        });
3629        let create = |bounds| {
3630            StateTransition::IdentityCreate(IdentityCreateTransition::V0(
3631                IdentityCreateTransitionV0 {
3632                    identity_id: Identifier::from([3u8; 32]),
3633                    public_keys: vec![key(bounds)],
3634                    ..Default::default()
3635                },
3636            ))
3637        };
3638        let update = |bounds| {
3639            let StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(mut v0)) =
3640                sample_identity_update_st()
3641            else {
3642                panic!("expected a version 0 identity update");
3643            };
3644            v0.add_public_keys = vec![key(bounds)];
3645            StateTransition::IdentityUpdate(IdentityUpdateTransition::V0(v0))
3646        };
3647
3648        assert_eq!(
3649            create(contract_bound.clone()).active_version_range(),
3650            ALL_VERSIONS
3651        );
3652        assert_eq!(
3653            create(group_bound.clone()).active_version_range(),
3654            14..=LATEST_VERSION
3655        );
3656        assert_eq!(update(contract_bound).active_version_range(), ALL_VERSIONS);
3657        assert_eq!(
3658            update(group_bound.clone()).active_version_range(),
3659            14..=LATEST_VERSION
3660        );
3661
3662        let bytes = PlatformSerializable::serialize_to_bytes(&update(group_bound))
3663            .expect("serialize succeeds");
3664        let version_13 = PlatformVersion::get(13).expect("platform version 13 exists");
3665        let err = StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_13)
3666            .expect_err("a contract group bound key is not active at protocol version 13");
3667        assert!(
3668            matches!(
3669                err,
3670                ProtocolError::StateTransitionError(
3671                    StateTransitionError::StateTransitionIsNotActiveError { .. }
3672                )
3673            ),
3674            "{err:?}"
3675        );
3676        let version_14 = PlatformVersion::get(14).expect("platform version 14 exists");
3677        StateTransition::deserialize_from_bytes_untrusted_in_version(&bytes, version_14)
3678            .expect("a contract group bound key is active at protocol version 14");
3679    }
3680
3681    // -----------------------------------------------------------------------
3682    // Additional coverage: variants not yet exercised.
3683    //
3684    // The tests below target:
3685    //   * IdentityCreditWithdrawal::V1 (previously only V0 was covered).
3686    //   * IdentityCreditTransferToAddresses (its own top-level arm).
3687    //   * AddressFundingFromAssetLock (identity-signed + asset-lock arm).
3688    //   * AddressCreditWithdrawal (not identity-signed, address-funds arm).
3689    //   * ShieldFromAssetLock (asset-lock, non-identity-signed).
3690    //   * Batch with a token transition (nested enum, previously only Delete).
3691    // -----------------------------------------------------------------------
3692
3693    use crate::state_transition::identity_credit_transfer_to_addresses_transition::v0::IdentityCreditTransferToAddressesTransitionV0;
3694    use crate::state_transition::identity_credit_transfer_to_addresses_transition::IdentityCreditTransferToAddressesTransition;
3695    use crate::state_transition::identity_credit_withdrawal_transition::v1::IdentityCreditWithdrawalTransitionV1;
3696    use crate::withdrawal::Pooling as WithdrawalPooling;
3697
3698    fn sample_withdrawal_v1_st() -> StateTransition {
3699        let v1 = IdentityCreditWithdrawalTransitionV1 {
3700            identity_id: Identifier::from([12u8; 32]),
3701            amount: 777,
3702            core_fee_per_byte: 2,
3703            pooling: WithdrawalPooling::Standard,
3704            output_script: None,
3705            nonce: 9,
3706            user_fee_increase: 4,
3707            signature_public_key_id: 21,
3708            signature: BinaryData::new(vec![0x12; 65]),
3709        };
3710        StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V1(v1))
3711    }
3712
3713    fn sample_credit_transfer_to_addresses_st() -> StateTransition {
3714        let v0 = IdentityCreditTransferToAddressesTransitionV0 {
3715            identity_id: Identifier::from([13u8; 32]),
3716            ..Default::default()
3717        };
3718        StateTransition::IdentityCreditTransferToAddresses(
3719            IdentityCreditTransferToAddressesTransition::V0(v0),
3720        )
3721    }
3722
3723    fn sample_address_credit_withdrawal_st() -> StateTransition {
3724        use crate::state_transition::address_credit_withdrawal_transition::v0::AddressCreditWithdrawalTransitionV0;
3725        StateTransition::AddressCreditWithdrawal(AddressCreditWithdrawalTransition::V0(
3726            AddressCreditWithdrawalTransitionV0::default(),
3727        ))
3728    }
3729
3730    fn sample_shield_from_asset_lock_st() -> StateTransition {
3731        use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0;
3732        StateTransition::ShieldFromAssetLock(ShieldFromAssetLockTransition::V0(
3733            ShieldFromAssetLockTransitionV0 {
3734                asset_lock_proof: Default::default(),
3735                actions: vec![],
3736                value_balance: 100,
3737                anchor: [0u8; 32],
3738                proof: vec![],
3739                binding_signature: [0u8; 64],
3740                surplus_output: None,
3741                signature: BinaryData::new(vec![0x55; 65]),
3742            },
3743        ))
3744    }
3745
3746    // ---------- IdentityCreditWithdrawal V1 accessors ----------
3747
3748    #[test]
3749    fn test_withdrawal_v1_name_and_type() {
3750        let st = sample_withdrawal_v1_st();
3751        assert_eq!(st.name(), "IdentityCreditWithdrawal");
3752        assert_eq!(
3753            st.state_transition_type(),
3754            StateTransitionType::IdentityCreditWithdrawal
3755        );
3756    }
3757
3758    #[test]
3759    fn test_withdrawal_v1_is_identity_signed_true() {
3760        assert!(sample_withdrawal_v1_st().is_identity_signed());
3761    }
3762
3763    #[test]
3764    fn test_withdrawal_v1_signature_and_owner_and_key_id() {
3765        let st = sample_withdrawal_v1_st();
3766        // signature accessor -> Some
3767        let sig = st.signature().expect("V1 withdrawal has a signature");
3768        assert_eq!(sig.as_slice(), &[0x12; 65]);
3769        // owner_id delegates to identity_id
3770        assert_eq!(st.owner_id(), Some(Identifier::from([12u8; 32])));
3771        assert_eq!(st.signature_public_key_id(), Some(21));
3772        assert_eq!(st.user_fee_increase(), 4);
3773    }
3774
3775    #[test]
3776    fn test_withdrawal_v1_set_signature_and_fee_and_key_id() {
3777        let mut st = sample_withdrawal_v1_st();
3778        assert!(st.set_signature(BinaryData::new(vec![0x99; 65])));
3779        assert_eq!(st.signature().unwrap().as_slice(), &[0x99; 65]);
3780
3781        st.set_user_fee_increase(33);
3782        assert_eq!(st.user_fee_increase(), 33);
3783
3784        st.set_signature_public_key_id(64);
3785        assert_eq!(st.signature_public_key_id(), Some(64));
3786    }
3787
3788    #[test]
3789    fn test_withdrawal_v1_serialize_roundtrip_via_state_transition() {
3790        use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
3791        let original = sample_withdrawal_v1_st();
3792        let bytes = PlatformSerializable::serialize_to_bytes(&original).expect("serialize ok");
3793        let restored =
3794            StateTransition::deserialize_from_bytes_untrusted(&bytes).expect("deserialize ok");
3795        assert_eq!(original, restored);
3796        // The restored variant must still be V1, not V0 — exercises the
3797        // feature-version dispatch in deserialize.
3798        match restored {
3799            StateTransition::IdentityCreditWithdrawal(IdentityCreditWithdrawalTransition::V1(
3800                _,
3801            )) => {}
3802            other => panic!("expected V1 inner variant, got: {:?}", other),
3803        }
3804    }
3805
3806    #[test]
3807    fn test_withdrawal_v1_transaction_id_differs_from_v0() {
3808        // V0 and V1 carry different serialized forms → distinct transaction ids.
3809        let v0 = sample_withdrawal_st();
3810        let v1 = sample_withdrawal_v1_st();
3811        let id_v0 = v0.transaction_id().expect("v0 hash");
3812        let id_v1 = v1.transaction_id().expect("v1 hash");
3813        assert_ne!(id_v0, id_v1);
3814    }
3815
3816    #[test]
3817    fn test_withdrawal_v1_required_asset_lock_balance_errors() {
3818        let err = sample_withdrawal_v1_st()
3819            .required_asset_lock_balance_for_processing_start(PlatformVersion::latest())
3820            .expect_err("withdrawal is not an asset lock ST");
3821        matches!(err, ProtocolError::CorruptedCodeExecution(_));
3822    }
3823
3824    // ---------- IdentityCreditTransferToAddresses ----------
3825
3826    #[test]
3827    fn test_credit_transfer_to_addresses_name_and_type() {
3828        let st = sample_credit_transfer_to_addresses_st();
3829        assert_eq!(st.name(), "IdentityCreditTransferToAddresses");
3830        assert_eq!(
3831            st.state_transition_type(),
3832            StateTransitionType::IdentityCreditTransferToAddresses
3833        );
3834    }
3835
3836    #[test]
3837    fn test_credit_transfer_to_addresses_signature_some_and_owner_some() {
3838        let st = sample_credit_transfer_to_addresses_st();
3839        assert!(st.signature().is_some(), "has a signature field");
3840        assert_eq!(st.owner_id(), Some(Identifier::from([13u8; 32])));
3841    }
3842
3843    #[test]
3844    fn test_credit_transfer_to_addresses_is_identity_signed_true() {
3845        // Not in the "not identity signed" list → should be true.
3846        assert!(sample_credit_transfer_to_addresses_st().is_identity_signed());
3847    }
3848
3849    #[test]
3850    fn test_credit_transfer_to_addresses_inputs_none_active_range_11_latest() {
3851        let st = sample_credit_transfer_to_addresses_st();
3852        assert!(st.inputs().is_none());
3853        // Per mod.rs table: this variant is in the 11..=LATEST_VERSION group.
3854        let range = st.active_version_range();
3855        assert_eq!(*range.start(), 11);
3856        assert_eq!(*range.end(), LATEST_VERSION);
3857    }
3858
3859    #[test]
3860    fn test_credit_transfer_to_addresses_set_signature_returns_true() {
3861        let mut st = sample_credit_transfer_to_addresses_st();
3862        let ok = st.set_signature(BinaryData::new(vec![0x77; 65]));
3863        assert!(ok);
3864        assert_eq!(st.signature().unwrap().as_slice(), &[0x77; 65]);
3865    }
3866
3867    #[test]
3868    fn test_credit_transfer_to_addresses_from_outer_enum() {
3869        let outer = IdentityCreditTransferToAddressesTransition::V0(
3870            IdentityCreditTransferToAddressesTransitionV0::default(),
3871        );
3872        let st: StateTransition = outer.into();
3873        assert!(matches!(
3874            st,
3875            StateTransition::IdentityCreditTransferToAddresses(_)
3876        ));
3877    }
3878
3879    #[test]
3880    fn test_credit_transfer_to_addresses_user_fee_increase_setter() {
3881        let mut st = sample_credit_transfer_to_addresses_st();
3882        st.set_user_fee_increase(55);
3883        assert_eq!(st.user_fee_increase(), 55);
3884    }
3885
3886    // ---------- AddressCreditWithdrawal ----------
3887
3888    #[test]
3889    fn test_address_credit_withdrawal_name_type_and_accessors() {
3890        let st = sample_address_credit_withdrawal_st();
3891        assert_eq!(st.name(), "AddressCreditWithdrawal");
3892        assert_eq!(
3893            st.state_transition_type(),
3894            StateTransitionType::AddressCreditWithdrawal
3895        );
3896        // signature is None for AddressCreditWithdrawal (see mod.rs arm).
3897        assert!(st.signature().is_none());
3898        // owner_id is None for every address-* variant.
3899        assert!(st.owner_id().is_none());
3900        // inputs → Some (delegated to inner struct's inputs map, may be empty).
3901        assert!(st.inputs().is_some());
3902    }
3903
3904    #[test]
3905    fn test_address_credit_withdrawal_set_signature_returns_false() {
3906        let mut st = sample_address_credit_withdrawal_st();
3907        assert!(!st.set_signature(BinaryData::new(vec![0xAB; 65])));
3908    }
3909
3910    #[test]
3911    fn test_address_credit_withdrawal_is_identity_signed_true() {
3912        // Per mod.rs: `is_identity_signed` is !matches!(identity_create/topup/shield*/unshield/shielded*)
3913        // so address-* variants return true — even though signature() returns None.
3914        assert!(sample_address_credit_withdrawal_st().is_identity_signed());
3915    }
3916
3917    #[test]
3918    fn test_address_credit_withdrawal_active_range_is_11_latest() {
3919        let range = sample_address_credit_withdrawal_st().active_version_range();
3920        assert_eq!(*range.start(), 11);
3921        assert_eq!(*range.end(), LATEST_VERSION);
3922    }
3923
3924    // ---------- ShieldFromAssetLock ----------
3925
3926    #[test]
3927    fn test_shield_from_asset_lock_name_type_and_accessors() {
3928        let st = sample_shield_from_asset_lock_st();
3929        assert_eq!(st.name(), "ShieldFromAssetLock");
3930        assert_eq!(
3931            st.state_transition_type(),
3932            StateTransitionType::ShieldFromAssetLock
3933        );
3934        // signature IS present on ShieldFromAssetLock — Some arm.
3935        let sig = st
3936            .signature()
3937            .expect("shield-from-asset-lock has signature");
3938        assert_eq!(sig.as_slice(), &[0x55; 65]);
3939        // owner_id is always None for shielded-* arms.
3940        assert!(st.owner_id().is_none());
3941    }
3942
3943    #[test]
3944    fn test_shield_from_asset_lock_is_not_identity_signed() {
3945        assert!(!sample_shield_from_asset_lock_st().is_identity_signed());
3946    }
3947
3948    #[test]
3949    fn test_shield_from_asset_lock_optional_asset_lock_proof_some() {
3950        // Critical: this is one of the THREE arms where optional_asset_lock_proof
3951        // actually forwards to Some(_). Other Some arms are covered by
3952        // IdentityCreate and IdentityTopUp which have default asset lock proof.
3953        let st = sample_shield_from_asset_lock_st();
3954        assert!(st.optional_asset_lock_proof().is_some());
3955    }
3956
3957    #[test]
3958    fn test_shield_from_asset_lock_user_fee_increase_is_zero_and_setter_noop() {
3959        let mut st = sample_shield_from_asset_lock_st();
3960        assert_eq!(st.user_fee_increase(), 0);
3961        st.set_user_fee_increase(123);
3962        // Set is a no-op per mod.rs table.
3963        assert_eq!(st.user_fee_increase(), 0);
3964    }
3965
3966    #[test]
3967    fn test_shield_from_asset_lock_set_signature_returns_true() {
3968        let mut st = sample_shield_from_asset_lock_st();
3969        assert!(st.set_signature(BinaryData::new(vec![0x44; 65])));
3970        assert_eq!(st.signature().unwrap().as_slice(), &[0x44; 65]);
3971    }
3972
3973    #[test]
3974    fn test_shield_from_asset_lock_required_asset_lock_balance_succeeds() {
3975        // This is the only arm besides IdentityCreate/TopUp/AddressFundingFromAssetLock
3976        // that returns Ok from required_asset_lock_balance_for_processing_start.
3977        let st = sample_shield_from_asset_lock_st();
3978        let result = st.required_asset_lock_balance_for_processing_start(PlatformVersion::latest());
3979        assert!(
3980            result.is_ok(),
3981            "ShieldFromAssetLock should return Ok, got {:?}",
3982            result
3983        );
3984    }
3985
3986    #[test]
3987    fn test_shield_from_asset_lock_active_range_12_latest() {
3988        let range = sample_shield_from_asset_lock_st().active_version_range();
3989        assert_eq!(*range.start(), 12);
3990        assert_eq!(*range.end(), LATEST_VERSION);
3991    }
3992
3993    // ---------- Batch with Token transition exercises TokenTransfer arm
3994    //            in the name() nested match. ----------
3995
3996    #[test]
3997    fn test_batch_with_token_transfer_name_contains_token_transfer() {
3998        use crate::state_transition::batch_transition::batched_transition::token_transition::TokenTransition as TT;
3999        use crate::state_transition::batch_transition::batched_transition::BatchedTransition;
4000        use crate::state_transition::batch_transition::token_base_transition::v0::TokenBaseTransitionV0;
4001        use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition;
4002        use crate::state_transition::batch_transition::token_transfer_transition::v0::TokenTransferTransitionV0;
4003        use crate::state_transition::batch_transition::token_transfer_transition::TokenTransferTransition;
4004        use crate::state_transition::batch_transition::BatchTransitionV1;
4005
4006        let base = TokenBaseTransition::V0(TokenBaseTransitionV0 {
4007            identity_contract_nonce: 1,
4008            token_contract_position: 0,
4009            data_contract_id: Identifier::from([1u8; 32]),
4010            token_id: Identifier::from([2u8; 32]),
4011            using_group_info: None,
4012        });
4013        let token_transfer = TokenTransferTransition::V0(TokenTransferTransitionV0 {
4014            base,
4015            amount: 100,
4016            recipient_id: Identifier::from([3u8; 32]),
4017            public_note: None,
4018            shared_encrypted_note: None,
4019            private_encrypted_note: None,
4020        });
4021
4022        // BatchTransitionV1 is used for tokens. Build a single-token batch.
4023        let batch = BatchTransition::V1(BatchTransitionV1 {
4024            owner_id: Identifier::from([9u8; 32]),
4025            transitions: vec![BatchedTransition::Token(TT::Transfer(token_transfer))],
4026            user_fee_increase: 0,
4027            signature_public_key_id: 0,
4028            signature: BinaryData::new(vec![0u8; 65]),
4029        });
4030        let st = StateTransition::Batch(batch);
4031
4032        assert_eq!(st.name(), "DocumentsBatch([TokenTransfer])");
4033    }
4034
4035    // -----------------------------------------------------------------------
4036    // Cross-variant consistency: transaction_id is a 32-byte blake3/sha256 hash
4037    // of the serialized form. Make sure it's stable across clones for the newly
4038    // covered variants too.
4039    // -----------------------------------------------------------------------
4040
4041    #[test]
4042    fn test_transaction_id_length_32_for_new_variants() {
4043        for st in [
4044            sample_withdrawal_v1_st(),
4045            sample_credit_transfer_to_addresses_st(),
4046            sample_shield_from_asset_lock_st(),
4047        ] {
4048            let id = st.transaction_id().expect("hash");
4049            assert_eq!(id.len(), 32);
4050        }
4051    }
4052
4053    // -----------------------------------------------------------------------
4054    // Clone-and-equality coverage for newly added variants (PartialEq via
4055    // derived impl, exercises the top-level enum's PartialEq arms).
4056    // -----------------------------------------------------------------------
4057
4058    #[test]
4059    fn test_clone_eq_for_new_variants() {
4060        let cases = [
4061            sample_withdrawal_v1_st(),
4062            sample_credit_transfer_to_addresses_st(),
4063            sample_address_credit_withdrawal_st(),
4064            sample_shield_from_asset_lock_st(),
4065        ];
4066        for st in cases {
4067            let cloned = st.clone();
4068            assert_eq!(st, cloned, "clone must be equal for {}", st.name());
4069        }
4070    }
4071
4072    // -----------------------------------------------------------------------
4073    // unique_identifiers() for address-* variants: the implementation
4074    // dispatches via call_method! and each variant returns a non-empty Vec.
4075    // -----------------------------------------------------------------------
4076
4077    #[test]
4078    fn test_unique_identifiers_for_address_and_shielded_variants() {
4079        // The address variants compute identifiers from their `inputs` map.
4080        // With a default (empty inputs) transition, unique_identifiers is empty
4081        // — that's fine, but we still want to exercise the call_method!
4082        // dispatch for these arms without panicking.
4083        for st in [
4084            sample_address_credit_withdrawal_st(),
4085            sample_shield_from_asset_lock_st(),
4086            sample_credit_transfer_to_addresses_st(),
4087            sample_withdrawal_v1_st(),
4088        ] {
4089            // Just calling unique_identifiers exercises the match arm; the
4090            // result may be empty for default-constructed inputs-based
4091            // variants, non-empty for identity-based ones.
4092            let _ids = st.unique_identifiers();
4093        }
4094        // For the identity-based variants the result IS non-empty.
4095        assert!(!sample_withdrawal_v1_st().unique_identifiers().is_empty());
4096        assert!(!sample_credit_transfer_to_addresses_st()
4097            .unique_identifiers()
4098            .is_empty());
4099    }
4100
4101    // -----------------------------------------------------------------------
4102    // sign_with_core_signer byte-parity test
4103    //
4104    // Proves that `StateTransition::sign_with_core_signer` produces a
4105    // byte-identical signature to the legacy `sign_by_private_key` ECDSA path
4106    // when both are driven by the same underlying secret. This is the on-wire
4107    // contract the Swift / external-signer flow depends on: changing the
4108    // digest pre-image or the recoverable-compact encoding would silently
4109    // break asset-lock verification on testnet/mainnet, so we pin both shapes
4110    // here.
4111    // -----------------------------------------------------------------------
4112    #[cfg(all(
4113        feature = "state-transition-signing",
4114        feature = "core_key_wallet",
4115        feature = "bls-signatures"
4116    ))]
4117    #[tokio::test]
4118    async fn sign_with_core_signer_matches_sign_by_private_key_byte_for_byte() {
4119        use async_trait::async_trait;
4120        use dashcore::secp256k1::{
4121            ecdsa, rand::rngs::OsRng, Message, PublicKey, Secp256k1, SecretKey,
4122        };
4123        use key_wallet::bip32::{DerivationPath, ExtendedPubKey};
4124        use key_wallet::signer::{ExtendedPubKeySigner, Signer as KwSigner, SignerMethod};
4125
4126        /// Fixed-key in-memory signer used only by this test. Mirrors how a
4127        /// real KeychainSigner would behave: derive once, sign atomically,
4128        /// return non-recoverable `(Signature, PublicKey)`. The path is
4129        /// ignored — the wrapper holds exactly one key.
4130        #[derive(Debug)]
4131        struct FixedKeySigner {
4132            secret: SecretKey,
4133            public: PublicKey,
4134        }
4135
4136        #[async_trait]
4137        impl KwSigner for FixedKeySigner {
4138            type Error = String;
4139
4140            fn supported_methods(&self) -> &[SignerMethod] {
4141                &[SignerMethod::Digest]
4142            }
4143
4144            async fn sign_ecdsa(
4145                &self,
4146                _path: &DerivationPath,
4147                sighash: [u8; 32],
4148            ) -> Result<(ecdsa::Signature, PublicKey), Self::Error> {
4149                let secp = Secp256k1::new();
4150                let msg = Message::from_digest(sighash);
4151                let sig = secp.sign_ecdsa(&msg, &self.secret);
4152                Ok((sig, self.public))
4153            }
4154
4155            async fn public_key(&self, _path: &DerivationPath) -> Result<PublicKey, Self::Error> {
4156                Ok(self.public)
4157            }
4158        }
4159
4160        #[async_trait]
4161        impl ExtendedPubKeySigner for FixedKeySigner {
4162            async fn extended_public_key(
4163                &self,
4164                _path: &DerivationPath,
4165            ) -> Result<ExtendedPubKey, Self::Error> {
4166                Err("FixedKeySigner does not derive extended public keys".to_string())
4167            }
4168        }
4169
4170        // Generate a single random key. Using the same key on both sides is
4171        // load-bearing: the legacy path signs raw bytes, the signer path
4172        // derives + signs inside the trust boundary. If the digest pre-image
4173        // or compact-encoding differs, the bytes will diverge.
4174        let secp = Secp256k1::new();
4175        let (secret_key, public_key) = secp.generate_keypair(&mut OsRng);
4176        let private_key_bytes = secret_key.secret_bytes();
4177
4178        let signer = FixedKeySigner {
4179            secret: secret_key,
4180            public: public_key,
4181        };
4182        let path = DerivationPath::default();
4183
4184        // Use a sample state transition that exercises signable_bytes() —
4185        // any signable ST works since we're only comparing the signature
4186        // bytes the two paths produce over the SAME `signable_bytes()`.
4187        let mut st_legacy = sample_transfer_st();
4188        let mut st_signer = sample_transfer_st();
4189
4190        // Sanity: both copies must have identical signable_bytes before signing.
4191        assert_eq!(
4192            st_legacy.signable_bytes().expect("legacy signable_bytes"),
4193            st_signer.signable_bytes().expect("signer signable_bytes"),
4194            "signable_bytes pre-image must match across copies"
4195        );
4196
4197        // Legacy path: raw &[u8] private key → 65-byte recoverable compact.
4198        // BLS is only used by `sign_by_private_key` when key_type is BLS12_381 —
4199        // for the ECDSA path it's unused, but the function signature requires
4200        // it, so we pass the NativeBlsModule that's already in the workspace.
4201        let bls = crate::bls::native_bls::NativeBlsModule;
4202        st_legacy
4203            .sign_by_private_key(&private_key_bytes, KeyType::ECDSA_HASH160, &bls)
4204            .expect("sign_by_private_key");
4205
4206        // New signer-driven path: digest → external signer → recovered →
4207        // 65-byte recoverable compact. Byte-identical to the legacy result.
4208        st_signer
4209            .sign_with_core_signer(&path, &signer)
4210            .await
4211            .expect("sign_with_core_signer");
4212
4213        let sig_legacy = st_legacy.signature().expect("legacy signature set");
4214        let sig_signer = st_signer.signature().expect("signer signature set");
4215
4216        assert_eq!(
4217            sig_legacy.as_slice().len(),
4218            65,
4219            "legacy ECDSA signature must be 65 bytes (recoverable compact)"
4220        );
4221        assert_eq!(
4222            sig_signer.as_slice().len(),
4223            65,
4224            "signer ECDSA signature must be 65 bytes (recoverable compact)"
4225        );
4226        assert_eq!(
4227            sig_legacy.as_slice(),
4228            sig_signer.as_slice(),
4229            "sign_with_core_signer must produce byte-identical output to sign_by_private_key"
4230        );
4231    }
4232}