diff --git a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs index 05dee2b8ab2..d22a61f4a01 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -77,7 +77,8 @@ use crate::consensus::basic::state_transition::{ InputWitnessCountMismatchError, InputsNotLessThanOutputsError, InsufficientFundingAmountError, InvalidRemainderOutputCountError, InvalidStateTransitionTypeError, MissingStateTransitionTypeError, OutputAddressAlsoInputError, OutputBelowMinimumError, - OutputsNotGreaterThanInputsError, ShieldedEmptyProofError, ShieldedInvalidValueBalanceError, + OutputsNotGreaterThanInputsError, ShieldedEmptyProofError, + ShieldedEncryptedNoteSizeMismatchError, ShieldedInvalidValueBalanceError, ShieldedNoActionsError, ShieldedTooManyActionsError, ShieldedZeroAnchorError, StateTransitionMaxSizeExceededError, StateTransitionNotActiveError, TransitionNoInputsError, TransitionNoOutputsError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, @@ -673,6 +674,9 @@ pub enum BasicError { #[error(transparent)] ShieldedInvalidValueBalanceError(ShieldedInvalidValueBalanceError), + + #[error(transparent)] + ShieldedEncryptedNoteSizeMismatchError(ShieldedEncryptedNoteSizeMismatchError), } impl From for ConsensusError { diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs index b9acc33f2a7..4b549af9155 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs @@ -14,6 +14,7 @@ mod output_address_also_input_error; mod output_below_minimum_error; mod outputs_not_greater_than_inputs_error; mod shielded_empty_proof_error; +mod shielded_encrypted_note_size_mismatch_error; mod shielded_invalid_value_balance_error; mod shielded_no_actions_error; mod shielded_too_many_actions_error; @@ -43,6 +44,7 @@ pub use output_address_also_input_error::*; pub use output_below_minimum_error::*; pub use outputs_not_greater_than_inputs_error::*; pub use shielded_empty_proof_error::*; +pub use shielded_encrypted_note_size_mismatch_error::*; pub use shielded_invalid_value_balance_error::*; pub use shielded_no_actions_error::*; pub use shielded_too_many_actions_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_encrypted_note_size_mismatch_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_encrypted_note_size_mismatch_error.rs new file mode 100644 index 00000000000..e091503a933 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_encrypted_note_size_mismatch_error.rs @@ -0,0 +1,46 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error( + "Shielded action encrypted_note has invalid size: expected {expected_size} bytes, got {actual_size} bytes" +)] +#[platform_serialize(unversioned)] +pub struct ShieldedEncryptedNoteSizeMismatchError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + expected_size: u32, + actual_size: u32, +} + +impl ShieldedEncryptedNoteSizeMismatchError { + pub fn new(expected_size: u32, actual_size: u32) -> Self { + Self { + expected_size, + actual_size, + } + } + + pub fn expected_size(&self) -> u32 { + self.expected_size + } + + pub fn actual_size(&self) -> u32 { + self.actual_size + } +} + +impl From for ConsensusError { + fn from(err: ShieldedEncryptedNoteSizeMismatchError) -> Self { + Self::BasicError(BasicError::ShieldedEncryptedNoteSizeMismatchError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index ed24cbc15aa..d850c4e046a 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -237,6 +237,7 @@ impl ErrorWithCode for BasicError { Self::ShieldedEmptyProofError(_) => 10820, Self::ShieldedZeroAnchorError(_) => 10821, Self::ShieldedInvalidValueBalanceError(_) => 10822, + Self::ShieldedEncryptedNoteSizeMismatchError(_) => 10823, Self::ShieldedTooManyActionsError(_) => 10825, } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/common_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/common_validation.rs index 3cf21276f38..be56f5516fb 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/common_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/common_validation.rs @@ -1,11 +1,16 @@ use crate::consensus::basic::state_transition::{ - ShieldedEmptyProofError, ShieldedNoActionsError, ShieldedTooManyActionsError, - ShieldedZeroAnchorError, + ShieldedEmptyProofError, ShieldedEncryptedNoteSizeMismatchError, ShieldedNoActionsError, + ShieldedTooManyActionsError, ShieldedZeroAnchorError, }; use crate::consensus::basic::BasicError; use crate::shielded::SerializedAction; use crate::validation::SimpleConsensusValidationResult; +/// Expected size of the encrypted_note field in each SerializedAction. +/// This is epk (32) + enc_ciphertext (104) + out_ciphertext (80) = 216 bytes. +/// Canonical source of truth — drive-abci imports this constant. +pub const ENCRYPTED_NOTE_SIZE: usize = 216; + /// Validate that the actions list is not empty and does not exceed the maximum. pub fn validate_actions_count( actions: &[SerializedAction], @@ -50,6 +55,28 @@ pub fn validate_anchor_not_zero(anchor: &[u8; 32]) -> SimpleConsensusValidationR } } +/// Defense-in-depth: validate that every action's `encrypted_note` field is exactly +/// `ENCRYPTED_NOTE_SIZE` (216) bytes. This rejects malformed data early at the DPP +/// layer before it reaches the ABCI bundle reconstruction, saving network bandwidth. +pub fn validate_encrypted_note_sizes( + actions: &[SerializedAction], +) -> SimpleConsensusValidationResult { + for action in actions { + if action.encrypted_note.len() != ENCRYPTED_NOTE_SIZE { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedEncryptedNoteSizeMismatchError( + ShieldedEncryptedNoteSizeMismatchError::new( + ENCRYPTED_NOTE_SIZE as u32, + action.encrypted_note.len() as u32, + ), + ) + .into(), + ); + } + } + SimpleConsensusValidationResult::new() +} + #[cfg(test)] mod tests { use super::*; @@ -171,4 +198,104 @@ mod tests { result.errors ); } + + // --- validate_encrypted_note_sizes --- + + #[test] + fn validate_encrypted_note_sizes_should_accept_correct_size() { + let actions = vec![dummy_action()]; + let result = validate_encrypted_note_sizes(&actions); + assert!( + result.is_valid(), + "Expected valid, got: {:?}", + result.errors + ); + } + + #[test] + fn validate_encrypted_note_sizes_should_accept_multiple_correct_actions() { + let actions = vec![dummy_action(); 3]; + let result = validate_encrypted_note_sizes(&actions); + assert!( + result.is_valid(), + "Expected valid, got: {:?}", + result.errors + ); + } + + #[test] + fn validate_encrypted_note_sizes_should_reject_too_short() { + let mut action = dummy_action(); + action.encrypted_note = vec![4u8; 100]; // Too short + let actions = vec![action]; + let result = validate_encrypted_note_sizes(&actions); + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::ShieldedEncryptedNoteSizeMismatchError(e) + )] => { + assert_eq!(e.expected_size(), ENCRYPTED_NOTE_SIZE as u32); + assert_eq!(e.actual_size(), 100); + } + ); + } + + #[test] + fn validate_encrypted_note_sizes_should_reject_too_long() { + let mut action = dummy_action(); + action.encrypted_note = vec![4u8; 300]; // Too long + let actions = vec![action]; + let result = validate_encrypted_note_sizes(&actions); + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::ShieldedEncryptedNoteSizeMismatchError(e) + )] => { + assert_eq!(e.expected_size(), ENCRYPTED_NOTE_SIZE as u32); + assert_eq!(e.actual_size(), 300); + } + ); + } + + #[test] + fn validate_encrypted_note_sizes_should_reject_empty() { + let mut action = dummy_action(); + action.encrypted_note = vec![]; // Empty + let actions = vec![action]; + let result = validate_encrypted_note_sizes(&actions); + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::ShieldedEncryptedNoteSizeMismatchError(e) + )] => { + assert_eq!(e.expected_size(), ENCRYPTED_NOTE_SIZE as u32); + assert_eq!(e.actual_size(), 0); + } + ); + } + + #[test] + fn validate_encrypted_note_sizes_should_reject_second_invalid_action() { + let good_action = dummy_action(); + let mut bad_action = dummy_action(); + bad_action.encrypted_note = vec![4u8; 100]; + let actions = vec![good_action, bad_action]; + let result = validate_encrypted_note_sizes(&actions); + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::ShieldedEncryptedNoteSizeMismatchError(_) + )] + ); + } + + #[test] + fn validate_encrypted_note_sizes_should_accept_empty_actions_list() { + let result = validate_encrypted_note_sizes(&[]); + assert!( + result.is_valid(), + "Expected valid for empty actions list, got: {:?}", + result.errors + ); + } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rs index cb79e3f690f..6a8cafc1932 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod common_validation; +pub mod common_validation; pub mod shield_from_asset_lock_transition; pub mod shield_transition; pub mod shielded_transfer_transition; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_validation.rs index eb00a805dee..063061a4b4f 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_validation.rs @@ -2,7 +2,8 @@ use crate::consensus::basic::state_transition::ShieldedInvalidValueBalanceError; use crate::consensus::basic::BasicError; use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; use crate::state_transition::state_transitions::shielded::common_validation::{ - validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, + validate_actions_count, validate_anchor_not_zero, validate_encrypted_note_sizes, + validate_proof_not_empty, }; use crate::state_transition::StateTransitionStructureValidation; use crate::validation::SimpleConsensusValidationResult; @@ -24,6 +25,12 @@ impl StateTransitionStructureValidation for ShieldFromAssetLockTransitionV0 { return result; } + // Each action's encrypted_note must be exactly ENCRYPTED_NOTE_SIZE bytes + let result = validate_encrypted_note_sizes(&self.actions); + if !result.is_valid() { + return result; + } + // value_balance must be > 0 (credits flowing into pool) if self.value_balance == 0 { return SimpleConsensusValidationResult::new_with_error( @@ -114,6 +121,21 @@ mod tests { ); } + #[test] + fn should_reject_invalid_encrypted_note_size() { + let platform_version = PlatformVersion::latest(); + let mut transition = valid_shield_from_asset_lock_transition(); + transition.actions[0].encrypted_note = vec![4u8; 100]; // Wrong size + + let result = transition.validate_structure(platform_version); + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::ShieldedEncryptedNoteSizeMismatchError(_) + )] + ); + } + #[test] fn should_reject_empty_actions() { let platform_version = PlatformVersion::latest(); diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_validation.rs index 1b92ffad1c9..1ee250576fc 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_validation.rs @@ -6,7 +6,8 @@ use crate::consensus::basic::state_transition::{ use crate::consensus::basic::BasicError; use crate::state_transition::shield_transition::v0::ShieldTransitionV0; use crate::state_transition::state_transitions::shielded::common_validation::{ - validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, + validate_actions_count, validate_anchor_not_zero, validate_encrypted_note_sizes, + validate_proof_not_empty, }; use crate::state_transition::StateTransitionStructureValidation; use crate::validation::SimpleConsensusValidationResult; @@ -29,6 +30,12 @@ impl StateTransitionStructureValidation for ShieldTransitionV0 { return result; } + // Each action's encrypted_note must be exactly ENCRYPTED_NOTE_SIZE bytes + let result = validate_encrypted_note_sizes(&self.actions); + if !result.is_valid() { + return result; + } + // Inputs must not be empty (shield requires address funding) if self.inputs.is_empty() { return SimpleConsensusValidationResult::new_with_error( @@ -218,6 +225,21 @@ mod tests { ); } + #[test] + fn should_reject_invalid_encrypted_note_size() { + let platform_version = PlatformVersion::latest(); + let mut transition = valid_shield_transition(); + transition.actions[0].encrypted_note = vec![4u8; 100]; // Wrong size + + let result = transition.validate_structure(platform_version); + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::ShieldedEncryptedNoteSizeMismatchError(_) + )] + ); + } + #[test] fn should_reject_empty_actions() { let platform_version = PlatformVersion::latest(); diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_validation.rs index f034e9ea8ba..8a331ae5d50 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_validation.rs @@ -2,7 +2,8 @@ use crate::consensus::basic::state_transition::ShieldedInvalidValueBalanceError; use crate::consensus::basic::BasicError; use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; use crate::state_transition::state_transitions::shielded::common_validation::{ - validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, + validate_actions_count, validate_anchor_not_zero, validate_encrypted_note_sizes, + validate_proof_not_empty, }; use crate::state_transition::StateTransitionStructureValidation; use crate::validation::SimpleConsensusValidationResult; @@ -24,6 +25,12 @@ impl StateTransitionStructureValidation for ShieldedTransferTransitionV0 { return result; } + // Each action's encrypted_note must be exactly ENCRYPTED_NOTE_SIZE bytes + let result = validate_encrypted_note_sizes(&self.actions); + if !result.is_valid() { + return result; + } + // value_balance must be positive (it IS the fee for shielded transfers) if self.value_balance == 0 { return SimpleConsensusValidationResult::new_with_error( @@ -103,6 +110,21 @@ mod tests { ); } + #[test] + fn should_reject_invalid_encrypted_note_size() { + let platform_version = PlatformVersion::latest(); + let mut transition = valid_shielded_transfer_transition(); + transition.actions[0].encrypted_note = vec![4u8; 100]; // Wrong size + + let result = transition.validate_structure(platform_version); + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::ShieldedEncryptedNoteSizeMismatchError(_) + )] + ); + } + #[test] fn should_reject_empty_actions() { let platform_version = PlatformVersion::latest(); diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_validation.rs index a5c39469f0e..8f725c9a067 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_validation.rs @@ -2,7 +2,8 @@ use crate::consensus::basic::state_transition::ShieldedInvalidValueBalanceError; use crate::consensus::basic::BasicError; use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; use crate::state_transition::state_transitions::shielded::common_validation::{ - validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, + validate_actions_count, validate_anchor_not_zero, validate_encrypted_note_sizes, + validate_proof_not_empty, }; use crate::state_transition::StateTransitionStructureValidation; use crate::validation::SimpleConsensusValidationResult; @@ -24,6 +25,12 @@ impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { return result; } + // Each action's encrypted_note must be exactly ENCRYPTED_NOTE_SIZE bytes + let result = validate_encrypted_note_sizes(&self.actions); + if !result.is_valid() { + return result; + } + // unshielding_amount must be positive and within i64::MAX if self.unshielding_amount == 0 { return SimpleConsensusValidationResult::new_with_error( @@ -108,6 +115,21 @@ mod tests { ); } + #[test] + fn should_reject_invalid_encrypted_note_size() { + let platform_version = PlatformVersion::latest(); + let mut transition = valid_shielded_withdrawal_transition(); + transition.actions[0].encrypted_note = vec![4u8; 100]; // Wrong size + + let result = transition.validate_structure(platform_version); + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::ShieldedEncryptedNoteSizeMismatchError(_) + )] + ); + } + #[test] fn should_reject_empty_actions() { let platform_version = PlatformVersion::latest(); diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_validation.rs index 04a34b616e3..be888647a38 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_validation.rs @@ -1,7 +1,8 @@ use crate::consensus::basic::state_transition::ShieldedInvalidValueBalanceError; use crate::consensus::basic::BasicError; use crate::state_transition::state_transitions::shielded::common_validation::{ - validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, + validate_actions_count, validate_anchor_not_zero, validate_encrypted_note_sizes, + validate_proof_not_empty, }; use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; use crate::state_transition::StateTransitionStructureValidation; @@ -24,6 +25,12 @@ impl StateTransitionStructureValidation for UnshieldTransitionV0 { return result; } + // Each action's encrypted_note must be exactly ENCRYPTED_NOTE_SIZE bytes + let result = validate_encrypted_note_sizes(&self.actions); + if !result.is_valid() { + return result; + } + // unshielding_amount must be positive and within i64::MAX if self.unshielding_amount == 0 { return SimpleConsensusValidationResult::new_with_error( @@ -104,6 +111,21 @@ mod tests { ); } + #[test] + fn should_reject_invalid_encrypted_note_size() { + let platform_version = PlatformVersion::latest(); + let mut transition = valid_unshield_transition(); + transition.actions[0].encrypted_note = vec![4u8; 100]; // Wrong size + + let result = transition.validate_structure(platform_version); + assert_matches!( + result.errors.as_slice(), + [ConsensusError::BasicError( + BasicError::ShieldedEncryptedNoteSizeMismatchError(_) + )] + ); + } + #[test] fn should_reject_empty_actions() { let platform_version = PlatformVersion::latest(); diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield/tests.rs index 83bff25d816..c1c68667c4c 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield/tests.rs @@ -876,12 +876,14 @@ mod tests { let processing_result = process_transition(&platform, transition, platform_version); - // The encrypted_note size check happens in reconstruct_and_verify_bundle, - // which now runs at the processor level before state validation. + // The encrypted_note size check now happens in DPP structure validation + // (before reaching proof verification), returning a BasicError. assert_matches!( processing_result.execution_results().as_slice(), [StateTransitionExecutionResult::UnpaidConsensusError( - ConsensusError::StateError(StateError::InvalidShieldedProofError(_)) + ConsensusError::BasicError(BasicError::ShieldedEncryptedNoteSizeMismatchError( + _ + )) )] ); } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs index d190f57c326..0614b27e1fe 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs @@ -47,7 +47,15 @@ pub fn warmup_shielded_verifying_key() { const EPK_SIZE: usize = 32; const ENC_CIPHERTEXT_SIZE: usize = 104; const OUT_CIPHERTEXT_SIZE: usize = 80; -const ENCRYPTED_NOTE_SIZE: usize = EPK_SIZE + ENC_CIPHERTEXT_SIZE + OUT_CIPHERTEXT_SIZE; // 216 + +// Import the canonical constant from DPP (single source of truth). +use dpp::state_transition::state_transitions::shielded::common_validation::ENCRYPTED_NOTE_SIZE; + +// Compile-time check: component sizes must sum to the canonical constant. +const _: () = assert!( + EPK_SIZE + ENC_CIPHERTEXT_SIZE + OUT_CIPHERTEXT_SIZE == ENCRYPTED_NOTE_SIZE, + "component sizes diverged from ENCRYPTED_NOTE_SIZE" +); /// Reconstructs an orchard `Bundle` from the serialized fields /// of a shielded state transition and verifies the Halo 2 ZK proof along with diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_transfer/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_transfer/tests.rs index 98fa684d345..d700debd995 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_transfer/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_transfer/tests.rs @@ -394,10 +394,13 @@ mod tests { let processing_result = process_transition(&platform, transition, platform_version); + // DPP structure validation now catches this before proof verification assert_matches!( processing_result.execution_results().as_slice(), [StateTransitionExecutionResult::UnpaidConsensusError( - ConsensusError::StateError(StateError::InvalidShieldedProofError(_)) + ConsensusError::BasicError(BasicError::ShieldedEncryptedNoteSizeMismatchError( + _ + )) )] ); } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_withdrawal/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_withdrawal/tests.rs index b77108d13b7..328bcc774bc 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_withdrawal/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_withdrawal/tests.rs @@ -501,10 +501,13 @@ mod tests { let processing_result = process_transition(&platform, transition, platform_version); + // DPP structure validation now catches this before proof verification assert_matches!( processing_result.execution_results().as_slice(), [StateTransitionExecutionResult::UnpaidConsensusError( - ConsensusError::StateError(StateError::InvalidShieldedProofError(_)) + ConsensusError::BasicError(BasicError::ShieldedEncryptedNoteSizeMismatchError( + _ + )) )] ); } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/unshield/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/unshield/tests.rs index a9b82340509..733cfe6df01 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/unshield/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/unshield/tests.rs @@ -463,10 +463,13 @@ mod tests { let processing_result = process_transition(&platform, transition, platform_version); + // DPP structure validation now catches this before proof verification assert_matches!( processing_result.execution_results().as_slice(), [StateTransitionExecutionResult::UnpaidConsensusError( - ConsensusError::StateError(StateError::InvalidShieldedProofError(_)) + ConsensusError::BasicError(BasicError::ShieldedEncryptedNoteSizeMismatchError( + _ + )) )] ); } diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index d32586c219f..8d63701b0b5 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -93,7 +93,7 @@ use dpp::consensus::state::shielded::insufficient_shielded_fee_error::Insufficie use dpp::consensus::state::shielded::invalid_anchor_error::InvalidAnchorError; use dpp::consensus::state::shielded::invalid_shielded_proof_error::InvalidShieldedProofError; use dpp::consensus::state::shielded::nullifier_already_spent_error::NullifierAlreadySpentError; -use dpp::consensus::basic::state_transition::{StateTransitionNotActiveError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, InputWitnessCountMismatchError, TransitionNoInputsError, TransitionNoOutputsError, FeeStrategyEmptyError, FeeStrategyDuplicateError, FeeStrategyIndexOutOfBoundsError, FeeStrategyTooManyStepsError, InputBelowMinimumError, OutputBelowMinimumError, InputOutputBalanceMismatchError, OutputsNotGreaterThanInputsError, WithdrawalBalanceMismatchError, InsufficientFundingAmountError, InputsNotLessThanOutputsError, OutputAddressAlsoInputError, InvalidRemainderOutputCountError, WithdrawalBelowMinAmountError, ShieldedNoActionsError, ShieldedTooManyActionsError, ShieldedEmptyProofError, ShieldedZeroAnchorError, ShieldedInvalidValueBalanceError}; +use dpp::consensus::basic::state_transition::{StateTransitionNotActiveError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, InputWitnessCountMismatchError, TransitionNoInputsError, TransitionNoOutputsError, FeeStrategyEmptyError, FeeStrategyDuplicateError, FeeStrategyIndexOutOfBoundsError, FeeStrategyTooManyStepsError, InputBelowMinimumError, OutputBelowMinimumError, InputOutputBalanceMismatchError, OutputsNotGreaterThanInputsError, WithdrawalBalanceMismatchError, InsufficientFundingAmountError, InputsNotLessThanOutputsError, OutputAddressAlsoInputError, InvalidRemainderOutputCountError, WithdrawalBelowMinAmountError, ShieldedNoActionsError, ShieldedTooManyActionsError, ShieldedEmptyProofError, ShieldedZeroAnchorError, ShieldedInvalidValueBalanceError, ShieldedEncryptedNoteSizeMismatchError}; use dpp::consensus::state::voting::masternode_incorrect_voter_identity_id_error::MasternodeIncorrectVoterIdentityIdError; use dpp::consensus::state::voting::masternode_incorrect_voting_address_error::MasternodeIncorrectVotingAddressError; use dpp::consensus::state::voting::masternode_not_found_error::MasternodeNotFoundError; @@ -956,6 +956,9 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::ShieldedInvalidValueBalanceError(e) => { generic_consensus_error!(ShieldedInvalidValueBalanceError, e).into() } + BasicError::ShieldedEncryptedNoteSizeMismatchError(e) => { + generic_consensus_error!(ShieldedEncryptedNoteSizeMismatchError, e).into() + } } }