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