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